##// END OF EJS Templates
revlog: change addgroup callbacks to take revision numbers...
Joerg Sonnenberger -
r47259:7a93b7b3 default
parent child Browse files
Show More
@@ -1,1301 +1,1301 b''
1 # sqlitestore.py - Storage backend that uses SQLite
1 # sqlitestore.py - Storage backend that uses SQLite
2 #
2 #
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.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 """store repository data in SQLite (EXPERIMENTAL)
8 """store repository data in SQLite (EXPERIMENTAL)
9
9
10 The sqlitestore extension enables the storage of repository data in SQLite.
10 The sqlitestore extension enables the storage of repository data in SQLite.
11
11
12 This extension is HIGHLY EXPERIMENTAL. There are NO BACKWARDS COMPATIBILITY
12 This extension is HIGHLY EXPERIMENTAL. There are NO BACKWARDS COMPATIBILITY
13 GUARANTEES. This means that repositories created with this extension may
13 GUARANTEES. This means that repositories created with this extension may
14 only be usable with the exact version of this extension/Mercurial that was
14 only be usable with the exact version of this extension/Mercurial that was
15 used. The extension attempts to enforce this in order to prevent repository
15 used. The extension attempts to enforce this in order to prevent repository
16 corruption.
16 corruption.
17
17
18 In addition, several features are not yet supported or have known bugs:
18 In addition, several features are not yet supported or have known bugs:
19
19
20 * Only some data is stored in SQLite. Changeset, manifest, and other repository
20 * Only some data is stored in SQLite. Changeset, manifest, and other repository
21 data is not yet stored in SQLite.
21 data is not yet stored in SQLite.
22 * Transactions are not robust. If the process is aborted at the right time
22 * Transactions are not robust. If the process is aborted at the right time
23 during transaction close/rollback, the repository could be in an inconsistent
23 during transaction close/rollback, the repository could be in an inconsistent
24 state. This problem will diminish once all repository data is tracked by
24 state. This problem will diminish once all repository data is tracked by
25 SQLite.
25 SQLite.
26 * Bundle repositories do not work (the ability to use e.g.
26 * Bundle repositories do not work (the ability to use e.g.
27 `hg -R <bundle-file> log` to automatically overlay a bundle on top of the
27 `hg -R <bundle-file> log` to automatically overlay a bundle on top of the
28 existing repository).
28 existing repository).
29 * Various other features don't work.
29 * Various other features don't work.
30
30
31 This extension should work for basic clone/pull, update, and commit workflows.
31 This extension should work for basic clone/pull, update, and commit workflows.
32 Some history rewriting operations may fail due to lack of support for bundle
32 Some history rewriting operations may fail due to lack of support for bundle
33 repositories.
33 repositories.
34
34
35 To use, activate the extension and set the ``storage.new-repo-backend`` config
35 To use, activate the extension and set the ``storage.new-repo-backend`` config
36 option to ``sqlite`` to enable new repositories to use SQLite for storage.
36 option to ``sqlite`` to enable new repositories to use SQLite for storage.
37 """
37 """
38
38
39 # To run the test suite with repos using SQLite by default, execute the
39 # To run the test suite with repos using SQLite by default, execute the
40 # following:
40 # following:
41 #
41 #
42 # HGREPOFEATURES="sqlitestore" run-tests.py \
42 # HGREPOFEATURES="sqlitestore" run-tests.py \
43 # --extra-config-opt extensions.sqlitestore= \
43 # --extra-config-opt extensions.sqlitestore= \
44 # --extra-config-opt storage.new-repo-backend=sqlite
44 # --extra-config-opt storage.new-repo-backend=sqlite
45
45
46 from __future__ import absolute_import
46 from __future__ import absolute_import
47
47
48 import sqlite3
48 import sqlite3
49 import struct
49 import struct
50 import threading
50 import threading
51 import zlib
51 import zlib
52
52
53 from mercurial.i18n import _
53 from mercurial.i18n import _
54 from mercurial.node import (
54 from mercurial.node import (
55 nullid,
55 nullid,
56 nullrev,
56 nullrev,
57 short,
57 short,
58 )
58 )
59 from mercurial.thirdparty import attr
59 from mercurial.thirdparty import attr
60 from mercurial import (
60 from mercurial import (
61 ancestor,
61 ancestor,
62 dagop,
62 dagop,
63 encoding,
63 encoding,
64 error,
64 error,
65 extensions,
65 extensions,
66 localrepo,
66 localrepo,
67 mdiff,
67 mdiff,
68 pycompat,
68 pycompat,
69 registrar,
69 registrar,
70 requirements,
70 requirements,
71 util,
71 util,
72 verify,
72 verify,
73 )
73 )
74 from mercurial.interfaces import (
74 from mercurial.interfaces import (
75 repository,
75 repository,
76 util as interfaceutil,
76 util as interfaceutil,
77 )
77 )
78 from mercurial.utils import (
78 from mercurial.utils import (
79 hashutil,
79 hashutil,
80 storageutil,
80 storageutil,
81 )
81 )
82
82
83 try:
83 try:
84 from mercurial import zstd
84 from mercurial import zstd
85
85
86 zstd.__version__
86 zstd.__version__
87 except ImportError:
87 except ImportError:
88 zstd = None
88 zstd = None
89
89
90 configtable = {}
90 configtable = {}
91 configitem = registrar.configitem(configtable)
91 configitem = registrar.configitem(configtable)
92
92
93 # experimental config: storage.sqlite.compression
93 # experimental config: storage.sqlite.compression
94 configitem(
94 configitem(
95 b'storage',
95 b'storage',
96 b'sqlite.compression',
96 b'sqlite.compression',
97 default=b'zstd' if zstd else b'zlib',
97 default=b'zstd' if zstd else b'zlib',
98 experimental=True,
98 experimental=True,
99 )
99 )
100
100
101 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
101 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
102 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
102 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
103 # be specifying the version(s) of Mercurial they are tested with, or
103 # be specifying the version(s) of Mercurial they are tested with, or
104 # leave the attribute unspecified.
104 # leave the attribute unspecified.
105 testedwith = b'ships-with-hg-core'
105 testedwith = b'ships-with-hg-core'
106
106
107 REQUIREMENT = b'exp-sqlite-001'
107 REQUIREMENT = b'exp-sqlite-001'
108 REQUIREMENT_ZSTD = b'exp-sqlite-comp-001=zstd'
108 REQUIREMENT_ZSTD = b'exp-sqlite-comp-001=zstd'
109 REQUIREMENT_ZLIB = b'exp-sqlite-comp-001=zlib'
109 REQUIREMENT_ZLIB = b'exp-sqlite-comp-001=zlib'
110 REQUIREMENT_NONE = b'exp-sqlite-comp-001=none'
110 REQUIREMENT_NONE = b'exp-sqlite-comp-001=none'
111 REQUIREMENT_SHALLOW_FILES = b'exp-sqlite-shallow-files'
111 REQUIREMENT_SHALLOW_FILES = b'exp-sqlite-shallow-files'
112
112
113 CURRENT_SCHEMA_VERSION = 1
113 CURRENT_SCHEMA_VERSION = 1
114
114
115 COMPRESSION_NONE = 1
115 COMPRESSION_NONE = 1
116 COMPRESSION_ZSTD = 2
116 COMPRESSION_ZSTD = 2
117 COMPRESSION_ZLIB = 3
117 COMPRESSION_ZLIB = 3
118
118
119 FLAG_CENSORED = 1
119 FLAG_CENSORED = 1
120 FLAG_MISSING_P1 = 2
120 FLAG_MISSING_P1 = 2
121 FLAG_MISSING_P2 = 4
121 FLAG_MISSING_P2 = 4
122
122
123 CREATE_SCHEMA = [
123 CREATE_SCHEMA = [
124 # Deltas are stored as content-indexed blobs.
124 # Deltas are stored as content-indexed blobs.
125 # compression column holds COMPRESSION_* constant for how the
125 # compression column holds COMPRESSION_* constant for how the
126 # delta is encoded.
126 # delta is encoded.
127 'CREATE TABLE delta ('
127 'CREATE TABLE delta ('
128 ' id INTEGER PRIMARY KEY, '
128 ' id INTEGER PRIMARY KEY, '
129 ' compression INTEGER NOT NULL, '
129 ' compression INTEGER NOT NULL, '
130 ' hash BLOB UNIQUE ON CONFLICT ABORT, '
130 ' hash BLOB UNIQUE ON CONFLICT ABORT, '
131 ' delta BLOB NOT NULL '
131 ' delta BLOB NOT NULL '
132 ')',
132 ')',
133 # Tracked paths are denormalized to integers to avoid redundant
133 # Tracked paths are denormalized to integers to avoid redundant
134 # storage of the path name.
134 # storage of the path name.
135 'CREATE TABLE filepath ('
135 'CREATE TABLE filepath ('
136 ' id INTEGER PRIMARY KEY, '
136 ' id INTEGER PRIMARY KEY, '
137 ' path BLOB NOT NULL '
137 ' path BLOB NOT NULL '
138 ')',
138 ')',
139 'CREATE UNIQUE INDEX filepath_path ON filepath (path)',
139 'CREATE UNIQUE INDEX filepath_path ON filepath (path)',
140 # We have a single table for all file revision data.
140 # We have a single table for all file revision data.
141 # Each file revision is uniquely described by a (path, rev) and
141 # Each file revision is uniquely described by a (path, rev) and
142 # (path, node).
142 # (path, node).
143 #
143 #
144 # Revision data is stored as a pointer to the delta producing this
144 # Revision data is stored as a pointer to the delta producing this
145 # revision and the file revision whose delta should be applied before
145 # revision and the file revision whose delta should be applied before
146 # that one. One can reconstruct the delta chain by recursively following
146 # that one. One can reconstruct the delta chain by recursively following
147 # the delta base revision pointers until one encounters NULL.
147 # the delta base revision pointers until one encounters NULL.
148 #
148 #
149 # flags column holds bitwise integer flags controlling storage options.
149 # flags column holds bitwise integer flags controlling storage options.
150 # These flags are defined by the FLAG_* constants.
150 # These flags are defined by the FLAG_* constants.
151 'CREATE TABLE fileindex ('
151 'CREATE TABLE fileindex ('
152 ' id INTEGER PRIMARY KEY, '
152 ' id INTEGER PRIMARY KEY, '
153 ' pathid INTEGER REFERENCES filepath(id), '
153 ' pathid INTEGER REFERENCES filepath(id), '
154 ' revnum INTEGER NOT NULL, '
154 ' revnum INTEGER NOT NULL, '
155 ' p1rev INTEGER NOT NULL, '
155 ' p1rev INTEGER NOT NULL, '
156 ' p2rev INTEGER NOT NULL, '
156 ' p2rev INTEGER NOT NULL, '
157 ' linkrev INTEGER NOT NULL, '
157 ' linkrev INTEGER NOT NULL, '
158 ' flags INTEGER NOT NULL, '
158 ' flags INTEGER NOT NULL, '
159 ' deltaid INTEGER REFERENCES delta(id), '
159 ' deltaid INTEGER REFERENCES delta(id), '
160 ' deltabaseid INTEGER REFERENCES fileindex(id), '
160 ' deltabaseid INTEGER REFERENCES fileindex(id), '
161 ' node BLOB NOT NULL '
161 ' node BLOB NOT NULL '
162 ')',
162 ')',
163 'CREATE UNIQUE INDEX fileindex_pathrevnum '
163 'CREATE UNIQUE INDEX fileindex_pathrevnum '
164 ' ON fileindex (pathid, revnum)',
164 ' ON fileindex (pathid, revnum)',
165 'CREATE UNIQUE INDEX fileindex_pathnode ON fileindex (pathid, node)',
165 'CREATE UNIQUE INDEX fileindex_pathnode ON fileindex (pathid, node)',
166 # Provide a view over all file data for convenience.
166 # Provide a view over all file data for convenience.
167 'CREATE VIEW filedata AS '
167 'CREATE VIEW filedata AS '
168 'SELECT '
168 'SELECT '
169 ' fileindex.id AS id, '
169 ' fileindex.id AS id, '
170 ' filepath.id AS pathid, '
170 ' filepath.id AS pathid, '
171 ' filepath.path AS path, '
171 ' filepath.path AS path, '
172 ' fileindex.revnum AS revnum, '
172 ' fileindex.revnum AS revnum, '
173 ' fileindex.node AS node, '
173 ' fileindex.node AS node, '
174 ' fileindex.p1rev AS p1rev, '
174 ' fileindex.p1rev AS p1rev, '
175 ' fileindex.p2rev AS p2rev, '
175 ' fileindex.p2rev AS p2rev, '
176 ' fileindex.linkrev AS linkrev, '
176 ' fileindex.linkrev AS linkrev, '
177 ' fileindex.flags AS flags, '
177 ' fileindex.flags AS flags, '
178 ' fileindex.deltaid AS deltaid, '
178 ' fileindex.deltaid AS deltaid, '
179 ' fileindex.deltabaseid AS deltabaseid '
179 ' fileindex.deltabaseid AS deltabaseid '
180 'FROM filepath, fileindex '
180 'FROM filepath, fileindex '
181 'WHERE fileindex.pathid=filepath.id',
181 'WHERE fileindex.pathid=filepath.id',
182 'PRAGMA user_version=%d' % CURRENT_SCHEMA_VERSION,
182 'PRAGMA user_version=%d' % CURRENT_SCHEMA_VERSION,
183 ]
183 ]
184
184
185
185
186 def resolvedeltachain(db, pathid, node, revisioncache, stoprids, zstddctx=None):
186 def resolvedeltachain(db, pathid, node, revisioncache, stoprids, zstddctx=None):
187 """Resolve a delta chain for a file node."""
187 """Resolve a delta chain for a file node."""
188
188
189 # TODO the "not in ({stops})" here is possibly slowing down the query
189 # TODO the "not in ({stops})" here is possibly slowing down the query
190 # because it needs to perform the lookup on every recursive invocation.
190 # because it needs to perform the lookup on every recursive invocation.
191 # This could possibly be faster if we created a temporary query with
191 # This could possibly be faster if we created a temporary query with
192 # baseid "poisoned" to null and limited the recursive filter to
192 # baseid "poisoned" to null and limited the recursive filter to
193 # "is not null".
193 # "is not null".
194 res = db.execute(
194 res = db.execute(
195 'WITH RECURSIVE '
195 'WITH RECURSIVE '
196 ' deltachain(deltaid, baseid) AS ('
196 ' deltachain(deltaid, baseid) AS ('
197 ' SELECT deltaid, deltabaseid FROM fileindex '
197 ' SELECT deltaid, deltabaseid FROM fileindex '
198 ' WHERE pathid=? AND node=? '
198 ' WHERE pathid=? AND node=? '
199 ' UNION ALL '
199 ' UNION ALL '
200 ' SELECT fileindex.deltaid, deltabaseid '
200 ' SELECT fileindex.deltaid, deltabaseid '
201 ' FROM fileindex, deltachain '
201 ' FROM fileindex, deltachain '
202 ' WHERE '
202 ' WHERE '
203 ' fileindex.id=deltachain.baseid '
203 ' fileindex.id=deltachain.baseid '
204 ' AND deltachain.baseid IS NOT NULL '
204 ' AND deltachain.baseid IS NOT NULL '
205 ' AND fileindex.id NOT IN ({stops}) '
205 ' AND fileindex.id NOT IN ({stops}) '
206 ' ) '
206 ' ) '
207 'SELECT deltachain.baseid, compression, delta '
207 'SELECT deltachain.baseid, compression, delta '
208 'FROM deltachain, delta '
208 'FROM deltachain, delta '
209 'WHERE delta.id=deltachain.deltaid'.format(
209 'WHERE delta.id=deltachain.deltaid'.format(
210 stops=','.join(['?'] * len(stoprids))
210 stops=','.join(['?'] * len(stoprids))
211 ),
211 ),
212 tuple([pathid, node] + list(stoprids.keys())),
212 tuple([pathid, node] + list(stoprids.keys())),
213 )
213 )
214
214
215 deltas = []
215 deltas = []
216 lastdeltabaseid = None
216 lastdeltabaseid = None
217
217
218 for deltabaseid, compression, delta in res:
218 for deltabaseid, compression, delta in res:
219 lastdeltabaseid = deltabaseid
219 lastdeltabaseid = deltabaseid
220
220
221 if compression == COMPRESSION_ZSTD:
221 if compression == COMPRESSION_ZSTD:
222 delta = zstddctx.decompress(delta)
222 delta = zstddctx.decompress(delta)
223 elif compression == COMPRESSION_NONE:
223 elif compression == COMPRESSION_NONE:
224 delta = delta
224 delta = delta
225 elif compression == COMPRESSION_ZLIB:
225 elif compression == COMPRESSION_ZLIB:
226 delta = zlib.decompress(delta)
226 delta = zlib.decompress(delta)
227 else:
227 else:
228 raise SQLiteStoreError(
228 raise SQLiteStoreError(
229 b'unhandled compression type: %d' % compression
229 b'unhandled compression type: %d' % compression
230 )
230 )
231
231
232 deltas.append(delta)
232 deltas.append(delta)
233
233
234 if lastdeltabaseid in stoprids:
234 if lastdeltabaseid in stoprids:
235 basetext = revisioncache[stoprids[lastdeltabaseid]]
235 basetext = revisioncache[stoprids[lastdeltabaseid]]
236 else:
236 else:
237 basetext = deltas.pop()
237 basetext = deltas.pop()
238
238
239 deltas.reverse()
239 deltas.reverse()
240 fulltext = mdiff.patches(basetext, deltas)
240 fulltext = mdiff.patches(basetext, deltas)
241
241
242 # SQLite returns buffer instances for blob columns on Python 2. This
242 # SQLite returns buffer instances for blob columns on Python 2. This
243 # type can propagate through the delta application layer. Because
243 # type can propagate through the delta application layer. Because
244 # downstream callers assume revisions are bytes, cast as needed.
244 # downstream callers assume revisions are bytes, cast as needed.
245 if not isinstance(fulltext, bytes):
245 if not isinstance(fulltext, bytes):
246 fulltext = bytes(delta)
246 fulltext = bytes(delta)
247
247
248 return fulltext
248 return fulltext
249
249
250
250
251 def insertdelta(db, compression, hash, delta):
251 def insertdelta(db, compression, hash, delta):
252 try:
252 try:
253 return db.execute(
253 return db.execute(
254 'INSERT INTO delta (compression, hash, delta) VALUES (?, ?, ?)',
254 'INSERT INTO delta (compression, hash, delta) VALUES (?, ?, ?)',
255 (compression, hash, delta),
255 (compression, hash, delta),
256 ).lastrowid
256 ).lastrowid
257 except sqlite3.IntegrityError:
257 except sqlite3.IntegrityError:
258 return db.execute(
258 return db.execute(
259 'SELECT id FROM delta WHERE hash=?', (hash,)
259 'SELECT id FROM delta WHERE hash=?', (hash,)
260 ).fetchone()[0]
260 ).fetchone()[0]
261
261
262
262
263 class SQLiteStoreError(error.StorageError):
263 class SQLiteStoreError(error.StorageError):
264 pass
264 pass
265
265
266
266
267 @attr.s
267 @attr.s
268 class revisionentry(object):
268 class revisionentry(object):
269 rid = attr.ib()
269 rid = attr.ib()
270 rev = attr.ib()
270 rev = attr.ib()
271 node = attr.ib()
271 node = attr.ib()
272 p1rev = attr.ib()
272 p1rev = attr.ib()
273 p2rev = attr.ib()
273 p2rev = attr.ib()
274 p1node = attr.ib()
274 p1node = attr.ib()
275 p2node = attr.ib()
275 p2node = attr.ib()
276 linkrev = attr.ib()
276 linkrev = attr.ib()
277 flags = attr.ib()
277 flags = attr.ib()
278
278
279
279
280 @interfaceutil.implementer(repository.irevisiondelta)
280 @interfaceutil.implementer(repository.irevisiondelta)
281 @attr.s(slots=True)
281 @attr.s(slots=True)
282 class sqliterevisiondelta(object):
282 class sqliterevisiondelta(object):
283 node = attr.ib()
283 node = attr.ib()
284 p1node = attr.ib()
284 p1node = attr.ib()
285 p2node = attr.ib()
285 p2node = attr.ib()
286 basenode = attr.ib()
286 basenode = attr.ib()
287 flags = attr.ib()
287 flags = attr.ib()
288 baserevisionsize = attr.ib()
288 baserevisionsize = attr.ib()
289 revision = attr.ib()
289 revision = attr.ib()
290 delta = attr.ib()
290 delta = attr.ib()
291 linknode = attr.ib(default=None)
291 linknode = attr.ib(default=None)
292
292
293
293
294 @interfaceutil.implementer(repository.iverifyproblem)
294 @interfaceutil.implementer(repository.iverifyproblem)
295 @attr.s(frozen=True)
295 @attr.s(frozen=True)
296 class sqliteproblem(object):
296 class sqliteproblem(object):
297 warning = attr.ib(default=None)
297 warning = attr.ib(default=None)
298 error = attr.ib(default=None)
298 error = attr.ib(default=None)
299 node = attr.ib(default=None)
299 node = attr.ib(default=None)
300
300
301
301
302 @interfaceutil.implementer(repository.ifilestorage)
302 @interfaceutil.implementer(repository.ifilestorage)
303 class sqlitefilestore(object):
303 class sqlitefilestore(object):
304 """Implements storage for an individual tracked path."""
304 """Implements storage for an individual tracked path."""
305
305
306 def __init__(self, db, path, compression):
306 def __init__(self, db, path, compression):
307 self._db = db
307 self._db = db
308 self._path = path
308 self._path = path
309
309
310 self._pathid = None
310 self._pathid = None
311
311
312 # revnum -> node
312 # revnum -> node
313 self._revtonode = {}
313 self._revtonode = {}
314 # node -> revnum
314 # node -> revnum
315 self._nodetorev = {}
315 self._nodetorev = {}
316 # node -> data structure
316 # node -> data structure
317 self._revisions = {}
317 self._revisions = {}
318
318
319 self._revisioncache = util.lrucachedict(10)
319 self._revisioncache = util.lrucachedict(10)
320
320
321 self._compengine = compression
321 self._compengine = compression
322
322
323 if compression == b'zstd':
323 if compression == b'zstd':
324 self._cctx = zstd.ZstdCompressor(level=3)
324 self._cctx = zstd.ZstdCompressor(level=3)
325 self._dctx = zstd.ZstdDecompressor()
325 self._dctx = zstd.ZstdDecompressor()
326 else:
326 else:
327 self._cctx = None
327 self._cctx = None
328 self._dctx = None
328 self._dctx = None
329
329
330 self._refreshindex()
330 self._refreshindex()
331
331
332 def _refreshindex(self):
332 def _refreshindex(self):
333 self._revtonode = {}
333 self._revtonode = {}
334 self._nodetorev = {}
334 self._nodetorev = {}
335 self._revisions = {}
335 self._revisions = {}
336
336
337 res = list(
337 res = list(
338 self._db.execute(
338 self._db.execute(
339 'SELECT id FROM filepath WHERE path=?', (self._path,)
339 'SELECT id FROM filepath WHERE path=?', (self._path,)
340 )
340 )
341 )
341 )
342
342
343 if not res:
343 if not res:
344 self._pathid = None
344 self._pathid = None
345 return
345 return
346
346
347 self._pathid = res[0][0]
347 self._pathid = res[0][0]
348
348
349 res = self._db.execute(
349 res = self._db.execute(
350 'SELECT id, revnum, node, p1rev, p2rev, linkrev, flags '
350 'SELECT id, revnum, node, p1rev, p2rev, linkrev, flags '
351 'FROM fileindex '
351 'FROM fileindex '
352 'WHERE pathid=? '
352 'WHERE pathid=? '
353 'ORDER BY revnum ASC',
353 'ORDER BY revnum ASC',
354 (self._pathid,),
354 (self._pathid,),
355 )
355 )
356
356
357 for i, row in enumerate(res):
357 for i, row in enumerate(res):
358 rid, rev, node, p1rev, p2rev, linkrev, flags = row
358 rid, rev, node, p1rev, p2rev, linkrev, flags = row
359
359
360 if i != rev:
360 if i != rev:
361 raise SQLiteStoreError(
361 raise SQLiteStoreError(
362 _(b'sqlite database has inconsistent revision numbers')
362 _(b'sqlite database has inconsistent revision numbers')
363 )
363 )
364
364
365 if p1rev == nullrev:
365 if p1rev == nullrev:
366 p1node = nullid
366 p1node = nullid
367 else:
367 else:
368 p1node = self._revtonode[p1rev]
368 p1node = self._revtonode[p1rev]
369
369
370 if p2rev == nullrev:
370 if p2rev == nullrev:
371 p2node = nullid
371 p2node = nullid
372 else:
372 else:
373 p2node = self._revtonode[p2rev]
373 p2node = self._revtonode[p2rev]
374
374
375 entry = revisionentry(
375 entry = revisionentry(
376 rid=rid,
376 rid=rid,
377 rev=rev,
377 rev=rev,
378 node=node,
378 node=node,
379 p1rev=p1rev,
379 p1rev=p1rev,
380 p2rev=p2rev,
380 p2rev=p2rev,
381 p1node=p1node,
381 p1node=p1node,
382 p2node=p2node,
382 p2node=p2node,
383 linkrev=linkrev,
383 linkrev=linkrev,
384 flags=flags,
384 flags=flags,
385 )
385 )
386
386
387 self._revtonode[rev] = node
387 self._revtonode[rev] = node
388 self._nodetorev[node] = rev
388 self._nodetorev[node] = rev
389 self._revisions[node] = entry
389 self._revisions[node] = entry
390
390
391 # Start of ifileindex interface.
391 # Start of ifileindex interface.
392
392
393 def __len__(self):
393 def __len__(self):
394 return len(self._revisions)
394 return len(self._revisions)
395
395
396 def __iter__(self):
396 def __iter__(self):
397 return iter(pycompat.xrange(len(self._revisions)))
397 return iter(pycompat.xrange(len(self._revisions)))
398
398
399 def hasnode(self, node):
399 def hasnode(self, node):
400 if node == nullid:
400 if node == nullid:
401 return False
401 return False
402
402
403 return node in self._nodetorev
403 return node in self._nodetorev
404
404
405 def revs(self, start=0, stop=None):
405 def revs(self, start=0, stop=None):
406 return storageutil.iterrevs(
406 return storageutil.iterrevs(
407 len(self._revisions), start=start, stop=stop
407 len(self._revisions), start=start, stop=stop
408 )
408 )
409
409
410 def parents(self, node):
410 def parents(self, node):
411 if node == nullid:
411 if node == nullid:
412 return nullid, nullid
412 return nullid, nullid
413
413
414 if node not in self._revisions:
414 if node not in self._revisions:
415 raise error.LookupError(node, self._path, _(b'no node'))
415 raise error.LookupError(node, self._path, _(b'no node'))
416
416
417 entry = self._revisions[node]
417 entry = self._revisions[node]
418 return entry.p1node, entry.p2node
418 return entry.p1node, entry.p2node
419
419
420 def parentrevs(self, rev):
420 def parentrevs(self, rev):
421 if rev == nullrev:
421 if rev == nullrev:
422 return nullrev, nullrev
422 return nullrev, nullrev
423
423
424 if rev not in self._revtonode:
424 if rev not in self._revtonode:
425 raise IndexError(rev)
425 raise IndexError(rev)
426
426
427 entry = self._revisions[self._revtonode[rev]]
427 entry = self._revisions[self._revtonode[rev]]
428 return entry.p1rev, entry.p2rev
428 return entry.p1rev, entry.p2rev
429
429
430 def rev(self, node):
430 def rev(self, node):
431 if node == nullid:
431 if node == nullid:
432 return nullrev
432 return nullrev
433
433
434 if node not in self._nodetorev:
434 if node not in self._nodetorev:
435 raise error.LookupError(node, self._path, _(b'no node'))
435 raise error.LookupError(node, self._path, _(b'no node'))
436
436
437 return self._nodetorev[node]
437 return self._nodetorev[node]
438
438
439 def node(self, rev):
439 def node(self, rev):
440 if rev == nullrev:
440 if rev == nullrev:
441 return nullid
441 return nullid
442
442
443 if rev not in self._revtonode:
443 if rev not in self._revtonode:
444 raise IndexError(rev)
444 raise IndexError(rev)
445
445
446 return self._revtonode[rev]
446 return self._revtonode[rev]
447
447
448 def lookup(self, node):
448 def lookup(self, node):
449 return storageutil.fileidlookup(self, node, self._path)
449 return storageutil.fileidlookup(self, node, self._path)
450
450
451 def linkrev(self, rev):
451 def linkrev(self, rev):
452 if rev == nullrev:
452 if rev == nullrev:
453 return nullrev
453 return nullrev
454
454
455 if rev not in self._revtonode:
455 if rev not in self._revtonode:
456 raise IndexError(rev)
456 raise IndexError(rev)
457
457
458 entry = self._revisions[self._revtonode[rev]]
458 entry = self._revisions[self._revtonode[rev]]
459 return entry.linkrev
459 return entry.linkrev
460
460
461 def iscensored(self, rev):
461 def iscensored(self, rev):
462 if rev == nullrev:
462 if rev == nullrev:
463 return False
463 return False
464
464
465 if rev not in self._revtonode:
465 if rev not in self._revtonode:
466 raise IndexError(rev)
466 raise IndexError(rev)
467
467
468 return self._revisions[self._revtonode[rev]].flags & FLAG_CENSORED
468 return self._revisions[self._revtonode[rev]].flags & FLAG_CENSORED
469
469
470 def commonancestorsheads(self, node1, node2):
470 def commonancestorsheads(self, node1, node2):
471 rev1 = self.rev(node1)
471 rev1 = self.rev(node1)
472 rev2 = self.rev(node2)
472 rev2 = self.rev(node2)
473
473
474 ancestors = ancestor.commonancestorsheads(self.parentrevs, rev1, rev2)
474 ancestors = ancestor.commonancestorsheads(self.parentrevs, rev1, rev2)
475 return pycompat.maplist(self.node, ancestors)
475 return pycompat.maplist(self.node, ancestors)
476
476
477 def descendants(self, revs):
477 def descendants(self, revs):
478 # TODO we could implement this using a recursive SQL query, which
478 # TODO we could implement this using a recursive SQL query, which
479 # might be faster.
479 # might be faster.
480 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
480 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
481
481
482 def heads(self, start=None, stop=None):
482 def heads(self, start=None, stop=None):
483 if start is None and stop is None:
483 if start is None and stop is None:
484 if not len(self):
484 if not len(self):
485 return [nullid]
485 return [nullid]
486
486
487 startrev = self.rev(start) if start is not None else nullrev
487 startrev = self.rev(start) if start is not None else nullrev
488 stoprevs = {self.rev(n) for n in stop or []}
488 stoprevs = {self.rev(n) for n in stop or []}
489
489
490 revs = dagop.headrevssubset(
490 revs = dagop.headrevssubset(
491 self.revs, self.parentrevs, startrev=startrev, stoprevs=stoprevs
491 self.revs, self.parentrevs, startrev=startrev, stoprevs=stoprevs
492 )
492 )
493
493
494 return [self.node(rev) for rev in revs]
494 return [self.node(rev) for rev in revs]
495
495
496 def children(self, node):
496 def children(self, node):
497 rev = self.rev(node)
497 rev = self.rev(node)
498
498
499 res = self._db.execute(
499 res = self._db.execute(
500 'SELECT'
500 'SELECT'
501 ' node '
501 ' node '
502 ' FROM filedata '
502 ' FROM filedata '
503 ' WHERE path=? AND (p1rev=? OR p2rev=?) '
503 ' WHERE path=? AND (p1rev=? OR p2rev=?) '
504 ' ORDER BY revnum ASC',
504 ' ORDER BY revnum ASC',
505 (self._path, rev, rev),
505 (self._path, rev, rev),
506 )
506 )
507
507
508 return [row[0] for row in res]
508 return [row[0] for row in res]
509
509
510 # End of ifileindex interface.
510 # End of ifileindex interface.
511
511
512 # Start of ifiledata interface.
512 # Start of ifiledata interface.
513
513
514 def size(self, rev):
514 def size(self, rev):
515 if rev == nullrev:
515 if rev == nullrev:
516 return 0
516 return 0
517
517
518 if rev not in self._revtonode:
518 if rev not in self._revtonode:
519 raise IndexError(rev)
519 raise IndexError(rev)
520
520
521 node = self._revtonode[rev]
521 node = self._revtonode[rev]
522
522
523 if self.renamed(node):
523 if self.renamed(node):
524 return len(self.read(node))
524 return len(self.read(node))
525
525
526 return len(self.revision(node))
526 return len(self.revision(node))
527
527
528 def revision(self, node, raw=False, _verifyhash=True):
528 def revision(self, node, raw=False, _verifyhash=True):
529 if node in (nullid, nullrev):
529 if node in (nullid, nullrev):
530 return b''
530 return b''
531
531
532 if isinstance(node, int):
532 if isinstance(node, int):
533 node = self.node(node)
533 node = self.node(node)
534
534
535 if node not in self._nodetorev:
535 if node not in self._nodetorev:
536 raise error.LookupError(node, self._path, _(b'no node'))
536 raise error.LookupError(node, self._path, _(b'no node'))
537
537
538 if node in self._revisioncache:
538 if node in self._revisioncache:
539 return self._revisioncache[node]
539 return self._revisioncache[node]
540
540
541 # Because we have a fulltext revision cache, we are able to
541 # Because we have a fulltext revision cache, we are able to
542 # short-circuit delta chain traversal and decompression as soon as
542 # short-circuit delta chain traversal and decompression as soon as
543 # we encounter a revision in the cache.
543 # we encounter a revision in the cache.
544
544
545 stoprids = {self._revisions[n].rid: n for n in self._revisioncache}
545 stoprids = {self._revisions[n].rid: n for n in self._revisioncache}
546
546
547 if not stoprids:
547 if not stoprids:
548 stoprids[-1] = None
548 stoprids[-1] = None
549
549
550 fulltext = resolvedeltachain(
550 fulltext = resolvedeltachain(
551 self._db,
551 self._db,
552 self._pathid,
552 self._pathid,
553 node,
553 node,
554 self._revisioncache,
554 self._revisioncache,
555 stoprids,
555 stoprids,
556 zstddctx=self._dctx,
556 zstddctx=self._dctx,
557 )
557 )
558
558
559 # Don't verify hashes if parent nodes were rewritten, as the hash
559 # Don't verify hashes if parent nodes were rewritten, as the hash
560 # wouldn't verify.
560 # wouldn't verify.
561 if self._revisions[node].flags & (FLAG_MISSING_P1 | FLAG_MISSING_P2):
561 if self._revisions[node].flags & (FLAG_MISSING_P1 | FLAG_MISSING_P2):
562 _verifyhash = False
562 _verifyhash = False
563
563
564 if _verifyhash:
564 if _verifyhash:
565 self._checkhash(fulltext, node)
565 self._checkhash(fulltext, node)
566 self._revisioncache[node] = fulltext
566 self._revisioncache[node] = fulltext
567
567
568 return fulltext
568 return fulltext
569
569
570 def rawdata(self, *args, **kwargs):
570 def rawdata(self, *args, **kwargs):
571 return self.revision(*args, **kwargs)
571 return self.revision(*args, **kwargs)
572
572
573 def read(self, node):
573 def read(self, node):
574 return storageutil.filtermetadata(self.revision(node))
574 return storageutil.filtermetadata(self.revision(node))
575
575
576 def renamed(self, node):
576 def renamed(self, node):
577 return storageutil.filerevisioncopied(self, node)
577 return storageutil.filerevisioncopied(self, node)
578
578
579 def cmp(self, node, fulltext):
579 def cmp(self, node, fulltext):
580 return not storageutil.filedataequivalent(self, node, fulltext)
580 return not storageutil.filedataequivalent(self, node, fulltext)
581
581
582 def emitrevisions(
582 def emitrevisions(
583 self,
583 self,
584 nodes,
584 nodes,
585 nodesorder=None,
585 nodesorder=None,
586 revisiondata=False,
586 revisiondata=False,
587 assumehaveparentrevisions=False,
587 assumehaveparentrevisions=False,
588 deltamode=repository.CG_DELTAMODE_STD,
588 deltamode=repository.CG_DELTAMODE_STD,
589 ):
589 ):
590 if nodesorder not in (b'nodes', b'storage', b'linear', None):
590 if nodesorder not in (b'nodes', b'storage', b'linear', None):
591 raise error.ProgrammingError(
591 raise error.ProgrammingError(
592 b'unhandled value for nodesorder: %s' % nodesorder
592 b'unhandled value for nodesorder: %s' % nodesorder
593 )
593 )
594
594
595 nodes = [n for n in nodes if n != nullid]
595 nodes = [n for n in nodes if n != nullid]
596
596
597 if not nodes:
597 if not nodes:
598 return
598 return
599
599
600 # TODO perform in a single query.
600 # TODO perform in a single query.
601 res = self._db.execute(
601 res = self._db.execute(
602 'SELECT revnum, deltaid FROM fileindex '
602 'SELECT revnum, deltaid FROM fileindex '
603 'WHERE pathid=? '
603 'WHERE pathid=? '
604 ' AND node in (%s)' % (','.join(['?'] * len(nodes))),
604 ' AND node in (%s)' % (','.join(['?'] * len(nodes))),
605 tuple([self._pathid] + nodes),
605 tuple([self._pathid] + nodes),
606 )
606 )
607
607
608 deltabases = {}
608 deltabases = {}
609
609
610 for rev, deltaid in res:
610 for rev, deltaid in res:
611 res = self._db.execute(
611 res = self._db.execute(
612 'SELECT revnum from fileindex WHERE pathid=? AND deltaid=?',
612 'SELECT revnum from fileindex WHERE pathid=? AND deltaid=?',
613 (self._pathid, deltaid),
613 (self._pathid, deltaid),
614 )
614 )
615 deltabases[rev] = res.fetchone()[0]
615 deltabases[rev] = res.fetchone()[0]
616
616
617 # TODO define revdifffn so we can use delta from storage.
617 # TODO define revdifffn so we can use delta from storage.
618 for delta in storageutil.emitrevisions(
618 for delta in storageutil.emitrevisions(
619 self,
619 self,
620 nodes,
620 nodes,
621 nodesorder,
621 nodesorder,
622 sqliterevisiondelta,
622 sqliterevisiondelta,
623 deltaparentfn=deltabases.__getitem__,
623 deltaparentfn=deltabases.__getitem__,
624 revisiondata=revisiondata,
624 revisiondata=revisiondata,
625 assumehaveparentrevisions=assumehaveparentrevisions,
625 assumehaveparentrevisions=assumehaveparentrevisions,
626 deltamode=deltamode,
626 deltamode=deltamode,
627 ):
627 ):
628
628
629 yield delta
629 yield delta
630
630
631 # End of ifiledata interface.
631 # End of ifiledata interface.
632
632
633 # Start of ifilemutation interface.
633 # Start of ifilemutation interface.
634
634
635 def add(self, filedata, meta, transaction, linkrev, p1, p2):
635 def add(self, filedata, meta, transaction, linkrev, p1, p2):
636 if meta or filedata.startswith(b'\x01\n'):
636 if meta or filedata.startswith(b'\x01\n'):
637 filedata = storageutil.packmeta(meta, filedata)
637 filedata = storageutil.packmeta(meta, filedata)
638
638
639 rev = self.addrevision(filedata, transaction, linkrev, p1, p2)
639 rev = self.addrevision(filedata, transaction, linkrev, p1, p2)
640 return self.node(rev)
640 return self.node(rev)
641
641
642 def addrevision(
642 def addrevision(
643 self,
643 self,
644 revisiondata,
644 revisiondata,
645 transaction,
645 transaction,
646 linkrev,
646 linkrev,
647 p1,
647 p1,
648 p2,
648 p2,
649 node=None,
649 node=None,
650 flags=0,
650 flags=0,
651 cachedelta=None,
651 cachedelta=None,
652 ):
652 ):
653 if flags:
653 if flags:
654 raise SQLiteStoreError(_(b'flags not supported on revisions'))
654 raise SQLiteStoreError(_(b'flags not supported on revisions'))
655
655
656 validatehash = node is not None
656 validatehash = node is not None
657 node = node or storageutil.hashrevisionsha1(revisiondata, p1, p2)
657 node = node or storageutil.hashrevisionsha1(revisiondata, p1, p2)
658
658
659 if validatehash:
659 if validatehash:
660 self._checkhash(revisiondata, node, p1, p2)
660 self._checkhash(revisiondata, node, p1, p2)
661
661
662 rev = self._nodetorev.get(node)
662 rev = self._nodetorev.get(node)
663 if rev is not None:
663 if rev is not None:
664 return rev
664 return rev
665
665
666 rev = self._addrawrevision(
666 rev = self._addrawrevision(
667 node, revisiondata, transaction, linkrev, p1, p2
667 node, revisiondata, transaction, linkrev, p1, p2
668 )
668 )
669
669
670 self._revisioncache[node] = revisiondata
670 self._revisioncache[node] = revisiondata
671 return rev
671 return rev
672
672
673 def addgroup(
673 def addgroup(
674 self,
674 self,
675 deltas,
675 deltas,
676 linkmapper,
676 linkmapper,
677 transaction,
677 transaction,
678 addrevisioncb=None,
678 addrevisioncb=None,
679 duplicaterevisioncb=None,
679 duplicaterevisioncb=None,
680 maybemissingparents=False,
680 maybemissingparents=False,
681 ):
681 ):
682 empty = True
682 empty = True
683
683
684 for node, p1, p2, linknode, deltabase, delta, wireflags in deltas:
684 for node, p1, p2, linknode, deltabase, delta, wireflags in deltas:
685 storeflags = 0
685 storeflags = 0
686
686
687 if wireflags & repository.REVISION_FLAG_CENSORED:
687 if wireflags & repository.REVISION_FLAG_CENSORED:
688 storeflags |= FLAG_CENSORED
688 storeflags |= FLAG_CENSORED
689
689
690 if wireflags & ~repository.REVISION_FLAG_CENSORED:
690 if wireflags & ~repository.REVISION_FLAG_CENSORED:
691 raise SQLiteStoreError(b'unhandled revision flag')
691 raise SQLiteStoreError(b'unhandled revision flag')
692
692
693 if maybemissingparents:
693 if maybemissingparents:
694 if p1 != nullid and not self.hasnode(p1):
694 if p1 != nullid and not self.hasnode(p1):
695 p1 = nullid
695 p1 = nullid
696 storeflags |= FLAG_MISSING_P1
696 storeflags |= FLAG_MISSING_P1
697
697
698 if p2 != nullid and not self.hasnode(p2):
698 if p2 != nullid and not self.hasnode(p2):
699 p2 = nullid
699 p2 = nullid
700 storeflags |= FLAG_MISSING_P2
700 storeflags |= FLAG_MISSING_P2
701
701
702 baserev = self.rev(deltabase)
702 baserev = self.rev(deltabase)
703
703
704 # If base is censored, delta must be full replacement in a single
704 # If base is censored, delta must be full replacement in a single
705 # patch operation.
705 # patch operation.
706 if baserev != nullrev and self.iscensored(baserev):
706 if baserev != nullrev and self.iscensored(baserev):
707 hlen = struct.calcsize(b'>lll')
707 hlen = struct.calcsize(b'>lll')
708 oldlen = len(self.rawdata(deltabase, _verifyhash=False))
708 oldlen = len(self.rawdata(deltabase, _verifyhash=False))
709 newlen = len(delta) - hlen
709 newlen = len(delta) - hlen
710
710
711 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
711 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
712 raise error.CensoredBaseError(self._path, deltabase)
712 raise error.CensoredBaseError(self._path, deltabase)
713
713
714 if not (storeflags & FLAG_CENSORED) and storageutil.deltaiscensored(
714 if not (storeflags & FLAG_CENSORED) and storageutil.deltaiscensored(
715 delta, baserev, lambda x: len(self.rawdata(x))
715 delta, baserev, lambda x: len(self.rawdata(x))
716 ):
716 ):
717 storeflags |= FLAG_CENSORED
717 storeflags |= FLAG_CENSORED
718
718
719 linkrev = linkmapper(linknode)
719 linkrev = linkmapper(linknode)
720
720
721 if node in self._revisions:
721 if node in self._revisions:
722 # Possibly reset parents to make them proper.
722 # Possibly reset parents to make them proper.
723 entry = self._revisions[node]
723 entry = self._revisions[node]
724
724
725 if entry.flags & FLAG_MISSING_P1 and p1 != nullid:
725 if entry.flags & FLAG_MISSING_P1 and p1 != nullid:
726 entry.p1node = p1
726 entry.p1node = p1
727 entry.p1rev = self._nodetorev[p1]
727 entry.p1rev = self._nodetorev[p1]
728 entry.flags &= ~FLAG_MISSING_P1
728 entry.flags &= ~FLAG_MISSING_P1
729
729
730 self._db.execute(
730 self._db.execute(
731 'UPDATE fileindex SET p1rev=?, flags=? WHERE id=?',
731 'UPDATE fileindex SET p1rev=?, flags=? WHERE id=?',
732 (self._nodetorev[p1], entry.flags, entry.rid),
732 (self._nodetorev[p1], entry.flags, entry.rid),
733 )
733 )
734
734
735 if entry.flags & FLAG_MISSING_P2 and p2 != nullid:
735 if entry.flags & FLAG_MISSING_P2 and p2 != nullid:
736 entry.p2node = p2
736 entry.p2node = p2
737 entry.p2rev = self._nodetorev[p2]
737 entry.p2rev = self._nodetorev[p2]
738 entry.flags &= ~FLAG_MISSING_P2
738 entry.flags &= ~FLAG_MISSING_P2
739
739
740 self._db.execute(
740 self._db.execute(
741 'UPDATE fileindex SET p2rev=?, flags=? WHERE id=?',
741 'UPDATE fileindex SET p2rev=?, flags=? WHERE id=?',
742 (self._nodetorev[p1], entry.flags, entry.rid),
742 (self._nodetorev[p1], entry.flags, entry.rid),
743 )
743 )
744
744
745 if duplicaterevisioncb:
745 if duplicaterevisioncb:
746 duplicaterevisioncb(self, node)
746 duplicaterevisioncb(self, self.rev(node))
747 empty = False
747 empty = False
748 continue
748 continue
749
749
750 if deltabase == nullid:
750 if deltabase == nullid:
751 text = mdiff.patch(b'', delta)
751 text = mdiff.patch(b'', delta)
752 storedelta = None
752 storedelta = None
753 else:
753 else:
754 text = None
754 text = None
755 storedelta = (deltabase, delta)
755 storedelta = (deltabase, delta)
756
756
757 self._addrawrevision(
757 rev = self._addrawrevision(
758 node,
758 node,
759 text,
759 text,
760 transaction,
760 transaction,
761 linkrev,
761 linkrev,
762 p1,
762 p1,
763 p2,
763 p2,
764 storedelta=storedelta,
764 storedelta=storedelta,
765 flags=storeflags,
765 flags=storeflags,
766 )
766 )
767
767
768 if addrevisioncb:
768 if addrevisioncb:
769 addrevisioncb(self, node)
769 addrevisioncb(self, rev)
770 empty = False
770 empty = False
771
771
772 return not empty
772 return not empty
773
773
774 def censorrevision(self, tr, censornode, tombstone=b''):
774 def censorrevision(self, tr, censornode, tombstone=b''):
775 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
775 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
776
776
777 # This restriction is cargo culted from revlogs and makes no sense for
777 # This restriction is cargo culted from revlogs and makes no sense for
778 # SQLite, since columns can be resized at will.
778 # SQLite, since columns can be resized at will.
779 if len(tombstone) > len(self.rawdata(censornode)):
779 if len(tombstone) > len(self.rawdata(censornode)):
780 raise error.Abort(
780 raise error.Abort(
781 _(b'censor tombstone must be no longer than censored data')
781 _(b'censor tombstone must be no longer than censored data')
782 )
782 )
783
783
784 # We need to replace the censored revision's data with the tombstone.
784 # We need to replace the censored revision's data with the tombstone.
785 # But replacing that data will have implications for delta chains that
785 # But replacing that data will have implications for delta chains that
786 # reference it.
786 # reference it.
787 #
787 #
788 # While "better," more complex strategies are possible, we do something
788 # While "better," more complex strategies are possible, we do something
789 # simple: we find delta chain children of the censored revision and we
789 # simple: we find delta chain children of the censored revision and we
790 # replace those incremental deltas with fulltexts of their corresponding
790 # replace those incremental deltas with fulltexts of their corresponding
791 # revision. Then we delete the now-unreferenced delta and original
791 # revision. Then we delete the now-unreferenced delta and original
792 # revision and insert a replacement.
792 # revision and insert a replacement.
793
793
794 # Find the delta to be censored.
794 # Find the delta to be censored.
795 censoreddeltaid = self._db.execute(
795 censoreddeltaid = self._db.execute(
796 'SELECT deltaid FROM fileindex WHERE id=?',
796 'SELECT deltaid FROM fileindex WHERE id=?',
797 (self._revisions[censornode].rid,),
797 (self._revisions[censornode].rid,),
798 ).fetchone()[0]
798 ).fetchone()[0]
799
799
800 # Find all its delta chain children.
800 # Find all its delta chain children.
801 # TODO once we support storing deltas for !files, we'll need to look
801 # TODO once we support storing deltas for !files, we'll need to look
802 # for those delta chains too.
802 # for those delta chains too.
803 rows = list(
803 rows = list(
804 self._db.execute(
804 self._db.execute(
805 'SELECT id, pathid, node FROM fileindex '
805 'SELECT id, pathid, node FROM fileindex '
806 'WHERE deltabaseid=? OR deltaid=?',
806 'WHERE deltabaseid=? OR deltaid=?',
807 (censoreddeltaid, censoreddeltaid),
807 (censoreddeltaid, censoreddeltaid),
808 )
808 )
809 )
809 )
810
810
811 for row in rows:
811 for row in rows:
812 rid, pathid, node = row
812 rid, pathid, node = row
813
813
814 fulltext = resolvedeltachain(
814 fulltext = resolvedeltachain(
815 self._db, pathid, node, {}, {-1: None}, zstddctx=self._dctx
815 self._db, pathid, node, {}, {-1: None}, zstddctx=self._dctx
816 )
816 )
817
817
818 deltahash = hashutil.sha1(fulltext).digest()
818 deltahash = hashutil.sha1(fulltext).digest()
819
819
820 if self._compengine == b'zstd':
820 if self._compengine == b'zstd':
821 deltablob = self._cctx.compress(fulltext)
821 deltablob = self._cctx.compress(fulltext)
822 compression = COMPRESSION_ZSTD
822 compression = COMPRESSION_ZSTD
823 elif self._compengine == b'zlib':
823 elif self._compengine == b'zlib':
824 deltablob = zlib.compress(fulltext)
824 deltablob = zlib.compress(fulltext)
825 compression = COMPRESSION_ZLIB
825 compression = COMPRESSION_ZLIB
826 elif self._compengine == b'none':
826 elif self._compengine == b'none':
827 deltablob = fulltext
827 deltablob = fulltext
828 compression = COMPRESSION_NONE
828 compression = COMPRESSION_NONE
829 else:
829 else:
830 raise error.ProgrammingError(
830 raise error.ProgrammingError(
831 b'unhandled compression engine: %s' % self._compengine
831 b'unhandled compression engine: %s' % self._compengine
832 )
832 )
833
833
834 if len(deltablob) >= len(fulltext):
834 if len(deltablob) >= len(fulltext):
835 deltablob = fulltext
835 deltablob = fulltext
836 compression = COMPRESSION_NONE
836 compression = COMPRESSION_NONE
837
837
838 deltaid = insertdelta(self._db, compression, deltahash, deltablob)
838 deltaid = insertdelta(self._db, compression, deltahash, deltablob)
839
839
840 self._db.execute(
840 self._db.execute(
841 'UPDATE fileindex SET deltaid=?, deltabaseid=NULL '
841 'UPDATE fileindex SET deltaid=?, deltabaseid=NULL '
842 'WHERE id=?',
842 'WHERE id=?',
843 (deltaid, rid),
843 (deltaid, rid),
844 )
844 )
845
845
846 # Now create the tombstone delta and replace the delta on the censored
846 # Now create the tombstone delta and replace the delta on the censored
847 # node.
847 # node.
848 deltahash = hashutil.sha1(tombstone).digest()
848 deltahash = hashutil.sha1(tombstone).digest()
849 tombstonedeltaid = insertdelta(
849 tombstonedeltaid = insertdelta(
850 self._db, COMPRESSION_NONE, deltahash, tombstone
850 self._db, COMPRESSION_NONE, deltahash, tombstone
851 )
851 )
852
852
853 flags = self._revisions[censornode].flags
853 flags = self._revisions[censornode].flags
854 flags |= FLAG_CENSORED
854 flags |= FLAG_CENSORED
855
855
856 self._db.execute(
856 self._db.execute(
857 'UPDATE fileindex SET flags=?, deltaid=?, deltabaseid=NULL '
857 'UPDATE fileindex SET flags=?, deltaid=?, deltabaseid=NULL '
858 'WHERE pathid=? AND node=?',
858 'WHERE pathid=? AND node=?',
859 (flags, tombstonedeltaid, self._pathid, censornode),
859 (flags, tombstonedeltaid, self._pathid, censornode),
860 )
860 )
861
861
862 self._db.execute('DELETE FROM delta WHERE id=?', (censoreddeltaid,))
862 self._db.execute('DELETE FROM delta WHERE id=?', (censoreddeltaid,))
863
863
864 self._refreshindex()
864 self._refreshindex()
865 self._revisioncache.clear()
865 self._revisioncache.clear()
866
866
867 def getstrippoint(self, minlink):
867 def getstrippoint(self, minlink):
868 return storageutil.resolvestripinfo(
868 return storageutil.resolvestripinfo(
869 minlink,
869 minlink,
870 len(self) - 1,
870 len(self) - 1,
871 [self.rev(n) for n in self.heads()],
871 [self.rev(n) for n in self.heads()],
872 self.linkrev,
872 self.linkrev,
873 self.parentrevs,
873 self.parentrevs,
874 )
874 )
875
875
876 def strip(self, minlink, transaction):
876 def strip(self, minlink, transaction):
877 if not len(self):
877 if not len(self):
878 return
878 return
879
879
880 rev, _ignored = self.getstrippoint(minlink)
880 rev, _ignored = self.getstrippoint(minlink)
881
881
882 if rev == len(self):
882 if rev == len(self):
883 return
883 return
884
884
885 for rev in self.revs(rev):
885 for rev in self.revs(rev):
886 self._db.execute(
886 self._db.execute(
887 'DELETE FROM fileindex WHERE pathid=? AND node=?',
887 'DELETE FROM fileindex WHERE pathid=? AND node=?',
888 (self._pathid, self.node(rev)),
888 (self._pathid, self.node(rev)),
889 )
889 )
890
890
891 # TODO how should we garbage collect data in delta table?
891 # TODO how should we garbage collect data in delta table?
892
892
893 self._refreshindex()
893 self._refreshindex()
894
894
895 # End of ifilemutation interface.
895 # End of ifilemutation interface.
896
896
897 # Start of ifilestorage interface.
897 # Start of ifilestorage interface.
898
898
899 def files(self):
899 def files(self):
900 return []
900 return []
901
901
902 def storageinfo(
902 def storageinfo(
903 self,
903 self,
904 exclusivefiles=False,
904 exclusivefiles=False,
905 sharedfiles=False,
905 sharedfiles=False,
906 revisionscount=False,
906 revisionscount=False,
907 trackedsize=False,
907 trackedsize=False,
908 storedsize=False,
908 storedsize=False,
909 ):
909 ):
910 d = {}
910 d = {}
911
911
912 if exclusivefiles:
912 if exclusivefiles:
913 d[b'exclusivefiles'] = []
913 d[b'exclusivefiles'] = []
914
914
915 if sharedfiles:
915 if sharedfiles:
916 # TODO list sqlite file(s) here.
916 # TODO list sqlite file(s) here.
917 d[b'sharedfiles'] = []
917 d[b'sharedfiles'] = []
918
918
919 if revisionscount:
919 if revisionscount:
920 d[b'revisionscount'] = len(self)
920 d[b'revisionscount'] = len(self)
921
921
922 if trackedsize:
922 if trackedsize:
923 d[b'trackedsize'] = sum(
923 d[b'trackedsize'] = sum(
924 len(self.revision(node)) for node in self._nodetorev
924 len(self.revision(node)) for node in self._nodetorev
925 )
925 )
926
926
927 if storedsize:
927 if storedsize:
928 # TODO implement this?
928 # TODO implement this?
929 d[b'storedsize'] = None
929 d[b'storedsize'] = None
930
930
931 return d
931 return d
932
932
933 def verifyintegrity(self, state):
933 def verifyintegrity(self, state):
934 state[b'skipread'] = set()
934 state[b'skipread'] = set()
935
935
936 for rev in self:
936 for rev in self:
937 node = self.node(rev)
937 node = self.node(rev)
938
938
939 try:
939 try:
940 self.revision(node)
940 self.revision(node)
941 except Exception as e:
941 except Exception as e:
942 yield sqliteproblem(
942 yield sqliteproblem(
943 error=_(b'unpacking %s: %s') % (short(node), e), node=node
943 error=_(b'unpacking %s: %s') % (short(node), e), node=node
944 )
944 )
945
945
946 state[b'skipread'].add(node)
946 state[b'skipread'].add(node)
947
947
948 # End of ifilestorage interface.
948 # End of ifilestorage interface.
949
949
950 def _checkhash(self, fulltext, node, p1=None, p2=None):
950 def _checkhash(self, fulltext, node, p1=None, p2=None):
951 if p1 is None and p2 is None:
951 if p1 is None and p2 is None:
952 p1, p2 = self.parents(node)
952 p1, p2 = self.parents(node)
953
953
954 if node == storageutil.hashrevisionsha1(fulltext, p1, p2):
954 if node == storageutil.hashrevisionsha1(fulltext, p1, p2):
955 return
955 return
956
956
957 try:
957 try:
958 del self._revisioncache[node]
958 del self._revisioncache[node]
959 except KeyError:
959 except KeyError:
960 pass
960 pass
961
961
962 if storageutil.iscensoredtext(fulltext):
962 if storageutil.iscensoredtext(fulltext):
963 raise error.CensoredNodeError(self._path, node, fulltext)
963 raise error.CensoredNodeError(self._path, node, fulltext)
964
964
965 raise SQLiteStoreError(_(b'integrity check failed on %s') % self._path)
965 raise SQLiteStoreError(_(b'integrity check failed on %s') % self._path)
966
966
967 def _addrawrevision(
967 def _addrawrevision(
968 self,
968 self,
969 node,
969 node,
970 revisiondata,
970 revisiondata,
971 transaction,
971 transaction,
972 linkrev,
972 linkrev,
973 p1,
973 p1,
974 p2,
974 p2,
975 storedelta=None,
975 storedelta=None,
976 flags=0,
976 flags=0,
977 ):
977 ):
978 if self._pathid is None:
978 if self._pathid is None:
979 res = self._db.execute(
979 res = self._db.execute(
980 'INSERT INTO filepath (path) VALUES (?)', (self._path,)
980 'INSERT INTO filepath (path) VALUES (?)', (self._path,)
981 )
981 )
982 self._pathid = res.lastrowid
982 self._pathid = res.lastrowid
983
983
984 # For simplicity, always store a delta against p1.
984 # For simplicity, always store a delta against p1.
985 # TODO we need a lot more logic here to make behavior reasonable.
985 # TODO we need a lot more logic here to make behavior reasonable.
986
986
987 if storedelta:
987 if storedelta:
988 deltabase, delta = storedelta
988 deltabase, delta = storedelta
989
989
990 if isinstance(deltabase, int):
990 if isinstance(deltabase, int):
991 deltabase = self.node(deltabase)
991 deltabase = self.node(deltabase)
992
992
993 else:
993 else:
994 assert revisiondata is not None
994 assert revisiondata is not None
995 deltabase = p1
995 deltabase = p1
996
996
997 if deltabase == nullid:
997 if deltabase == nullid:
998 delta = revisiondata
998 delta = revisiondata
999 else:
999 else:
1000 delta = mdiff.textdiff(
1000 delta = mdiff.textdiff(
1001 self.revision(self.rev(deltabase)), revisiondata
1001 self.revision(self.rev(deltabase)), revisiondata
1002 )
1002 )
1003
1003
1004 # File index stores a pointer to its delta and the parent delta.
1004 # File index stores a pointer to its delta and the parent delta.
1005 # The parent delta is stored via a pointer to the fileindex PK.
1005 # The parent delta is stored via a pointer to the fileindex PK.
1006 if deltabase == nullid:
1006 if deltabase == nullid:
1007 baseid = None
1007 baseid = None
1008 else:
1008 else:
1009 baseid = self._revisions[deltabase].rid
1009 baseid = self._revisions[deltabase].rid
1010
1010
1011 # Deltas are stored with a hash of their content. This allows
1011 # Deltas are stored with a hash of their content. This allows
1012 # us to de-duplicate. The table is configured to ignore conflicts
1012 # us to de-duplicate. The table is configured to ignore conflicts
1013 # and it is faster to just insert and silently noop than to look
1013 # and it is faster to just insert and silently noop than to look
1014 # first.
1014 # first.
1015 deltahash = hashutil.sha1(delta).digest()
1015 deltahash = hashutil.sha1(delta).digest()
1016
1016
1017 if self._compengine == b'zstd':
1017 if self._compengine == b'zstd':
1018 deltablob = self._cctx.compress(delta)
1018 deltablob = self._cctx.compress(delta)
1019 compression = COMPRESSION_ZSTD
1019 compression = COMPRESSION_ZSTD
1020 elif self._compengine == b'zlib':
1020 elif self._compengine == b'zlib':
1021 deltablob = zlib.compress(delta)
1021 deltablob = zlib.compress(delta)
1022 compression = COMPRESSION_ZLIB
1022 compression = COMPRESSION_ZLIB
1023 elif self._compengine == b'none':
1023 elif self._compengine == b'none':
1024 deltablob = delta
1024 deltablob = delta
1025 compression = COMPRESSION_NONE
1025 compression = COMPRESSION_NONE
1026 else:
1026 else:
1027 raise error.ProgrammingError(
1027 raise error.ProgrammingError(
1028 b'unhandled compression engine: %s' % self._compengine
1028 b'unhandled compression engine: %s' % self._compengine
1029 )
1029 )
1030
1030
1031 # Don't store compressed data if it isn't practical.
1031 # Don't store compressed data if it isn't practical.
1032 if len(deltablob) >= len(delta):
1032 if len(deltablob) >= len(delta):
1033 deltablob = delta
1033 deltablob = delta
1034 compression = COMPRESSION_NONE
1034 compression = COMPRESSION_NONE
1035
1035
1036 deltaid = insertdelta(self._db, compression, deltahash, deltablob)
1036 deltaid = insertdelta(self._db, compression, deltahash, deltablob)
1037
1037
1038 rev = len(self)
1038 rev = len(self)
1039
1039
1040 if p1 == nullid:
1040 if p1 == nullid:
1041 p1rev = nullrev
1041 p1rev = nullrev
1042 else:
1042 else:
1043 p1rev = self._nodetorev[p1]
1043 p1rev = self._nodetorev[p1]
1044
1044
1045 if p2 == nullid:
1045 if p2 == nullid:
1046 p2rev = nullrev
1046 p2rev = nullrev
1047 else:
1047 else:
1048 p2rev = self._nodetorev[p2]
1048 p2rev = self._nodetorev[p2]
1049
1049
1050 rid = self._db.execute(
1050 rid = self._db.execute(
1051 'INSERT INTO fileindex ('
1051 'INSERT INTO fileindex ('
1052 ' pathid, revnum, node, p1rev, p2rev, linkrev, flags, '
1052 ' pathid, revnum, node, p1rev, p2rev, linkrev, flags, '
1053 ' deltaid, deltabaseid) '
1053 ' deltaid, deltabaseid) '
1054 ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
1054 ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
1055 (
1055 (
1056 self._pathid,
1056 self._pathid,
1057 rev,
1057 rev,
1058 node,
1058 node,
1059 p1rev,
1059 p1rev,
1060 p2rev,
1060 p2rev,
1061 linkrev,
1061 linkrev,
1062 flags,
1062 flags,
1063 deltaid,
1063 deltaid,
1064 baseid,
1064 baseid,
1065 ),
1065 ),
1066 ).lastrowid
1066 ).lastrowid
1067
1067
1068 entry = revisionentry(
1068 entry = revisionentry(
1069 rid=rid,
1069 rid=rid,
1070 rev=rev,
1070 rev=rev,
1071 node=node,
1071 node=node,
1072 p1rev=p1rev,
1072 p1rev=p1rev,
1073 p2rev=p2rev,
1073 p2rev=p2rev,
1074 p1node=p1,
1074 p1node=p1,
1075 p2node=p2,
1075 p2node=p2,
1076 linkrev=linkrev,
1076 linkrev=linkrev,
1077 flags=flags,
1077 flags=flags,
1078 )
1078 )
1079
1079
1080 self._nodetorev[node] = rev
1080 self._nodetorev[node] = rev
1081 self._revtonode[rev] = node
1081 self._revtonode[rev] = node
1082 self._revisions[node] = entry
1082 self._revisions[node] = entry
1083
1083
1084 return rev
1084 return rev
1085
1085
1086
1086
1087 class sqliterepository(localrepo.localrepository):
1087 class sqliterepository(localrepo.localrepository):
1088 def cancopy(self):
1088 def cancopy(self):
1089 return False
1089 return False
1090
1090
1091 def transaction(self, *args, **kwargs):
1091 def transaction(self, *args, **kwargs):
1092 current = self.currenttransaction()
1092 current = self.currenttransaction()
1093
1093
1094 tr = super(sqliterepository, self).transaction(*args, **kwargs)
1094 tr = super(sqliterepository, self).transaction(*args, **kwargs)
1095
1095
1096 if current:
1096 if current:
1097 return tr
1097 return tr
1098
1098
1099 self._dbconn.execute('BEGIN TRANSACTION')
1099 self._dbconn.execute('BEGIN TRANSACTION')
1100
1100
1101 def committransaction(_):
1101 def committransaction(_):
1102 self._dbconn.commit()
1102 self._dbconn.commit()
1103
1103
1104 tr.addfinalize(b'sqlitestore', committransaction)
1104 tr.addfinalize(b'sqlitestore', committransaction)
1105
1105
1106 return tr
1106 return tr
1107
1107
1108 @property
1108 @property
1109 def _dbconn(self):
1109 def _dbconn(self):
1110 # SQLite connections can only be used on the thread that created
1110 # SQLite connections can only be used on the thread that created
1111 # them. In most cases, this "just works." However, hgweb uses
1111 # them. In most cases, this "just works." However, hgweb uses
1112 # multiple threads.
1112 # multiple threads.
1113 tid = threading.current_thread().ident
1113 tid = threading.current_thread().ident
1114
1114
1115 if self._db:
1115 if self._db:
1116 if self._db[0] == tid:
1116 if self._db[0] == tid:
1117 return self._db[1]
1117 return self._db[1]
1118
1118
1119 db = makedb(self.svfs.join(b'db.sqlite'))
1119 db = makedb(self.svfs.join(b'db.sqlite'))
1120 self._db = (tid, db)
1120 self._db = (tid, db)
1121
1121
1122 return db
1122 return db
1123
1123
1124
1124
1125 def makedb(path):
1125 def makedb(path):
1126 """Construct a database handle for a database at path."""
1126 """Construct a database handle for a database at path."""
1127
1127
1128 db = sqlite3.connect(encoding.strfromlocal(path))
1128 db = sqlite3.connect(encoding.strfromlocal(path))
1129 db.text_factory = bytes
1129 db.text_factory = bytes
1130
1130
1131 res = db.execute('PRAGMA user_version').fetchone()[0]
1131 res = db.execute('PRAGMA user_version').fetchone()[0]
1132
1132
1133 # New database.
1133 # New database.
1134 if res == 0:
1134 if res == 0:
1135 for statement in CREATE_SCHEMA:
1135 for statement in CREATE_SCHEMA:
1136 db.execute(statement)
1136 db.execute(statement)
1137
1137
1138 db.commit()
1138 db.commit()
1139
1139
1140 elif res == CURRENT_SCHEMA_VERSION:
1140 elif res == CURRENT_SCHEMA_VERSION:
1141 pass
1141 pass
1142
1142
1143 else:
1143 else:
1144 raise error.Abort(_(b'sqlite database has unrecognized version'))
1144 raise error.Abort(_(b'sqlite database has unrecognized version'))
1145
1145
1146 db.execute('PRAGMA journal_mode=WAL')
1146 db.execute('PRAGMA journal_mode=WAL')
1147
1147
1148 return db
1148 return db
1149
1149
1150
1150
1151 def featuresetup(ui, supported):
1151 def featuresetup(ui, supported):
1152 supported.add(REQUIREMENT)
1152 supported.add(REQUIREMENT)
1153
1153
1154 if zstd:
1154 if zstd:
1155 supported.add(REQUIREMENT_ZSTD)
1155 supported.add(REQUIREMENT_ZSTD)
1156
1156
1157 supported.add(REQUIREMENT_ZLIB)
1157 supported.add(REQUIREMENT_ZLIB)
1158 supported.add(REQUIREMENT_NONE)
1158 supported.add(REQUIREMENT_NONE)
1159 supported.add(REQUIREMENT_SHALLOW_FILES)
1159 supported.add(REQUIREMENT_SHALLOW_FILES)
1160 supported.add(requirements.NARROW_REQUIREMENT)
1160 supported.add(requirements.NARROW_REQUIREMENT)
1161
1161
1162
1162
1163 def newreporequirements(orig, ui, createopts):
1163 def newreporequirements(orig, ui, createopts):
1164 if createopts[b'backend'] != b'sqlite':
1164 if createopts[b'backend'] != b'sqlite':
1165 return orig(ui, createopts)
1165 return orig(ui, createopts)
1166
1166
1167 # This restriction can be lifted once we have more confidence.
1167 # This restriction can be lifted once we have more confidence.
1168 if b'sharedrepo' in createopts:
1168 if b'sharedrepo' in createopts:
1169 raise error.Abort(
1169 raise error.Abort(
1170 _(b'shared repositories not supported with SQLite store')
1170 _(b'shared repositories not supported with SQLite store')
1171 )
1171 )
1172
1172
1173 # This filtering is out of an abundance of caution: we want to ensure
1173 # This filtering is out of an abundance of caution: we want to ensure
1174 # we honor creation options and we do that by annotating exactly the
1174 # we honor creation options and we do that by annotating exactly the
1175 # creation options we recognize.
1175 # creation options we recognize.
1176 known = {
1176 known = {
1177 b'narrowfiles',
1177 b'narrowfiles',
1178 b'backend',
1178 b'backend',
1179 b'shallowfilestore',
1179 b'shallowfilestore',
1180 }
1180 }
1181
1181
1182 unsupported = set(createopts) - known
1182 unsupported = set(createopts) - known
1183 if unsupported:
1183 if unsupported:
1184 raise error.Abort(
1184 raise error.Abort(
1185 _(b'SQLite store does not support repo creation option: %s')
1185 _(b'SQLite store does not support repo creation option: %s')
1186 % b', '.join(sorted(unsupported))
1186 % b', '.join(sorted(unsupported))
1187 )
1187 )
1188
1188
1189 # Since we're a hybrid store that still relies on revlogs, we fall back
1189 # Since we're a hybrid store that still relies on revlogs, we fall back
1190 # to using the revlogv1 backend's storage requirements then adding our
1190 # to using the revlogv1 backend's storage requirements then adding our
1191 # own requirement.
1191 # own requirement.
1192 createopts[b'backend'] = b'revlogv1'
1192 createopts[b'backend'] = b'revlogv1'
1193 requirements = orig(ui, createopts)
1193 requirements = orig(ui, createopts)
1194 requirements.add(REQUIREMENT)
1194 requirements.add(REQUIREMENT)
1195
1195
1196 compression = ui.config(b'storage', b'sqlite.compression')
1196 compression = ui.config(b'storage', b'sqlite.compression')
1197
1197
1198 if compression == b'zstd' and not zstd:
1198 if compression == b'zstd' and not zstd:
1199 raise error.Abort(
1199 raise error.Abort(
1200 _(
1200 _(
1201 b'storage.sqlite.compression set to "zstd" but '
1201 b'storage.sqlite.compression set to "zstd" but '
1202 b'zstandard compression not available to this '
1202 b'zstandard compression not available to this '
1203 b'Mercurial install'
1203 b'Mercurial install'
1204 )
1204 )
1205 )
1205 )
1206
1206
1207 if compression == b'zstd':
1207 if compression == b'zstd':
1208 requirements.add(REQUIREMENT_ZSTD)
1208 requirements.add(REQUIREMENT_ZSTD)
1209 elif compression == b'zlib':
1209 elif compression == b'zlib':
1210 requirements.add(REQUIREMENT_ZLIB)
1210 requirements.add(REQUIREMENT_ZLIB)
1211 elif compression == b'none':
1211 elif compression == b'none':
1212 requirements.add(REQUIREMENT_NONE)
1212 requirements.add(REQUIREMENT_NONE)
1213 else:
1213 else:
1214 raise error.Abort(
1214 raise error.Abort(
1215 _(
1215 _(
1216 b'unknown compression engine defined in '
1216 b'unknown compression engine defined in '
1217 b'storage.sqlite.compression: %s'
1217 b'storage.sqlite.compression: %s'
1218 )
1218 )
1219 % compression
1219 % compression
1220 )
1220 )
1221
1221
1222 if createopts.get(b'shallowfilestore'):
1222 if createopts.get(b'shallowfilestore'):
1223 requirements.add(REQUIREMENT_SHALLOW_FILES)
1223 requirements.add(REQUIREMENT_SHALLOW_FILES)
1224
1224
1225 return requirements
1225 return requirements
1226
1226
1227
1227
1228 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1228 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
1229 class sqlitefilestorage(object):
1229 class sqlitefilestorage(object):
1230 """Repository file storage backed by SQLite."""
1230 """Repository file storage backed by SQLite."""
1231
1231
1232 def file(self, path):
1232 def file(self, path):
1233 if path[0] == b'/':
1233 if path[0] == b'/':
1234 path = path[1:]
1234 path = path[1:]
1235
1235
1236 if REQUIREMENT_ZSTD in self.requirements:
1236 if REQUIREMENT_ZSTD in self.requirements:
1237 compression = b'zstd'
1237 compression = b'zstd'
1238 elif REQUIREMENT_ZLIB in self.requirements:
1238 elif REQUIREMENT_ZLIB in self.requirements:
1239 compression = b'zlib'
1239 compression = b'zlib'
1240 elif REQUIREMENT_NONE in self.requirements:
1240 elif REQUIREMENT_NONE in self.requirements:
1241 compression = b'none'
1241 compression = b'none'
1242 else:
1242 else:
1243 raise error.Abort(
1243 raise error.Abort(
1244 _(
1244 _(
1245 b'unable to determine what compression engine '
1245 b'unable to determine what compression engine '
1246 b'to use for SQLite storage'
1246 b'to use for SQLite storage'
1247 )
1247 )
1248 )
1248 )
1249
1249
1250 return sqlitefilestore(self._dbconn, path, compression)
1250 return sqlitefilestore(self._dbconn, path, compression)
1251
1251
1252
1252
1253 def makefilestorage(orig, requirements, features, **kwargs):
1253 def makefilestorage(orig, requirements, features, **kwargs):
1254 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1254 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
1255 if REQUIREMENT in requirements:
1255 if REQUIREMENT in requirements:
1256 if REQUIREMENT_SHALLOW_FILES in requirements:
1256 if REQUIREMENT_SHALLOW_FILES in requirements:
1257 features.add(repository.REPO_FEATURE_SHALLOW_FILE_STORAGE)
1257 features.add(repository.REPO_FEATURE_SHALLOW_FILE_STORAGE)
1258
1258
1259 return sqlitefilestorage
1259 return sqlitefilestorage
1260 else:
1260 else:
1261 return orig(requirements=requirements, features=features, **kwargs)
1261 return orig(requirements=requirements, features=features, **kwargs)
1262
1262
1263
1263
1264 def makemain(orig, ui, requirements, **kwargs):
1264 def makemain(orig, ui, requirements, **kwargs):
1265 if REQUIREMENT in requirements:
1265 if REQUIREMENT in requirements:
1266 if REQUIREMENT_ZSTD in requirements and not zstd:
1266 if REQUIREMENT_ZSTD in requirements and not zstd:
1267 raise error.Abort(
1267 raise error.Abort(
1268 _(
1268 _(
1269 b'repository uses zstandard compression, which '
1269 b'repository uses zstandard compression, which '
1270 b'is not available to this Mercurial install'
1270 b'is not available to this Mercurial install'
1271 )
1271 )
1272 )
1272 )
1273
1273
1274 return sqliterepository
1274 return sqliterepository
1275
1275
1276 return orig(requirements=requirements, **kwargs)
1276 return orig(requirements=requirements, **kwargs)
1277
1277
1278
1278
1279 def verifierinit(orig, self, *args, **kwargs):
1279 def verifierinit(orig, self, *args, **kwargs):
1280 orig(self, *args, **kwargs)
1280 orig(self, *args, **kwargs)
1281
1281
1282 # We don't care that files in the store don't align with what is
1282 # We don't care that files in the store don't align with what is
1283 # advertised. So suppress these warnings.
1283 # advertised. So suppress these warnings.
1284 self.warnorphanstorefiles = False
1284 self.warnorphanstorefiles = False
1285
1285
1286
1286
1287 def extsetup(ui):
1287 def extsetup(ui):
1288 localrepo.featuresetupfuncs.add(featuresetup)
1288 localrepo.featuresetupfuncs.add(featuresetup)
1289 extensions.wrapfunction(
1289 extensions.wrapfunction(
1290 localrepo, b'newreporequirements', newreporequirements
1290 localrepo, b'newreporequirements', newreporequirements
1291 )
1291 )
1292 extensions.wrapfunction(localrepo, b'makefilestorage', makefilestorage)
1292 extensions.wrapfunction(localrepo, b'makefilestorage', makefilestorage)
1293 extensions.wrapfunction(localrepo, b'makemain', makemain)
1293 extensions.wrapfunction(localrepo, b'makemain', makemain)
1294 extensions.wrapfunction(verify.verifier, b'__init__', verifierinit)
1294 extensions.wrapfunction(verify.verifier, b'__init__', verifierinit)
1295
1295
1296
1296
1297 def reposetup(ui, repo):
1297 def reposetup(ui, repo):
1298 if isinstance(repo, sqliterepository):
1298 if isinstance(repo, sqliterepository):
1299 repo._db = None
1299 repo._db = None
1300
1300
1301 # TODO check for bundlerepository?
1301 # TODO check for bundlerepository?
@@ -1,1707 +1,1706 b''
1 # changegroup.py - Mercurial changegroup manipulation functions
1 # changegroup.py - Mercurial changegroup manipulation functions
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11 import struct
11 import struct
12 import weakref
12 import weakref
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 hex,
16 hex,
17 nullid,
17 nullid,
18 nullrev,
18 nullrev,
19 short,
19 short,
20 )
20 )
21 from .pycompat import open
21 from .pycompat import open
22
22
23 from . import (
23 from . import (
24 error,
24 error,
25 match as matchmod,
25 match as matchmod,
26 mdiff,
26 mdiff,
27 phases,
27 phases,
28 pycompat,
28 pycompat,
29 requirements,
29 requirements,
30 scmutil,
30 scmutil,
31 util,
31 util,
32 )
32 )
33
33
34 from .interfaces import repository
34 from .interfaces import repository
35
35
36 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct(b"20s20s20s20s")
36 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct(b"20s20s20s20s")
37 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct(b"20s20s20s20s20s")
37 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct(b"20s20s20s20s20s")
38 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(b">20s20s20s20s20sH")
38 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(b">20s20s20s20s20sH")
39
39
40 LFS_REQUIREMENT = b'lfs'
40 LFS_REQUIREMENT = b'lfs'
41
41
42 readexactly = util.readexactly
42 readexactly = util.readexactly
43
43
44
44
45 def getchunk(stream):
45 def getchunk(stream):
46 """return the next chunk from stream as a string"""
46 """return the next chunk from stream as a string"""
47 d = readexactly(stream, 4)
47 d = readexactly(stream, 4)
48 l = struct.unpack(b">l", d)[0]
48 l = struct.unpack(b">l", d)[0]
49 if l <= 4:
49 if l <= 4:
50 if l:
50 if l:
51 raise error.Abort(_(b"invalid chunk length %d") % l)
51 raise error.Abort(_(b"invalid chunk length %d") % l)
52 return b""
52 return b""
53 return readexactly(stream, l - 4)
53 return readexactly(stream, l - 4)
54
54
55
55
56 def chunkheader(length):
56 def chunkheader(length):
57 """return a changegroup chunk header (string)"""
57 """return a changegroup chunk header (string)"""
58 return struct.pack(b">l", length + 4)
58 return struct.pack(b">l", length + 4)
59
59
60
60
61 def closechunk():
61 def closechunk():
62 """return a changegroup chunk header (string) for a zero-length chunk"""
62 """return a changegroup chunk header (string) for a zero-length chunk"""
63 return struct.pack(b">l", 0)
63 return struct.pack(b">l", 0)
64
64
65
65
66 def _fileheader(path):
66 def _fileheader(path):
67 """Obtain a changegroup chunk header for a named path."""
67 """Obtain a changegroup chunk header for a named path."""
68 return chunkheader(len(path)) + path
68 return chunkheader(len(path)) + path
69
69
70
70
71 def writechunks(ui, chunks, filename, vfs=None):
71 def writechunks(ui, chunks, filename, vfs=None):
72 """Write chunks to a file and return its filename.
72 """Write chunks to a file and return its filename.
73
73
74 The stream is assumed to be a bundle file.
74 The stream is assumed to be a bundle file.
75 Existing files will not be overwritten.
75 Existing files will not be overwritten.
76 If no filename is specified, a temporary file is created.
76 If no filename is specified, a temporary file is created.
77 """
77 """
78 fh = None
78 fh = None
79 cleanup = None
79 cleanup = None
80 try:
80 try:
81 if filename:
81 if filename:
82 if vfs:
82 if vfs:
83 fh = vfs.open(filename, b"wb")
83 fh = vfs.open(filename, b"wb")
84 else:
84 else:
85 # Increase default buffer size because default is usually
85 # Increase default buffer size because default is usually
86 # small (4k is common on Linux).
86 # small (4k is common on Linux).
87 fh = open(filename, b"wb", 131072)
87 fh = open(filename, b"wb", 131072)
88 else:
88 else:
89 fd, filename = pycompat.mkstemp(prefix=b"hg-bundle-", suffix=b".hg")
89 fd, filename = pycompat.mkstemp(prefix=b"hg-bundle-", suffix=b".hg")
90 fh = os.fdopen(fd, "wb")
90 fh = os.fdopen(fd, "wb")
91 cleanup = filename
91 cleanup = filename
92 for c in chunks:
92 for c in chunks:
93 fh.write(c)
93 fh.write(c)
94 cleanup = None
94 cleanup = None
95 return filename
95 return filename
96 finally:
96 finally:
97 if fh is not None:
97 if fh is not None:
98 fh.close()
98 fh.close()
99 if cleanup is not None:
99 if cleanup is not None:
100 if filename and vfs:
100 if filename and vfs:
101 vfs.unlink(cleanup)
101 vfs.unlink(cleanup)
102 else:
102 else:
103 os.unlink(cleanup)
103 os.unlink(cleanup)
104
104
105
105
106 class cg1unpacker(object):
106 class cg1unpacker(object):
107 """Unpacker for cg1 changegroup streams.
107 """Unpacker for cg1 changegroup streams.
108
108
109 A changegroup unpacker handles the framing of the revision data in
109 A changegroup unpacker handles the framing of the revision data in
110 the wire format. Most consumers will want to use the apply()
110 the wire format. Most consumers will want to use the apply()
111 method to add the changes from the changegroup to a repository.
111 method to add the changes from the changegroup to a repository.
112
112
113 If you're forwarding a changegroup unmodified to another consumer,
113 If you're forwarding a changegroup unmodified to another consumer,
114 use getchunks(), which returns an iterator of changegroup
114 use getchunks(), which returns an iterator of changegroup
115 chunks. This is mostly useful for cases where you need to know the
115 chunks. This is mostly useful for cases where you need to know the
116 data stream has ended by observing the end of the changegroup.
116 data stream has ended by observing the end of the changegroup.
117
117
118 deltachunk() is useful only if you're applying delta data. Most
118 deltachunk() is useful only if you're applying delta data. Most
119 consumers should prefer apply() instead.
119 consumers should prefer apply() instead.
120
120
121 A few other public methods exist. Those are used only for
121 A few other public methods exist. Those are used only for
122 bundlerepo and some debug commands - their use is discouraged.
122 bundlerepo and some debug commands - their use is discouraged.
123 """
123 """
124
124
125 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
125 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
126 deltaheadersize = deltaheader.size
126 deltaheadersize = deltaheader.size
127 version = b'01'
127 version = b'01'
128 _grouplistcount = 1 # One list of files after the manifests
128 _grouplistcount = 1 # One list of files after the manifests
129
129
130 def __init__(self, fh, alg, extras=None):
130 def __init__(self, fh, alg, extras=None):
131 if alg is None:
131 if alg is None:
132 alg = b'UN'
132 alg = b'UN'
133 if alg not in util.compengines.supportedbundletypes:
133 if alg not in util.compengines.supportedbundletypes:
134 raise error.Abort(_(b'unknown stream compression type: %s') % alg)
134 raise error.Abort(_(b'unknown stream compression type: %s') % alg)
135 if alg == b'BZ':
135 if alg == b'BZ':
136 alg = b'_truncatedBZ'
136 alg = b'_truncatedBZ'
137
137
138 compengine = util.compengines.forbundletype(alg)
138 compengine = util.compengines.forbundletype(alg)
139 self._stream = compengine.decompressorreader(fh)
139 self._stream = compengine.decompressorreader(fh)
140 self._type = alg
140 self._type = alg
141 self.extras = extras or {}
141 self.extras = extras or {}
142 self.callback = None
142 self.callback = None
143
143
144 # These methods (compressed, read, seek, tell) all appear to only
144 # These methods (compressed, read, seek, tell) all appear to only
145 # be used by bundlerepo, but it's a little hard to tell.
145 # be used by bundlerepo, but it's a little hard to tell.
146 def compressed(self):
146 def compressed(self):
147 return self._type is not None and self._type != b'UN'
147 return self._type is not None and self._type != b'UN'
148
148
149 def read(self, l):
149 def read(self, l):
150 return self._stream.read(l)
150 return self._stream.read(l)
151
151
152 def seek(self, pos):
152 def seek(self, pos):
153 return self._stream.seek(pos)
153 return self._stream.seek(pos)
154
154
155 def tell(self):
155 def tell(self):
156 return self._stream.tell()
156 return self._stream.tell()
157
157
158 def close(self):
158 def close(self):
159 return self._stream.close()
159 return self._stream.close()
160
160
161 def _chunklength(self):
161 def _chunklength(self):
162 d = readexactly(self._stream, 4)
162 d = readexactly(self._stream, 4)
163 l = struct.unpack(b">l", d)[0]
163 l = struct.unpack(b">l", d)[0]
164 if l <= 4:
164 if l <= 4:
165 if l:
165 if l:
166 raise error.Abort(_(b"invalid chunk length %d") % l)
166 raise error.Abort(_(b"invalid chunk length %d") % l)
167 return 0
167 return 0
168 if self.callback:
168 if self.callback:
169 self.callback()
169 self.callback()
170 return l - 4
170 return l - 4
171
171
172 def changelogheader(self):
172 def changelogheader(self):
173 """v10 does not have a changelog header chunk"""
173 """v10 does not have a changelog header chunk"""
174 return {}
174 return {}
175
175
176 def manifestheader(self):
176 def manifestheader(self):
177 """v10 does not have a manifest header chunk"""
177 """v10 does not have a manifest header chunk"""
178 return {}
178 return {}
179
179
180 def filelogheader(self):
180 def filelogheader(self):
181 """return the header of the filelogs chunk, v10 only has the filename"""
181 """return the header of the filelogs chunk, v10 only has the filename"""
182 l = self._chunklength()
182 l = self._chunklength()
183 if not l:
183 if not l:
184 return {}
184 return {}
185 fname = readexactly(self._stream, l)
185 fname = readexactly(self._stream, l)
186 return {b'filename': fname}
186 return {b'filename': fname}
187
187
188 def _deltaheader(self, headertuple, prevnode):
188 def _deltaheader(self, headertuple, prevnode):
189 node, p1, p2, cs = headertuple
189 node, p1, p2, cs = headertuple
190 if prevnode is None:
190 if prevnode is None:
191 deltabase = p1
191 deltabase = p1
192 else:
192 else:
193 deltabase = prevnode
193 deltabase = prevnode
194 flags = 0
194 flags = 0
195 return node, p1, p2, deltabase, cs, flags
195 return node, p1, p2, deltabase, cs, flags
196
196
197 def deltachunk(self, prevnode):
197 def deltachunk(self, prevnode):
198 l = self._chunklength()
198 l = self._chunklength()
199 if not l:
199 if not l:
200 return {}
200 return {}
201 headerdata = readexactly(self._stream, self.deltaheadersize)
201 headerdata = readexactly(self._stream, self.deltaheadersize)
202 header = self.deltaheader.unpack(headerdata)
202 header = self.deltaheader.unpack(headerdata)
203 delta = readexactly(self._stream, l - self.deltaheadersize)
203 delta = readexactly(self._stream, l - self.deltaheadersize)
204 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
204 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
205 return (node, p1, p2, cs, deltabase, delta, flags)
205 return (node, p1, p2, cs, deltabase, delta, flags)
206
206
207 def getchunks(self):
207 def getchunks(self):
208 """returns all the chunks contains in the bundle
208 """returns all the chunks contains in the bundle
209
209
210 Used when you need to forward the binary stream to a file or another
210 Used when you need to forward the binary stream to a file or another
211 network API. To do so, it parse the changegroup data, otherwise it will
211 network API. To do so, it parse the changegroup data, otherwise it will
212 block in case of sshrepo because it don't know the end of the stream.
212 block in case of sshrepo because it don't know the end of the stream.
213 """
213 """
214 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
214 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
215 # and a list of filelogs. For changegroup 3, we expect 4 parts:
215 # and a list of filelogs. For changegroup 3, we expect 4 parts:
216 # changelog, manifestlog, a list of tree manifestlogs, and a list of
216 # changelog, manifestlog, a list of tree manifestlogs, and a list of
217 # filelogs.
217 # filelogs.
218 #
218 #
219 # Changelog and manifestlog parts are terminated with empty chunks. The
219 # Changelog and manifestlog parts are terminated with empty chunks. The
220 # tree and file parts are a list of entry sections. Each entry section
220 # tree and file parts are a list of entry sections. Each entry section
221 # is a series of chunks terminating in an empty chunk. The list of these
221 # is a series of chunks terminating in an empty chunk. The list of these
222 # entry sections is terminated in yet another empty chunk, so we know
222 # entry sections is terminated in yet another empty chunk, so we know
223 # we've reached the end of the tree/file list when we reach an empty
223 # we've reached the end of the tree/file list when we reach an empty
224 # chunk that was proceeded by no non-empty chunks.
224 # chunk that was proceeded by no non-empty chunks.
225
225
226 parts = 0
226 parts = 0
227 while parts < 2 + self._grouplistcount:
227 while parts < 2 + self._grouplistcount:
228 noentries = True
228 noentries = True
229 while True:
229 while True:
230 chunk = getchunk(self)
230 chunk = getchunk(self)
231 if not chunk:
231 if not chunk:
232 # The first two empty chunks represent the end of the
232 # The first two empty chunks represent the end of the
233 # changelog and the manifestlog portions. The remaining
233 # changelog and the manifestlog portions. The remaining
234 # empty chunks represent either A) the end of individual
234 # empty chunks represent either A) the end of individual
235 # tree or file entries in the file list, or B) the end of
235 # tree or file entries in the file list, or B) the end of
236 # the entire list. It's the end of the entire list if there
236 # the entire list. It's the end of the entire list if there
237 # were no entries (i.e. noentries is True).
237 # were no entries (i.e. noentries is True).
238 if parts < 2:
238 if parts < 2:
239 parts += 1
239 parts += 1
240 elif noentries:
240 elif noentries:
241 parts += 1
241 parts += 1
242 break
242 break
243 noentries = False
243 noentries = False
244 yield chunkheader(len(chunk))
244 yield chunkheader(len(chunk))
245 pos = 0
245 pos = 0
246 while pos < len(chunk):
246 while pos < len(chunk):
247 next = pos + 2 ** 20
247 next = pos + 2 ** 20
248 yield chunk[pos:next]
248 yield chunk[pos:next]
249 pos = next
249 pos = next
250 yield closechunk()
250 yield closechunk()
251
251
252 def _unpackmanifests(self, repo, revmap, trp, prog):
252 def _unpackmanifests(self, repo, revmap, trp, prog):
253 self.callback = prog.increment
253 self.callback = prog.increment
254 # no need to check for empty manifest group here:
254 # no need to check for empty manifest group here:
255 # if the result of the merge of 1 and 2 is the same in 3 and 4,
255 # if the result of the merge of 1 and 2 is the same in 3 and 4,
256 # no new manifest will be created and the manifest group will
256 # no new manifest will be created and the manifest group will
257 # be empty during the pull
257 # be empty during the pull
258 self.manifestheader()
258 self.manifestheader()
259 deltas = self.deltaiter()
259 deltas = self.deltaiter()
260 repo.manifestlog.getstorage(b'').addgroup(deltas, revmap, trp)
260 repo.manifestlog.getstorage(b'').addgroup(deltas, revmap, trp)
261 prog.complete()
261 prog.complete()
262 self.callback = None
262 self.callback = None
263
263
264 def apply(
264 def apply(
265 self,
265 self,
266 repo,
266 repo,
267 tr,
267 tr,
268 srctype,
268 srctype,
269 url,
269 url,
270 targetphase=phases.draft,
270 targetphase=phases.draft,
271 expectedtotal=None,
271 expectedtotal=None,
272 ):
272 ):
273 """Add the changegroup returned by source.read() to this repo.
273 """Add the changegroup returned by source.read() to this repo.
274 srctype is a string like 'push', 'pull', or 'unbundle'. url is
274 srctype is a string like 'push', 'pull', or 'unbundle'. url is
275 the URL of the repo where this changegroup is coming from.
275 the URL of the repo where this changegroup is coming from.
276
276
277 Return an integer summarizing the change to this repo:
277 Return an integer summarizing the change to this repo:
278 - nothing changed or no source: 0
278 - nothing changed or no source: 0
279 - more heads than before: 1+added heads (2..n)
279 - more heads than before: 1+added heads (2..n)
280 - fewer heads than before: -1-removed heads (-2..-n)
280 - fewer heads than before: -1-removed heads (-2..-n)
281 - number of heads stays the same: 1
281 - number of heads stays the same: 1
282 """
282 """
283 repo = repo.unfiltered()
283 repo = repo.unfiltered()
284
284
285 def csmap(x):
285 def csmap(x):
286 repo.ui.debug(b"add changeset %s\n" % short(x))
286 repo.ui.debug(b"add changeset %s\n" % short(x))
287 return len(cl)
287 return len(cl)
288
288
289 def revmap(x):
289 def revmap(x):
290 return cl.rev(x)
290 return cl.rev(x)
291
291
292 try:
292 try:
293 # The transaction may already carry source information. In this
293 # The transaction may already carry source information. In this
294 # case we use the top level data. We overwrite the argument
294 # case we use the top level data. We overwrite the argument
295 # because we need to use the top level value (if they exist)
295 # because we need to use the top level value (if they exist)
296 # in this function.
296 # in this function.
297 srctype = tr.hookargs.setdefault(b'source', srctype)
297 srctype = tr.hookargs.setdefault(b'source', srctype)
298 tr.hookargs.setdefault(b'url', url)
298 tr.hookargs.setdefault(b'url', url)
299 repo.hook(
299 repo.hook(
300 b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs)
300 b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs)
301 )
301 )
302
302
303 # write changelog data to temp files so concurrent readers
303 # write changelog data to temp files so concurrent readers
304 # will not see an inconsistent view
304 # will not see an inconsistent view
305 cl = repo.changelog
305 cl = repo.changelog
306 cl.delayupdate(tr)
306 cl.delayupdate(tr)
307 oldheads = set(cl.heads())
307 oldheads = set(cl.heads())
308
308
309 trp = weakref.proxy(tr)
309 trp = weakref.proxy(tr)
310 # pull off the changeset group
310 # pull off the changeset group
311 repo.ui.status(_(b"adding changesets\n"))
311 repo.ui.status(_(b"adding changesets\n"))
312 clstart = len(cl)
312 clstart = len(cl)
313 progress = repo.ui.makeprogress(
313 progress = repo.ui.makeprogress(
314 _(b'changesets'), unit=_(b'chunks'), total=expectedtotal
314 _(b'changesets'), unit=_(b'chunks'), total=expectedtotal
315 )
315 )
316 self.callback = progress.increment
316 self.callback = progress.increment
317
317
318 efilesset = set()
318 efilesset = set()
319 cgnodes = []
319 cgnodes = []
320
320
321 def ondupchangelog(cl, node):
321 def ondupchangelog(cl, rev):
322 if cl.rev(node) < clstart:
322 if rev < clstart:
323 cgnodes.append(node)
323 cgnodes.append(cl.node(rev))
324
324
325 def onchangelog(cl, node):
325 def onchangelog(cl, rev):
326 rev = cl.rev(node)
327 ctx = cl.changelogrevision(rev)
326 ctx = cl.changelogrevision(rev)
328 efilesset.update(ctx.files)
327 efilesset.update(ctx.files)
329 repo.register_changeset(rev, ctx)
328 repo.register_changeset(rev, ctx)
330
329
331 self.changelogheader()
330 self.changelogheader()
332 deltas = self.deltaiter()
331 deltas = self.deltaiter()
333 if not cl.addgroup(
332 if not cl.addgroup(
334 deltas,
333 deltas,
335 csmap,
334 csmap,
336 trp,
335 trp,
337 alwayscache=True,
336 alwayscache=True,
338 addrevisioncb=onchangelog,
337 addrevisioncb=onchangelog,
339 duplicaterevisioncb=ondupchangelog,
338 duplicaterevisioncb=ondupchangelog,
340 ):
339 ):
341 repo.ui.develwarn(
340 repo.ui.develwarn(
342 b'applied empty changelog from changegroup',
341 b'applied empty changelog from changegroup',
343 config=b'warn-empty-changegroup',
342 config=b'warn-empty-changegroup',
344 )
343 )
345 efiles = len(efilesset)
344 efiles = len(efilesset)
346 clend = len(cl)
345 clend = len(cl)
347 changesets = clend - clstart
346 changesets = clend - clstart
348 progress.complete()
347 progress.complete()
349 del deltas
348 del deltas
350 # TODO Python 2.7 removal
349 # TODO Python 2.7 removal
351 # del efilesset
350 # del efilesset
352 efilesset = None
351 efilesset = None
353 self.callback = None
352 self.callback = None
354
353
355 # pull off the manifest group
354 # pull off the manifest group
356 repo.ui.status(_(b"adding manifests\n"))
355 repo.ui.status(_(b"adding manifests\n"))
357 # We know that we'll never have more manifests than we had
356 # We know that we'll never have more manifests than we had
358 # changesets.
357 # changesets.
359 progress = repo.ui.makeprogress(
358 progress = repo.ui.makeprogress(
360 _(b'manifests'), unit=_(b'chunks'), total=changesets
359 _(b'manifests'), unit=_(b'chunks'), total=changesets
361 )
360 )
362 self._unpackmanifests(repo, revmap, trp, progress)
361 self._unpackmanifests(repo, revmap, trp, progress)
363
362
364 needfiles = {}
363 needfiles = {}
365 if repo.ui.configbool(b'server', b'validate'):
364 if repo.ui.configbool(b'server', b'validate'):
366 cl = repo.changelog
365 cl = repo.changelog
367 ml = repo.manifestlog
366 ml = repo.manifestlog
368 # validate incoming csets have their manifests
367 # validate incoming csets have their manifests
369 for cset in pycompat.xrange(clstart, clend):
368 for cset in pycompat.xrange(clstart, clend):
370 mfnode = cl.changelogrevision(cset).manifest
369 mfnode = cl.changelogrevision(cset).manifest
371 mfest = ml[mfnode].readdelta()
370 mfest = ml[mfnode].readdelta()
372 # store file nodes we must see
371 # store file nodes we must see
373 for f, n in pycompat.iteritems(mfest):
372 for f, n in pycompat.iteritems(mfest):
374 needfiles.setdefault(f, set()).add(n)
373 needfiles.setdefault(f, set()).add(n)
375
374
376 # process the files
375 # process the files
377 repo.ui.status(_(b"adding file changes\n"))
376 repo.ui.status(_(b"adding file changes\n"))
378 newrevs, newfiles = _addchangegroupfiles(
377 newrevs, newfiles = _addchangegroupfiles(
379 repo, self, revmap, trp, efiles, needfiles
378 repo, self, revmap, trp, efiles, needfiles
380 )
379 )
381
380
382 # making sure the value exists
381 # making sure the value exists
383 tr.changes.setdefault(b'changegroup-count-changesets', 0)
382 tr.changes.setdefault(b'changegroup-count-changesets', 0)
384 tr.changes.setdefault(b'changegroup-count-revisions', 0)
383 tr.changes.setdefault(b'changegroup-count-revisions', 0)
385 tr.changes.setdefault(b'changegroup-count-files', 0)
384 tr.changes.setdefault(b'changegroup-count-files', 0)
386 tr.changes.setdefault(b'changegroup-count-heads', 0)
385 tr.changes.setdefault(b'changegroup-count-heads', 0)
387
386
388 # some code use bundle operation for internal purpose. They usually
387 # some code use bundle operation for internal purpose. They usually
389 # set `ui.quiet` to do this outside of user sight. Size the report
388 # set `ui.quiet` to do this outside of user sight. Size the report
390 # of such operation now happens at the end of the transaction, that
389 # of such operation now happens at the end of the transaction, that
391 # ui.quiet has not direct effect on the output.
390 # ui.quiet has not direct effect on the output.
392 #
391 #
393 # To preserve this intend use an inelegant hack, we fail to report
392 # To preserve this intend use an inelegant hack, we fail to report
394 # the change if `quiet` is set. We should probably move to
393 # the change if `quiet` is set. We should probably move to
395 # something better, but this is a good first step to allow the "end
394 # something better, but this is a good first step to allow the "end
396 # of transaction report" to pass tests.
395 # of transaction report" to pass tests.
397 if not repo.ui.quiet:
396 if not repo.ui.quiet:
398 tr.changes[b'changegroup-count-changesets'] += changesets
397 tr.changes[b'changegroup-count-changesets'] += changesets
399 tr.changes[b'changegroup-count-revisions'] += newrevs
398 tr.changes[b'changegroup-count-revisions'] += newrevs
400 tr.changes[b'changegroup-count-files'] += newfiles
399 tr.changes[b'changegroup-count-files'] += newfiles
401
400
402 deltaheads = 0
401 deltaheads = 0
403 if oldheads:
402 if oldheads:
404 heads = cl.heads()
403 heads = cl.heads()
405 deltaheads += len(heads) - len(oldheads)
404 deltaheads += len(heads) - len(oldheads)
406 for h in heads:
405 for h in heads:
407 if h not in oldheads and repo[h].closesbranch():
406 if h not in oldheads and repo[h].closesbranch():
408 deltaheads -= 1
407 deltaheads -= 1
409
408
410 # see previous comment about checking ui.quiet
409 # see previous comment about checking ui.quiet
411 if not repo.ui.quiet:
410 if not repo.ui.quiet:
412 tr.changes[b'changegroup-count-heads'] += deltaheads
411 tr.changes[b'changegroup-count-heads'] += deltaheads
413 repo.invalidatevolatilesets()
412 repo.invalidatevolatilesets()
414
413
415 if changesets > 0:
414 if changesets > 0:
416 if b'node' not in tr.hookargs:
415 if b'node' not in tr.hookargs:
417 tr.hookargs[b'node'] = hex(cl.node(clstart))
416 tr.hookargs[b'node'] = hex(cl.node(clstart))
418 tr.hookargs[b'node_last'] = hex(cl.node(clend - 1))
417 tr.hookargs[b'node_last'] = hex(cl.node(clend - 1))
419 hookargs = dict(tr.hookargs)
418 hookargs = dict(tr.hookargs)
420 else:
419 else:
421 hookargs = dict(tr.hookargs)
420 hookargs = dict(tr.hookargs)
422 hookargs[b'node'] = hex(cl.node(clstart))
421 hookargs[b'node'] = hex(cl.node(clstart))
423 hookargs[b'node_last'] = hex(cl.node(clend - 1))
422 hookargs[b'node_last'] = hex(cl.node(clend - 1))
424 repo.hook(
423 repo.hook(
425 b'pretxnchangegroup',
424 b'pretxnchangegroup',
426 throw=True,
425 throw=True,
427 **pycompat.strkwargs(hookargs)
426 **pycompat.strkwargs(hookargs)
428 )
427 )
429
428
430 added = pycompat.xrange(clstart, clend)
429 added = pycompat.xrange(clstart, clend)
431 phaseall = None
430 phaseall = None
432 if srctype in (b'push', b'serve'):
431 if srctype in (b'push', b'serve'):
433 # Old servers can not push the boundary themselves.
432 # Old servers can not push the boundary themselves.
434 # New servers won't push the boundary if changeset already
433 # New servers won't push the boundary if changeset already
435 # exists locally as secret
434 # exists locally as secret
436 #
435 #
437 # We should not use added here but the list of all change in
436 # We should not use added here but the list of all change in
438 # the bundle
437 # the bundle
439 if repo.publishing():
438 if repo.publishing():
440 targetphase = phaseall = phases.public
439 targetphase = phaseall = phases.public
441 else:
440 else:
442 # closer target phase computation
441 # closer target phase computation
443
442
444 # Those changesets have been pushed from the
443 # Those changesets have been pushed from the
445 # outside, their phases are going to be pushed
444 # outside, their phases are going to be pushed
446 # alongside. Therefor `targetphase` is
445 # alongside. Therefor `targetphase` is
447 # ignored.
446 # ignored.
448 targetphase = phaseall = phases.draft
447 targetphase = phaseall = phases.draft
449 if added:
448 if added:
450 phases.registernew(repo, tr, targetphase, added)
449 phases.registernew(repo, tr, targetphase, added)
451 if phaseall is not None:
450 if phaseall is not None:
452 phases.advanceboundary(repo, tr, phaseall, cgnodes, revs=added)
451 phases.advanceboundary(repo, tr, phaseall, cgnodes, revs=added)
453 cgnodes = []
452 cgnodes = []
454
453
455 if changesets > 0:
454 if changesets > 0:
456
455
457 def runhooks(unused_success):
456 def runhooks(unused_success):
458 # These hooks run when the lock releases, not when the
457 # These hooks run when the lock releases, not when the
459 # transaction closes. So it's possible for the changelog
458 # transaction closes. So it's possible for the changelog
460 # to have changed since we last saw it.
459 # to have changed since we last saw it.
461 if clstart >= len(repo):
460 if clstart >= len(repo):
462 return
461 return
463
462
464 repo.hook(b"changegroup", **pycompat.strkwargs(hookargs))
463 repo.hook(b"changegroup", **pycompat.strkwargs(hookargs))
465
464
466 for rev in added:
465 for rev in added:
467 args = hookargs.copy()
466 args = hookargs.copy()
468 args[b'node'] = hex(cl.node(rev))
467 args[b'node'] = hex(cl.node(rev))
469 del args[b'node_last']
468 del args[b'node_last']
470 repo.hook(b"incoming", **pycompat.strkwargs(args))
469 repo.hook(b"incoming", **pycompat.strkwargs(args))
471
470
472 newheads = [h for h in repo.heads() if h not in oldheads]
471 newheads = [h for h in repo.heads() if h not in oldheads]
473 repo.ui.log(
472 repo.ui.log(
474 b"incoming",
473 b"incoming",
475 b"%d incoming changes - new heads: %s\n",
474 b"%d incoming changes - new heads: %s\n",
476 len(added),
475 len(added),
477 b', '.join([hex(c[:6]) for c in newheads]),
476 b', '.join([hex(c[:6]) for c in newheads]),
478 )
477 )
479
478
480 tr.addpostclose(
479 tr.addpostclose(
481 b'changegroup-runhooks-%020i' % clstart,
480 b'changegroup-runhooks-%020i' % clstart,
482 lambda tr: repo._afterlock(runhooks),
481 lambda tr: repo._afterlock(runhooks),
483 )
482 )
484 finally:
483 finally:
485 repo.ui.flush()
484 repo.ui.flush()
486 # never return 0 here:
485 # never return 0 here:
487 if deltaheads < 0:
486 if deltaheads < 0:
488 ret = deltaheads - 1
487 ret = deltaheads - 1
489 else:
488 else:
490 ret = deltaheads + 1
489 ret = deltaheads + 1
491 return ret
490 return ret
492
491
493 def deltaiter(self):
492 def deltaiter(self):
494 """
493 """
495 returns an iterator of the deltas in this changegroup
494 returns an iterator of the deltas in this changegroup
496
495
497 Useful for passing to the underlying storage system to be stored.
496 Useful for passing to the underlying storage system to be stored.
498 """
497 """
499 chain = None
498 chain = None
500 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
499 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
501 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
500 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
502 yield chunkdata
501 yield chunkdata
503 chain = chunkdata[0]
502 chain = chunkdata[0]
504
503
505
504
506 class cg2unpacker(cg1unpacker):
505 class cg2unpacker(cg1unpacker):
507 """Unpacker for cg2 streams.
506 """Unpacker for cg2 streams.
508
507
509 cg2 streams add support for generaldelta, so the delta header
508 cg2 streams add support for generaldelta, so the delta header
510 format is slightly different. All other features about the data
509 format is slightly different. All other features about the data
511 remain the same.
510 remain the same.
512 """
511 """
513
512
514 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
513 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
515 deltaheadersize = deltaheader.size
514 deltaheadersize = deltaheader.size
516 version = b'02'
515 version = b'02'
517
516
518 def _deltaheader(self, headertuple, prevnode):
517 def _deltaheader(self, headertuple, prevnode):
519 node, p1, p2, deltabase, cs = headertuple
518 node, p1, p2, deltabase, cs = headertuple
520 flags = 0
519 flags = 0
521 return node, p1, p2, deltabase, cs, flags
520 return node, p1, p2, deltabase, cs, flags
522
521
523
522
524 class cg3unpacker(cg2unpacker):
523 class cg3unpacker(cg2unpacker):
525 """Unpacker for cg3 streams.
524 """Unpacker for cg3 streams.
526
525
527 cg3 streams add support for exchanging treemanifests and revlog
526 cg3 streams add support for exchanging treemanifests and revlog
528 flags. It adds the revlog flags to the delta header and an empty chunk
527 flags. It adds the revlog flags to the delta header and an empty chunk
529 separating manifests and files.
528 separating manifests and files.
530 """
529 """
531
530
532 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
531 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
533 deltaheadersize = deltaheader.size
532 deltaheadersize = deltaheader.size
534 version = b'03'
533 version = b'03'
535 _grouplistcount = 2 # One list of manifests and one list of files
534 _grouplistcount = 2 # One list of manifests and one list of files
536
535
537 def _deltaheader(self, headertuple, prevnode):
536 def _deltaheader(self, headertuple, prevnode):
538 node, p1, p2, deltabase, cs, flags = headertuple
537 node, p1, p2, deltabase, cs, flags = headertuple
539 return node, p1, p2, deltabase, cs, flags
538 return node, p1, p2, deltabase, cs, flags
540
539
541 def _unpackmanifests(self, repo, revmap, trp, prog):
540 def _unpackmanifests(self, repo, revmap, trp, prog):
542 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog)
541 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog)
543 for chunkdata in iter(self.filelogheader, {}):
542 for chunkdata in iter(self.filelogheader, {}):
544 # If we get here, there are directory manifests in the changegroup
543 # If we get here, there are directory manifests in the changegroup
545 d = chunkdata[b"filename"]
544 d = chunkdata[b"filename"]
546 repo.ui.debug(b"adding %s revisions\n" % d)
545 repo.ui.debug(b"adding %s revisions\n" % d)
547 deltas = self.deltaiter()
546 deltas = self.deltaiter()
548 if not repo.manifestlog.getstorage(d).addgroup(deltas, revmap, trp):
547 if not repo.manifestlog.getstorage(d).addgroup(deltas, revmap, trp):
549 raise error.Abort(_(b"received dir revlog group is empty"))
548 raise error.Abort(_(b"received dir revlog group is empty"))
550
549
551
550
552 class headerlessfixup(object):
551 class headerlessfixup(object):
553 def __init__(self, fh, h):
552 def __init__(self, fh, h):
554 self._h = h
553 self._h = h
555 self._fh = fh
554 self._fh = fh
556
555
557 def read(self, n):
556 def read(self, n):
558 if self._h:
557 if self._h:
559 d, self._h = self._h[:n], self._h[n:]
558 d, self._h = self._h[:n], self._h[n:]
560 if len(d) < n:
559 if len(d) < n:
561 d += readexactly(self._fh, n - len(d))
560 d += readexactly(self._fh, n - len(d))
562 return d
561 return d
563 return readexactly(self._fh, n)
562 return readexactly(self._fh, n)
564
563
565
564
566 def _revisiondeltatochunks(delta, headerfn):
565 def _revisiondeltatochunks(delta, headerfn):
567 """Serialize a revisiondelta to changegroup chunks."""
566 """Serialize a revisiondelta to changegroup chunks."""
568
567
569 # The captured revision delta may be encoded as a delta against
568 # The captured revision delta may be encoded as a delta against
570 # a base revision or as a full revision. The changegroup format
569 # a base revision or as a full revision. The changegroup format
571 # requires that everything on the wire be deltas. So for full
570 # requires that everything on the wire be deltas. So for full
572 # revisions, we need to invent a header that says to rewrite
571 # revisions, we need to invent a header that says to rewrite
573 # data.
572 # data.
574
573
575 if delta.delta is not None:
574 if delta.delta is not None:
576 prefix, data = b'', delta.delta
575 prefix, data = b'', delta.delta
577 elif delta.basenode == nullid:
576 elif delta.basenode == nullid:
578 data = delta.revision
577 data = delta.revision
579 prefix = mdiff.trivialdiffheader(len(data))
578 prefix = mdiff.trivialdiffheader(len(data))
580 else:
579 else:
581 data = delta.revision
580 data = delta.revision
582 prefix = mdiff.replacediffheader(delta.baserevisionsize, len(data))
581 prefix = mdiff.replacediffheader(delta.baserevisionsize, len(data))
583
582
584 meta = headerfn(delta)
583 meta = headerfn(delta)
585
584
586 yield chunkheader(len(meta) + len(prefix) + len(data))
585 yield chunkheader(len(meta) + len(prefix) + len(data))
587 yield meta
586 yield meta
588 if prefix:
587 if prefix:
589 yield prefix
588 yield prefix
590 yield data
589 yield data
591
590
592
591
593 def _sortnodesellipsis(store, nodes, cl, lookup):
592 def _sortnodesellipsis(store, nodes, cl, lookup):
594 """Sort nodes for changegroup generation."""
593 """Sort nodes for changegroup generation."""
595 # Ellipses serving mode.
594 # Ellipses serving mode.
596 #
595 #
597 # In a perfect world, we'd generate better ellipsis-ified graphs
596 # In a perfect world, we'd generate better ellipsis-ified graphs
598 # for non-changelog revlogs. In practice, we haven't started doing
597 # for non-changelog revlogs. In practice, we haven't started doing
599 # that yet, so the resulting DAGs for the manifestlog and filelogs
598 # that yet, so the resulting DAGs for the manifestlog and filelogs
600 # are actually full of bogus parentage on all the ellipsis
599 # are actually full of bogus parentage on all the ellipsis
601 # nodes. This has the side effect that, while the contents are
600 # nodes. This has the side effect that, while the contents are
602 # correct, the individual DAGs might be completely out of whack in
601 # correct, the individual DAGs might be completely out of whack in
603 # a case like 882681bc3166 and its ancestors (back about 10
602 # a case like 882681bc3166 and its ancestors (back about 10
604 # revisions or so) in the main hg repo.
603 # revisions or so) in the main hg repo.
605 #
604 #
606 # The one invariant we *know* holds is that the new (potentially
605 # The one invariant we *know* holds is that the new (potentially
607 # bogus) DAG shape will be valid if we order the nodes in the
606 # bogus) DAG shape will be valid if we order the nodes in the
608 # order that they're introduced in dramatis personae by the
607 # order that they're introduced in dramatis personae by the
609 # changelog, so what we do is we sort the non-changelog histories
608 # changelog, so what we do is we sort the non-changelog histories
610 # by the order in which they are used by the changelog.
609 # by the order in which they are used by the changelog.
611 key = lambda n: cl.rev(lookup(n))
610 key = lambda n: cl.rev(lookup(n))
612 return sorted(nodes, key=key)
611 return sorted(nodes, key=key)
613
612
614
613
615 def _resolvenarrowrevisioninfo(
614 def _resolvenarrowrevisioninfo(
616 cl,
615 cl,
617 store,
616 store,
618 ischangelog,
617 ischangelog,
619 rev,
618 rev,
620 linkrev,
619 linkrev,
621 linknode,
620 linknode,
622 clrevtolocalrev,
621 clrevtolocalrev,
623 fullclnodes,
622 fullclnodes,
624 precomputedellipsis,
623 precomputedellipsis,
625 ):
624 ):
626 linkparents = precomputedellipsis[linkrev]
625 linkparents = precomputedellipsis[linkrev]
627
626
628 def local(clrev):
627 def local(clrev):
629 """Turn a changelog revnum into a local revnum.
628 """Turn a changelog revnum into a local revnum.
630
629
631 The ellipsis dag is stored as revnums on the changelog,
630 The ellipsis dag is stored as revnums on the changelog,
632 but when we're producing ellipsis entries for
631 but when we're producing ellipsis entries for
633 non-changelog revlogs, we need to turn those numbers into
632 non-changelog revlogs, we need to turn those numbers into
634 something local. This does that for us, and during the
633 something local. This does that for us, and during the
635 changelog sending phase will also expand the stored
634 changelog sending phase will also expand the stored
636 mappings as needed.
635 mappings as needed.
637 """
636 """
638 if clrev == nullrev:
637 if clrev == nullrev:
639 return nullrev
638 return nullrev
640
639
641 if ischangelog:
640 if ischangelog:
642 return clrev
641 return clrev
643
642
644 # Walk the ellipsis-ized changelog breadth-first looking for a
643 # Walk the ellipsis-ized changelog breadth-first looking for a
645 # change that has been linked from the current revlog.
644 # change that has been linked from the current revlog.
646 #
645 #
647 # For a flat manifest revlog only a single step should be necessary
646 # For a flat manifest revlog only a single step should be necessary
648 # as all relevant changelog entries are relevant to the flat
647 # as all relevant changelog entries are relevant to the flat
649 # manifest.
648 # manifest.
650 #
649 #
651 # For a filelog or tree manifest dirlog however not every changelog
650 # For a filelog or tree manifest dirlog however not every changelog
652 # entry will have been relevant, so we need to skip some changelog
651 # entry will have been relevant, so we need to skip some changelog
653 # nodes even after ellipsis-izing.
652 # nodes even after ellipsis-izing.
654 walk = [clrev]
653 walk = [clrev]
655 while walk:
654 while walk:
656 p = walk[0]
655 p = walk[0]
657 walk = walk[1:]
656 walk = walk[1:]
658 if p in clrevtolocalrev:
657 if p in clrevtolocalrev:
659 return clrevtolocalrev[p]
658 return clrevtolocalrev[p]
660 elif p in fullclnodes:
659 elif p in fullclnodes:
661 walk.extend([pp for pp in cl.parentrevs(p) if pp != nullrev])
660 walk.extend([pp for pp in cl.parentrevs(p) if pp != nullrev])
662 elif p in precomputedellipsis:
661 elif p in precomputedellipsis:
663 walk.extend(
662 walk.extend(
664 [pp for pp in precomputedellipsis[p] if pp != nullrev]
663 [pp for pp in precomputedellipsis[p] if pp != nullrev]
665 )
664 )
666 else:
665 else:
667 # In this case, we've got an ellipsis with parents
666 # In this case, we've got an ellipsis with parents
668 # outside the current bundle (likely an
667 # outside the current bundle (likely an
669 # incremental pull). We "know" that we can use the
668 # incremental pull). We "know" that we can use the
670 # value of this same revlog at whatever revision
669 # value of this same revlog at whatever revision
671 # is pointed to by linknode. "Know" is in scare
670 # is pointed to by linknode. "Know" is in scare
672 # quotes because I haven't done enough examination
671 # quotes because I haven't done enough examination
673 # of edge cases to convince myself this is really
672 # of edge cases to convince myself this is really
674 # a fact - it works for all the (admittedly
673 # a fact - it works for all the (admittedly
675 # thorough) cases in our testsuite, but I would be
674 # thorough) cases in our testsuite, but I would be
676 # somewhat unsurprised to find a case in the wild
675 # somewhat unsurprised to find a case in the wild
677 # where this breaks down a bit. That said, I don't
676 # where this breaks down a bit. That said, I don't
678 # know if it would hurt anything.
677 # know if it would hurt anything.
679 for i in pycompat.xrange(rev, 0, -1):
678 for i in pycompat.xrange(rev, 0, -1):
680 if store.linkrev(i) == clrev:
679 if store.linkrev(i) == clrev:
681 return i
680 return i
682 # We failed to resolve a parent for this node, so
681 # We failed to resolve a parent for this node, so
683 # we crash the changegroup construction.
682 # we crash the changegroup construction.
684 raise error.Abort(
683 raise error.Abort(
685 b'unable to resolve parent while packing %r %r'
684 b'unable to resolve parent while packing %r %r'
686 b' for changeset %r' % (store.indexfile, rev, clrev)
685 b' for changeset %r' % (store.indexfile, rev, clrev)
687 )
686 )
688
687
689 return nullrev
688 return nullrev
690
689
691 if not linkparents or (store.parentrevs(rev) == (nullrev, nullrev)):
690 if not linkparents or (store.parentrevs(rev) == (nullrev, nullrev)):
692 p1, p2 = nullrev, nullrev
691 p1, p2 = nullrev, nullrev
693 elif len(linkparents) == 1:
692 elif len(linkparents) == 1:
694 (p1,) = sorted(local(p) for p in linkparents)
693 (p1,) = sorted(local(p) for p in linkparents)
695 p2 = nullrev
694 p2 = nullrev
696 else:
695 else:
697 p1, p2 = sorted(local(p) for p in linkparents)
696 p1, p2 = sorted(local(p) for p in linkparents)
698
697
699 p1node, p2node = store.node(p1), store.node(p2)
698 p1node, p2node = store.node(p1), store.node(p2)
700
699
701 return p1node, p2node, linknode
700 return p1node, p2node, linknode
702
701
703
702
704 def deltagroup(
703 def deltagroup(
705 repo,
704 repo,
706 store,
705 store,
707 nodes,
706 nodes,
708 ischangelog,
707 ischangelog,
709 lookup,
708 lookup,
710 forcedeltaparentprev,
709 forcedeltaparentprev,
711 topic=None,
710 topic=None,
712 ellipses=False,
711 ellipses=False,
713 clrevtolocalrev=None,
712 clrevtolocalrev=None,
714 fullclnodes=None,
713 fullclnodes=None,
715 precomputedellipsis=None,
714 precomputedellipsis=None,
716 ):
715 ):
717 """Calculate deltas for a set of revisions.
716 """Calculate deltas for a set of revisions.
718
717
719 Is a generator of ``revisiondelta`` instances.
718 Is a generator of ``revisiondelta`` instances.
720
719
721 If topic is not None, progress detail will be generated using this
720 If topic is not None, progress detail will be generated using this
722 topic name (e.g. changesets, manifests, etc).
721 topic name (e.g. changesets, manifests, etc).
723 """
722 """
724 if not nodes:
723 if not nodes:
725 return
724 return
726
725
727 cl = repo.changelog
726 cl = repo.changelog
728
727
729 if ischangelog:
728 if ischangelog:
730 # `hg log` shows changesets in storage order. To preserve order
729 # `hg log` shows changesets in storage order. To preserve order
731 # across clones, send out changesets in storage order.
730 # across clones, send out changesets in storage order.
732 nodesorder = b'storage'
731 nodesorder = b'storage'
733 elif ellipses:
732 elif ellipses:
734 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
733 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
735 nodesorder = b'nodes'
734 nodesorder = b'nodes'
736 else:
735 else:
737 nodesorder = None
736 nodesorder = None
738
737
739 # Perform ellipses filtering and revision massaging. We do this before
738 # Perform ellipses filtering and revision massaging. We do this before
740 # emitrevisions() because a) filtering out revisions creates less work
739 # emitrevisions() because a) filtering out revisions creates less work
741 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
740 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
742 # assumptions about delta choices and we would possibly send a delta
741 # assumptions about delta choices and we would possibly send a delta
743 # referencing a missing base revision.
742 # referencing a missing base revision.
744 #
743 #
745 # Also, calling lookup() has side-effects with regards to populating
744 # Also, calling lookup() has side-effects with regards to populating
746 # data structures. If we don't call lookup() for each node or if we call
745 # data structures. If we don't call lookup() for each node or if we call
747 # lookup() after the first pass through each node, things can break -
746 # lookup() after the first pass through each node, things can break -
748 # possibly intermittently depending on the python hash seed! For that
747 # possibly intermittently depending on the python hash seed! For that
749 # reason, we store a mapping of all linknodes during the initial node
748 # reason, we store a mapping of all linknodes during the initial node
750 # pass rather than use lookup() on the output side.
749 # pass rather than use lookup() on the output side.
751 if ellipses:
750 if ellipses:
752 filtered = []
751 filtered = []
753 adjustedparents = {}
752 adjustedparents = {}
754 linknodes = {}
753 linknodes = {}
755
754
756 for node in nodes:
755 for node in nodes:
757 rev = store.rev(node)
756 rev = store.rev(node)
758 linknode = lookup(node)
757 linknode = lookup(node)
759 linkrev = cl.rev(linknode)
758 linkrev = cl.rev(linknode)
760 clrevtolocalrev[linkrev] = rev
759 clrevtolocalrev[linkrev] = rev
761
760
762 # If linknode is in fullclnodes, it means the corresponding
761 # If linknode is in fullclnodes, it means the corresponding
763 # changeset was a full changeset and is being sent unaltered.
762 # changeset was a full changeset and is being sent unaltered.
764 if linknode in fullclnodes:
763 if linknode in fullclnodes:
765 linknodes[node] = linknode
764 linknodes[node] = linknode
766
765
767 # If the corresponding changeset wasn't in the set computed
766 # If the corresponding changeset wasn't in the set computed
768 # as relevant to us, it should be dropped outright.
767 # as relevant to us, it should be dropped outright.
769 elif linkrev not in precomputedellipsis:
768 elif linkrev not in precomputedellipsis:
770 continue
769 continue
771
770
772 else:
771 else:
773 # We could probably do this later and avoid the dict
772 # We could probably do this later and avoid the dict
774 # holding state. But it likely doesn't matter.
773 # holding state. But it likely doesn't matter.
775 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
774 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
776 cl,
775 cl,
777 store,
776 store,
778 ischangelog,
777 ischangelog,
779 rev,
778 rev,
780 linkrev,
779 linkrev,
781 linknode,
780 linknode,
782 clrevtolocalrev,
781 clrevtolocalrev,
783 fullclnodes,
782 fullclnodes,
784 precomputedellipsis,
783 precomputedellipsis,
785 )
784 )
786
785
787 adjustedparents[node] = (p1node, p2node)
786 adjustedparents[node] = (p1node, p2node)
788 linknodes[node] = linknode
787 linknodes[node] = linknode
789
788
790 filtered.append(node)
789 filtered.append(node)
791
790
792 nodes = filtered
791 nodes = filtered
793
792
794 # We expect the first pass to be fast, so we only engage the progress
793 # We expect the first pass to be fast, so we only engage the progress
795 # meter for constructing the revision deltas.
794 # meter for constructing the revision deltas.
796 progress = None
795 progress = None
797 if topic is not None:
796 if topic is not None:
798 progress = repo.ui.makeprogress(
797 progress = repo.ui.makeprogress(
799 topic, unit=_(b'chunks'), total=len(nodes)
798 topic, unit=_(b'chunks'), total=len(nodes)
800 )
799 )
801
800
802 configtarget = repo.ui.config(b'devel', b'bundle.delta')
801 configtarget = repo.ui.config(b'devel', b'bundle.delta')
803 if configtarget not in (b'', b'p1', b'full'):
802 if configtarget not in (b'', b'p1', b'full'):
804 msg = _("""config "devel.bundle.delta" as unknown value: %s""")
803 msg = _("""config "devel.bundle.delta" as unknown value: %s""")
805 repo.ui.warn(msg % configtarget)
804 repo.ui.warn(msg % configtarget)
806
805
807 deltamode = repository.CG_DELTAMODE_STD
806 deltamode = repository.CG_DELTAMODE_STD
808 if forcedeltaparentprev:
807 if forcedeltaparentprev:
809 deltamode = repository.CG_DELTAMODE_PREV
808 deltamode = repository.CG_DELTAMODE_PREV
810 elif configtarget == b'p1':
809 elif configtarget == b'p1':
811 deltamode = repository.CG_DELTAMODE_P1
810 deltamode = repository.CG_DELTAMODE_P1
812 elif configtarget == b'full':
811 elif configtarget == b'full':
813 deltamode = repository.CG_DELTAMODE_FULL
812 deltamode = repository.CG_DELTAMODE_FULL
814
813
815 revisions = store.emitrevisions(
814 revisions = store.emitrevisions(
816 nodes,
815 nodes,
817 nodesorder=nodesorder,
816 nodesorder=nodesorder,
818 revisiondata=True,
817 revisiondata=True,
819 assumehaveparentrevisions=not ellipses,
818 assumehaveparentrevisions=not ellipses,
820 deltamode=deltamode,
819 deltamode=deltamode,
821 )
820 )
822
821
823 for i, revision in enumerate(revisions):
822 for i, revision in enumerate(revisions):
824 if progress:
823 if progress:
825 progress.update(i + 1)
824 progress.update(i + 1)
826
825
827 if ellipses:
826 if ellipses:
828 linknode = linknodes[revision.node]
827 linknode = linknodes[revision.node]
829
828
830 if revision.node in adjustedparents:
829 if revision.node in adjustedparents:
831 p1node, p2node = adjustedparents[revision.node]
830 p1node, p2node = adjustedparents[revision.node]
832 revision.p1node = p1node
831 revision.p1node = p1node
833 revision.p2node = p2node
832 revision.p2node = p2node
834 revision.flags |= repository.REVISION_FLAG_ELLIPSIS
833 revision.flags |= repository.REVISION_FLAG_ELLIPSIS
835
834
836 else:
835 else:
837 linknode = lookup(revision.node)
836 linknode = lookup(revision.node)
838
837
839 revision.linknode = linknode
838 revision.linknode = linknode
840 yield revision
839 yield revision
841
840
842 if progress:
841 if progress:
843 progress.complete()
842 progress.complete()
844
843
845
844
846 class cgpacker(object):
845 class cgpacker(object):
847 def __init__(
846 def __init__(
848 self,
847 self,
849 repo,
848 repo,
850 oldmatcher,
849 oldmatcher,
851 matcher,
850 matcher,
852 version,
851 version,
853 builddeltaheader,
852 builddeltaheader,
854 manifestsend,
853 manifestsend,
855 forcedeltaparentprev=False,
854 forcedeltaparentprev=False,
856 bundlecaps=None,
855 bundlecaps=None,
857 ellipses=False,
856 ellipses=False,
858 shallow=False,
857 shallow=False,
859 ellipsisroots=None,
858 ellipsisroots=None,
860 fullnodes=None,
859 fullnodes=None,
861 ):
860 ):
862 """Given a source repo, construct a bundler.
861 """Given a source repo, construct a bundler.
863
862
864 oldmatcher is a matcher that matches on files the client already has.
863 oldmatcher is a matcher that matches on files the client already has.
865 These will not be included in the changegroup.
864 These will not be included in the changegroup.
866
865
867 matcher is a matcher that matches on files to include in the
866 matcher is a matcher that matches on files to include in the
868 changegroup. Used to facilitate sparse changegroups.
867 changegroup. Used to facilitate sparse changegroups.
869
868
870 forcedeltaparentprev indicates whether delta parents must be against
869 forcedeltaparentprev indicates whether delta parents must be against
871 the previous revision in a delta group. This should only be used for
870 the previous revision in a delta group. This should only be used for
872 compatibility with changegroup version 1.
871 compatibility with changegroup version 1.
873
872
874 builddeltaheader is a callable that constructs the header for a group
873 builddeltaheader is a callable that constructs the header for a group
875 delta.
874 delta.
876
875
877 manifestsend is a chunk to send after manifests have been fully emitted.
876 manifestsend is a chunk to send after manifests have been fully emitted.
878
877
879 ellipses indicates whether ellipsis serving mode is enabled.
878 ellipses indicates whether ellipsis serving mode is enabled.
880
879
881 bundlecaps is optional and can be used to specify the set of
880 bundlecaps is optional and can be used to specify the set of
882 capabilities which can be used to build the bundle. While bundlecaps is
881 capabilities which can be used to build the bundle. While bundlecaps is
883 unused in core Mercurial, extensions rely on this feature to communicate
882 unused in core Mercurial, extensions rely on this feature to communicate
884 capabilities to customize the changegroup packer.
883 capabilities to customize the changegroup packer.
885
884
886 shallow indicates whether shallow data might be sent. The packer may
885 shallow indicates whether shallow data might be sent. The packer may
887 need to pack file contents not introduced by the changes being packed.
886 need to pack file contents not introduced by the changes being packed.
888
887
889 fullnodes is the set of changelog nodes which should not be ellipsis
888 fullnodes is the set of changelog nodes which should not be ellipsis
890 nodes. We store this rather than the set of nodes that should be
889 nodes. We store this rather than the set of nodes that should be
891 ellipsis because for very large histories we expect this to be
890 ellipsis because for very large histories we expect this to be
892 significantly smaller.
891 significantly smaller.
893 """
892 """
894 assert oldmatcher
893 assert oldmatcher
895 assert matcher
894 assert matcher
896 self._oldmatcher = oldmatcher
895 self._oldmatcher = oldmatcher
897 self._matcher = matcher
896 self._matcher = matcher
898
897
899 self.version = version
898 self.version = version
900 self._forcedeltaparentprev = forcedeltaparentprev
899 self._forcedeltaparentprev = forcedeltaparentprev
901 self._builddeltaheader = builddeltaheader
900 self._builddeltaheader = builddeltaheader
902 self._manifestsend = manifestsend
901 self._manifestsend = manifestsend
903 self._ellipses = ellipses
902 self._ellipses = ellipses
904
903
905 # Set of capabilities we can use to build the bundle.
904 # Set of capabilities we can use to build the bundle.
906 if bundlecaps is None:
905 if bundlecaps is None:
907 bundlecaps = set()
906 bundlecaps = set()
908 self._bundlecaps = bundlecaps
907 self._bundlecaps = bundlecaps
909 self._isshallow = shallow
908 self._isshallow = shallow
910 self._fullclnodes = fullnodes
909 self._fullclnodes = fullnodes
911
910
912 # Maps ellipsis revs to their roots at the changelog level.
911 # Maps ellipsis revs to their roots at the changelog level.
913 self._precomputedellipsis = ellipsisroots
912 self._precomputedellipsis = ellipsisroots
914
913
915 self._repo = repo
914 self._repo = repo
916
915
917 if self._repo.ui.verbose and not self._repo.ui.debugflag:
916 if self._repo.ui.verbose and not self._repo.ui.debugflag:
918 self._verbosenote = self._repo.ui.note
917 self._verbosenote = self._repo.ui.note
919 else:
918 else:
920 self._verbosenote = lambda s: None
919 self._verbosenote = lambda s: None
921
920
922 def generate(
921 def generate(
923 self, commonrevs, clnodes, fastpathlinkrev, source, changelog=True
922 self, commonrevs, clnodes, fastpathlinkrev, source, changelog=True
924 ):
923 ):
925 """Yield a sequence of changegroup byte chunks.
924 """Yield a sequence of changegroup byte chunks.
926 If changelog is False, changelog data won't be added to changegroup
925 If changelog is False, changelog data won't be added to changegroup
927 """
926 """
928
927
929 repo = self._repo
928 repo = self._repo
930 cl = repo.changelog
929 cl = repo.changelog
931
930
932 self._verbosenote(_(b'uncompressed size of bundle content:\n'))
931 self._verbosenote(_(b'uncompressed size of bundle content:\n'))
933 size = 0
932 size = 0
934
933
935 clstate, deltas = self._generatechangelog(
934 clstate, deltas = self._generatechangelog(
936 cl, clnodes, generate=changelog
935 cl, clnodes, generate=changelog
937 )
936 )
938 for delta in deltas:
937 for delta in deltas:
939 for chunk in _revisiondeltatochunks(delta, self._builddeltaheader):
938 for chunk in _revisiondeltatochunks(delta, self._builddeltaheader):
940 size += len(chunk)
939 size += len(chunk)
941 yield chunk
940 yield chunk
942
941
943 close = closechunk()
942 close = closechunk()
944 size += len(close)
943 size += len(close)
945 yield closechunk()
944 yield closechunk()
946
945
947 self._verbosenote(_(b'%8.i (changelog)\n') % size)
946 self._verbosenote(_(b'%8.i (changelog)\n') % size)
948
947
949 clrevorder = clstate[b'clrevorder']
948 clrevorder = clstate[b'clrevorder']
950 manifests = clstate[b'manifests']
949 manifests = clstate[b'manifests']
951 changedfiles = clstate[b'changedfiles']
950 changedfiles = clstate[b'changedfiles']
952
951
953 # We need to make sure that the linkrev in the changegroup refers to
952 # We need to make sure that the linkrev in the changegroup refers to
954 # the first changeset that introduced the manifest or file revision.
953 # the first changeset that introduced the manifest or file revision.
955 # The fastpath is usually safer than the slowpath, because the filelogs
954 # The fastpath is usually safer than the slowpath, because the filelogs
956 # are walked in revlog order.
955 # are walked in revlog order.
957 #
956 #
958 # When taking the slowpath when the manifest revlog uses generaldelta,
957 # When taking the slowpath when the manifest revlog uses generaldelta,
959 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
958 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
960 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
959 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
961 #
960 #
962 # When taking the fastpath, we are only vulnerable to reordering
961 # When taking the fastpath, we are only vulnerable to reordering
963 # of the changelog itself. The changelog never uses generaldelta and is
962 # of the changelog itself. The changelog never uses generaldelta and is
964 # never reordered. To handle this case, we simply take the slowpath,
963 # never reordered. To handle this case, we simply take the slowpath,
965 # which already has the 'clrevorder' logic. This was also fixed in
964 # which already has the 'clrevorder' logic. This was also fixed in
966 # cc0ff93d0c0c.
965 # cc0ff93d0c0c.
967
966
968 # Treemanifests don't work correctly with fastpathlinkrev
967 # Treemanifests don't work correctly with fastpathlinkrev
969 # either, because we don't discover which directory nodes to
968 # either, because we don't discover which directory nodes to
970 # send along with files. This could probably be fixed.
969 # send along with files. This could probably be fixed.
971 fastpathlinkrev = fastpathlinkrev and not scmutil.istreemanifest(repo)
970 fastpathlinkrev = fastpathlinkrev and not scmutil.istreemanifest(repo)
972
971
973 fnodes = {} # needed file nodes
972 fnodes = {} # needed file nodes
974
973
975 size = 0
974 size = 0
976 it = self.generatemanifests(
975 it = self.generatemanifests(
977 commonrevs,
976 commonrevs,
978 clrevorder,
977 clrevorder,
979 fastpathlinkrev,
978 fastpathlinkrev,
980 manifests,
979 manifests,
981 fnodes,
980 fnodes,
982 source,
981 source,
983 clstate[b'clrevtomanifestrev'],
982 clstate[b'clrevtomanifestrev'],
984 )
983 )
985
984
986 for tree, deltas in it:
985 for tree, deltas in it:
987 if tree:
986 if tree:
988 assert self.version == b'03'
987 assert self.version == b'03'
989 chunk = _fileheader(tree)
988 chunk = _fileheader(tree)
990 size += len(chunk)
989 size += len(chunk)
991 yield chunk
990 yield chunk
992
991
993 for delta in deltas:
992 for delta in deltas:
994 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
993 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
995 for chunk in chunks:
994 for chunk in chunks:
996 size += len(chunk)
995 size += len(chunk)
997 yield chunk
996 yield chunk
998
997
999 close = closechunk()
998 close = closechunk()
1000 size += len(close)
999 size += len(close)
1001 yield close
1000 yield close
1002
1001
1003 self._verbosenote(_(b'%8.i (manifests)\n') % size)
1002 self._verbosenote(_(b'%8.i (manifests)\n') % size)
1004 yield self._manifestsend
1003 yield self._manifestsend
1005
1004
1006 mfdicts = None
1005 mfdicts = None
1007 if self._ellipses and self._isshallow:
1006 if self._ellipses and self._isshallow:
1008 mfdicts = [
1007 mfdicts = [
1009 (self._repo.manifestlog[n].read(), lr)
1008 (self._repo.manifestlog[n].read(), lr)
1010 for (n, lr) in pycompat.iteritems(manifests)
1009 for (n, lr) in pycompat.iteritems(manifests)
1011 ]
1010 ]
1012
1011
1013 manifests.clear()
1012 manifests.clear()
1014 clrevs = {cl.rev(x) for x in clnodes}
1013 clrevs = {cl.rev(x) for x in clnodes}
1015
1014
1016 it = self.generatefiles(
1015 it = self.generatefiles(
1017 changedfiles,
1016 changedfiles,
1018 commonrevs,
1017 commonrevs,
1019 source,
1018 source,
1020 mfdicts,
1019 mfdicts,
1021 fastpathlinkrev,
1020 fastpathlinkrev,
1022 fnodes,
1021 fnodes,
1023 clrevs,
1022 clrevs,
1024 )
1023 )
1025
1024
1026 for path, deltas in it:
1025 for path, deltas in it:
1027 h = _fileheader(path)
1026 h = _fileheader(path)
1028 size = len(h)
1027 size = len(h)
1029 yield h
1028 yield h
1030
1029
1031 for delta in deltas:
1030 for delta in deltas:
1032 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
1031 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
1033 for chunk in chunks:
1032 for chunk in chunks:
1034 size += len(chunk)
1033 size += len(chunk)
1035 yield chunk
1034 yield chunk
1036
1035
1037 close = closechunk()
1036 close = closechunk()
1038 size += len(close)
1037 size += len(close)
1039 yield close
1038 yield close
1040
1039
1041 self._verbosenote(_(b'%8.i %s\n') % (size, path))
1040 self._verbosenote(_(b'%8.i %s\n') % (size, path))
1042
1041
1043 yield closechunk()
1042 yield closechunk()
1044
1043
1045 if clnodes:
1044 if clnodes:
1046 repo.hook(b'outgoing', node=hex(clnodes[0]), source=source)
1045 repo.hook(b'outgoing', node=hex(clnodes[0]), source=source)
1047
1046
1048 def _generatechangelog(self, cl, nodes, generate=True):
1047 def _generatechangelog(self, cl, nodes, generate=True):
1049 """Generate data for changelog chunks.
1048 """Generate data for changelog chunks.
1050
1049
1051 Returns a 2-tuple of a dict containing state and an iterable of
1050 Returns a 2-tuple of a dict containing state and an iterable of
1052 byte chunks. The state will not be fully populated until the
1051 byte chunks. The state will not be fully populated until the
1053 chunk stream has been fully consumed.
1052 chunk stream has been fully consumed.
1054
1053
1055 if generate is False, the state will be fully populated and no chunk
1054 if generate is False, the state will be fully populated and no chunk
1056 stream will be yielded
1055 stream will be yielded
1057 """
1056 """
1058 clrevorder = {}
1057 clrevorder = {}
1059 manifests = {}
1058 manifests = {}
1060 mfl = self._repo.manifestlog
1059 mfl = self._repo.manifestlog
1061 changedfiles = set()
1060 changedfiles = set()
1062 clrevtomanifestrev = {}
1061 clrevtomanifestrev = {}
1063
1062
1064 state = {
1063 state = {
1065 b'clrevorder': clrevorder,
1064 b'clrevorder': clrevorder,
1066 b'manifests': manifests,
1065 b'manifests': manifests,
1067 b'changedfiles': changedfiles,
1066 b'changedfiles': changedfiles,
1068 b'clrevtomanifestrev': clrevtomanifestrev,
1067 b'clrevtomanifestrev': clrevtomanifestrev,
1069 }
1068 }
1070
1069
1071 if not (generate or self._ellipses):
1070 if not (generate or self._ellipses):
1072 # sort the nodes in storage order
1071 # sort the nodes in storage order
1073 nodes = sorted(nodes, key=cl.rev)
1072 nodes = sorted(nodes, key=cl.rev)
1074 for node in nodes:
1073 for node in nodes:
1075 c = cl.changelogrevision(node)
1074 c = cl.changelogrevision(node)
1076 clrevorder[node] = len(clrevorder)
1075 clrevorder[node] = len(clrevorder)
1077 # record the first changeset introducing this manifest version
1076 # record the first changeset introducing this manifest version
1078 manifests.setdefault(c.manifest, node)
1077 manifests.setdefault(c.manifest, node)
1079 # Record a complete list of potentially-changed files in
1078 # Record a complete list of potentially-changed files in
1080 # this manifest.
1079 # this manifest.
1081 changedfiles.update(c.files)
1080 changedfiles.update(c.files)
1082
1081
1083 return state, ()
1082 return state, ()
1084
1083
1085 # Callback for the changelog, used to collect changed files and
1084 # Callback for the changelog, used to collect changed files and
1086 # manifest nodes.
1085 # manifest nodes.
1087 # Returns the linkrev node (identity in the changelog case).
1086 # Returns the linkrev node (identity in the changelog case).
1088 def lookupcl(x):
1087 def lookupcl(x):
1089 c = cl.changelogrevision(x)
1088 c = cl.changelogrevision(x)
1090 clrevorder[x] = len(clrevorder)
1089 clrevorder[x] = len(clrevorder)
1091
1090
1092 if self._ellipses:
1091 if self._ellipses:
1093 # Only update manifests if x is going to be sent. Otherwise we
1092 # Only update manifests if x is going to be sent. Otherwise we
1094 # end up with bogus linkrevs specified for manifests and
1093 # end up with bogus linkrevs specified for manifests and
1095 # we skip some manifest nodes that we should otherwise
1094 # we skip some manifest nodes that we should otherwise
1096 # have sent.
1095 # have sent.
1097 if (
1096 if (
1098 x in self._fullclnodes
1097 x in self._fullclnodes
1099 or cl.rev(x) in self._precomputedellipsis
1098 or cl.rev(x) in self._precomputedellipsis
1100 ):
1099 ):
1101
1100
1102 manifestnode = c.manifest
1101 manifestnode = c.manifest
1103 # Record the first changeset introducing this manifest
1102 # Record the first changeset introducing this manifest
1104 # version.
1103 # version.
1105 manifests.setdefault(manifestnode, x)
1104 manifests.setdefault(manifestnode, x)
1106 # Set this narrow-specific dict so we have the lowest
1105 # Set this narrow-specific dict so we have the lowest
1107 # manifest revnum to look up for this cl revnum. (Part of
1106 # manifest revnum to look up for this cl revnum. (Part of
1108 # mapping changelog ellipsis parents to manifest ellipsis
1107 # mapping changelog ellipsis parents to manifest ellipsis
1109 # parents)
1108 # parents)
1110 clrevtomanifestrev.setdefault(
1109 clrevtomanifestrev.setdefault(
1111 cl.rev(x), mfl.rev(manifestnode)
1110 cl.rev(x), mfl.rev(manifestnode)
1112 )
1111 )
1113 # We can't trust the changed files list in the changeset if the
1112 # We can't trust the changed files list in the changeset if the
1114 # client requested a shallow clone.
1113 # client requested a shallow clone.
1115 if self._isshallow:
1114 if self._isshallow:
1116 changedfiles.update(mfl[c.manifest].read().keys())
1115 changedfiles.update(mfl[c.manifest].read().keys())
1117 else:
1116 else:
1118 changedfiles.update(c.files)
1117 changedfiles.update(c.files)
1119 else:
1118 else:
1120 # record the first changeset introducing this manifest version
1119 # record the first changeset introducing this manifest version
1121 manifests.setdefault(c.manifest, x)
1120 manifests.setdefault(c.manifest, x)
1122 # Record a complete list of potentially-changed files in
1121 # Record a complete list of potentially-changed files in
1123 # this manifest.
1122 # this manifest.
1124 changedfiles.update(c.files)
1123 changedfiles.update(c.files)
1125
1124
1126 return x
1125 return x
1127
1126
1128 gen = deltagroup(
1127 gen = deltagroup(
1129 self._repo,
1128 self._repo,
1130 cl,
1129 cl,
1131 nodes,
1130 nodes,
1132 True,
1131 True,
1133 lookupcl,
1132 lookupcl,
1134 self._forcedeltaparentprev,
1133 self._forcedeltaparentprev,
1135 ellipses=self._ellipses,
1134 ellipses=self._ellipses,
1136 topic=_(b'changesets'),
1135 topic=_(b'changesets'),
1137 clrevtolocalrev={},
1136 clrevtolocalrev={},
1138 fullclnodes=self._fullclnodes,
1137 fullclnodes=self._fullclnodes,
1139 precomputedellipsis=self._precomputedellipsis,
1138 precomputedellipsis=self._precomputedellipsis,
1140 )
1139 )
1141
1140
1142 return state, gen
1141 return state, gen
1143
1142
1144 def generatemanifests(
1143 def generatemanifests(
1145 self,
1144 self,
1146 commonrevs,
1145 commonrevs,
1147 clrevorder,
1146 clrevorder,
1148 fastpathlinkrev,
1147 fastpathlinkrev,
1149 manifests,
1148 manifests,
1150 fnodes,
1149 fnodes,
1151 source,
1150 source,
1152 clrevtolocalrev,
1151 clrevtolocalrev,
1153 ):
1152 ):
1154 """Returns an iterator of changegroup chunks containing manifests.
1153 """Returns an iterator of changegroup chunks containing manifests.
1155
1154
1156 `source` is unused here, but is used by extensions like remotefilelog to
1155 `source` is unused here, but is used by extensions like remotefilelog to
1157 change what is sent based in pulls vs pushes, etc.
1156 change what is sent based in pulls vs pushes, etc.
1158 """
1157 """
1159 repo = self._repo
1158 repo = self._repo
1160 mfl = repo.manifestlog
1159 mfl = repo.manifestlog
1161 tmfnodes = {b'': manifests}
1160 tmfnodes = {b'': manifests}
1162
1161
1163 # Callback for the manifest, used to collect linkrevs for filelog
1162 # Callback for the manifest, used to collect linkrevs for filelog
1164 # revisions.
1163 # revisions.
1165 # Returns the linkrev node (collected in lookupcl).
1164 # Returns the linkrev node (collected in lookupcl).
1166 def makelookupmflinknode(tree, nodes):
1165 def makelookupmflinknode(tree, nodes):
1167 if fastpathlinkrev:
1166 if fastpathlinkrev:
1168 assert not tree
1167 assert not tree
1169 return (
1168 return (
1170 manifests.__getitem__
1169 manifests.__getitem__
1171 ) # pytype: disable=unsupported-operands
1170 ) # pytype: disable=unsupported-operands
1172
1171
1173 def lookupmflinknode(x):
1172 def lookupmflinknode(x):
1174 """Callback for looking up the linknode for manifests.
1173 """Callback for looking up the linknode for manifests.
1175
1174
1176 Returns the linkrev node for the specified manifest.
1175 Returns the linkrev node for the specified manifest.
1177
1176
1178 SIDE EFFECT:
1177 SIDE EFFECT:
1179
1178
1180 1) fclnodes gets populated with the list of relevant
1179 1) fclnodes gets populated with the list of relevant
1181 file nodes if we're not using fastpathlinkrev
1180 file nodes if we're not using fastpathlinkrev
1182 2) When treemanifests are in use, collects treemanifest nodes
1181 2) When treemanifests are in use, collects treemanifest nodes
1183 to send
1182 to send
1184
1183
1185 Note that this means manifests must be completely sent to
1184 Note that this means manifests must be completely sent to
1186 the client before you can trust the list of files and
1185 the client before you can trust the list of files and
1187 treemanifests to send.
1186 treemanifests to send.
1188 """
1187 """
1189 clnode = nodes[x]
1188 clnode = nodes[x]
1190 mdata = mfl.get(tree, x).readfast(shallow=True)
1189 mdata = mfl.get(tree, x).readfast(shallow=True)
1191 for p, n, fl in mdata.iterentries():
1190 for p, n, fl in mdata.iterentries():
1192 if fl == b't': # subdirectory manifest
1191 if fl == b't': # subdirectory manifest
1193 subtree = tree + p + b'/'
1192 subtree = tree + p + b'/'
1194 tmfclnodes = tmfnodes.setdefault(subtree, {})
1193 tmfclnodes = tmfnodes.setdefault(subtree, {})
1195 tmfclnode = tmfclnodes.setdefault(n, clnode)
1194 tmfclnode = tmfclnodes.setdefault(n, clnode)
1196 if clrevorder[clnode] < clrevorder[tmfclnode]:
1195 if clrevorder[clnode] < clrevorder[tmfclnode]:
1197 tmfclnodes[n] = clnode
1196 tmfclnodes[n] = clnode
1198 else:
1197 else:
1199 f = tree + p
1198 f = tree + p
1200 fclnodes = fnodes.setdefault(f, {})
1199 fclnodes = fnodes.setdefault(f, {})
1201 fclnode = fclnodes.setdefault(n, clnode)
1200 fclnode = fclnodes.setdefault(n, clnode)
1202 if clrevorder[clnode] < clrevorder[fclnode]:
1201 if clrevorder[clnode] < clrevorder[fclnode]:
1203 fclnodes[n] = clnode
1202 fclnodes[n] = clnode
1204 return clnode
1203 return clnode
1205
1204
1206 return lookupmflinknode
1205 return lookupmflinknode
1207
1206
1208 while tmfnodes:
1207 while tmfnodes:
1209 tree, nodes = tmfnodes.popitem()
1208 tree, nodes = tmfnodes.popitem()
1210
1209
1211 should_visit = self._matcher.visitdir(tree[:-1])
1210 should_visit = self._matcher.visitdir(tree[:-1])
1212 if tree and not should_visit:
1211 if tree and not should_visit:
1213 continue
1212 continue
1214
1213
1215 store = mfl.getstorage(tree)
1214 store = mfl.getstorage(tree)
1216
1215
1217 if not should_visit:
1216 if not should_visit:
1218 # No nodes to send because this directory is out of
1217 # No nodes to send because this directory is out of
1219 # the client's view of the repository (probably
1218 # the client's view of the repository (probably
1220 # because of narrow clones). Do this even for the root
1219 # because of narrow clones). Do this even for the root
1221 # directory (tree=='')
1220 # directory (tree=='')
1222 prunednodes = []
1221 prunednodes = []
1223 else:
1222 else:
1224 # Avoid sending any manifest nodes we can prove the
1223 # Avoid sending any manifest nodes we can prove the
1225 # client already has by checking linkrevs. See the
1224 # client already has by checking linkrevs. See the
1226 # related comment in generatefiles().
1225 # related comment in generatefiles().
1227 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1226 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1228
1227
1229 if tree and not prunednodes:
1228 if tree and not prunednodes:
1230 continue
1229 continue
1231
1230
1232 lookupfn = makelookupmflinknode(tree, nodes)
1231 lookupfn = makelookupmflinknode(tree, nodes)
1233
1232
1234 deltas = deltagroup(
1233 deltas = deltagroup(
1235 self._repo,
1234 self._repo,
1236 store,
1235 store,
1237 prunednodes,
1236 prunednodes,
1238 False,
1237 False,
1239 lookupfn,
1238 lookupfn,
1240 self._forcedeltaparentprev,
1239 self._forcedeltaparentprev,
1241 ellipses=self._ellipses,
1240 ellipses=self._ellipses,
1242 topic=_(b'manifests'),
1241 topic=_(b'manifests'),
1243 clrevtolocalrev=clrevtolocalrev,
1242 clrevtolocalrev=clrevtolocalrev,
1244 fullclnodes=self._fullclnodes,
1243 fullclnodes=self._fullclnodes,
1245 precomputedellipsis=self._precomputedellipsis,
1244 precomputedellipsis=self._precomputedellipsis,
1246 )
1245 )
1247
1246
1248 if not self._oldmatcher.visitdir(store.tree[:-1]):
1247 if not self._oldmatcher.visitdir(store.tree[:-1]):
1249 yield tree, deltas
1248 yield tree, deltas
1250 else:
1249 else:
1251 # 'deltas' is a generator and we need to consume it even if
1250 # 'deltas' is a generator and we need to consume it even if
1252 # we are not going to send it because a side-effect is that
1251 # we are not going to send it because a side-effect is that
1253 # it updates tmdnodes (via lookupfn)
1252 # it updates tmdnodes (via lookupfn)
1254 for d in deltas:
1253 for d in deltas:
1255 pass
1254 pass
1256 if not tree:
1255 if not tree:
1257 yield tree, []
1256 yield tree, []
1258
1257
1259 def _prunemanifests(self, store, nodes, commonrevs):
1258 def _prunemanifests(self, store, nodes, commonrevs):
1260 if not self._ellipses:
1259 if not self._ellipses:
1261 # In non-ellipses case and large repositories, it is better to
1260 # In non-ellipses case and large repositories, it is better to
1262 # prevent calling of store.rev and store.linkrev on a lot of
1261 # prevent calling of store.rev and store.linkrev on a lot of
1263 # nodes as compared to sending some extra data
1262 # nodes as compared to sending some extra data
1264 return nodes.copy()
1263 return nodes.copy()
1265 # This is split out as a separate method to allow filtering
1264 # This is split out as a separate method to allow filtering
1266 # commonrevs in extension code.
1265 # commonrevs in extension code.
1267 #
1266 #
1268 # TODO(augie): this shouldn't be required, instead we should
1267 # TODO(augie): this shouldn't be required, instead we should
1269 # make filtering of revisions to send delegated to the store
1268 # make filtering of revisions to send delegated to the store
1270 # layer.
1269 # layer.
1271 frev, flr = store.rev, store.linkrev
1270 frev, flr = store.rev, store.linkrev
1272 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1271 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1273
1272
1274 # The 'source' parameter is useful for extensions
1273 # The 'source' parameter is useful for extensions
1275 def generatefiles(
1274 def generatefiles(
1276 self,
1275 self,
1277 changedfiles,
1276 changedfiles,
1278 commonrevs,
1277 commonrevs,
1279 source,
1278 source,
1280 mfdicts,
1279 mfdicts,
1281 fastpathlinkrev,
1280 fastpathlinkrev,
1282 fnodes,
1281 fnodes,
1283 clrevs,
1282 clrevs,
1284 ):
1283 ):
1285 changedfiles = [
1284 changedfiles = [
1286 f
1285 f
1287 for f in changedfiles
1286 for f in changedfiles
1288 if self._matcher(f) and not self._oldmatcher(f)
1287 if self._matcher(f) and not self._oldmatcher(f)
1289 ]
1288 ]
1290
1289
1291 if not fastpathlinkrev:
1290 if not fastpathlinkrev:
1292
1291
1293 def normallinknodes(unused, fname):
1292 def normallinknodes(unused, fname):
1294 return fnodes.get(fname, {})
1293 return fnodes.get(fname, {})
1295
1294
1296 else:
1295 else:
1297 cln = self._repo.changelog.node
1296 cln = self._repo.changelog.node
1298
1297
1299 def normallinknodes(store, fname):
1298 def normallinknodes(store, fname):
1300 flinkrev = store.linkrev
1299 flinkrev = store.linkrev
1301 fnode = store.node
1300 fnode = store.node
1302 revs = ((r, flinkrev(r)) for r in store)
1301 revs = ((r, flinkrev(r)) for r in store)
1303 return {fnode(r): cln(lr) for r, lr in revs if lr in clrevs}
1302 return {fnode(r): cln(lr) for r, lr in revs if lr in clrevs}
1304
1303
1305 clrevtolocalrev = {}
1304 clrevtolocalrev = {}
1306
1305
1307 if self._isshallow:
1306 if self._isshallow:
1308 # In a shallow clone, the linknodes callback needs to also include
1307 # In a shallow clone, the linknodes callback needs to also include
1309 # those file nodes that are in the manifests we sent but weren't
1308 # those file nodes that are in the manifests we sent but weren't
1310 # introduced by those manifests.
1309 # introduced by those manifests.
1311 commonctxs = [self._repo[c] for c in commonrevs]
1310 commonctxs = [self._repo[c] for c in commonrevs]
1312 clrev = self._repo.changelog.rev
1311 clrev = self._repo.changelog.rev
1313
1312
1314 def linknodes(flog, fname):
1313 def linknodes(flog, fname):
1315 for c in commonctxs:
1314 for c in commonctxs:
1316 try:
1315 try:
1317 fnode = c.filenode(fname)
1316 fnode = c.filenode(fname)
1318 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1317 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1319 except error.ManifestLookupError:
1318 except error.ManifestLookupError:
1320 pass
1319 pass
1321 links = normallinknodes(flog, fname)
1320 links = normallinknodes(flog, fname)
1322 if len(links) != len(mfdicts):
1321 if len(links) != len(mfdicts):
1323 for mf, lr in mfdicts:
1322 for mf, lr in mfdicts:
1324 fnode = mf.get(fname, None)
1323 fnode = mf.get(fname, None)
1325 if fnode in links:
1324 if fnode in links:
1326 links[fnode] = min(links[fnode], lr, key=clrev)
1325 links[fnode] = min(links[fnode], lr, key=clrev)
1327 elif fnode:
1326 elif fnode:
1328 links[fnode] = lr
1327 links[fnode] = lr
1329 return links
1328 return links
1330
1329
1331 else:
1330 else:
1332 linknodes = normallinknodes
1331 linknodes = normallinknodes
1333
1332
1334 repo = self._repo
1333 repo = self._repo
1335 progress = repo.ui.makeprogress(
1334 progress = repo.ui.makeprogress(
1336 _(b'files'), unit=_(b'files'), total=len(changedfiles)
1335 _(b'files'), unit=_(b'files'), total=len(changedfiles)
1337 )
1336 )
1338 for i, fname in enumerate(sorted(changedfiles)):
1337 for i, fname in enumerate(sorted(changedfiles)):
1339 filerevlog = repo.file(fname)
1338 filerevlog = repo.file(fname)
1340 if not filerevlog:
1339 if not filerevlog:
1341 raise error.Abort(
1340 raise error.Abort(
1342 _(b"empty or missing file data for %s") % fname
1341 _(b"empty or missing file data for %s") % fname
1343 )
1342 )
1344
1343
1345 clrevtolocalrev.clear()
1344 clrevtolocalrev.clear()
1346
1345
1347 linkrevnodes = linknodes(filerevlog, fname)
1346 linkrevnodes = linknodes(filerevlog, fname)
1348 # Lookup for filenodes, we collected the linkrev nodes above in the
1347 # Lookup for filenodes, we collected the linkrev nodes above in the
1349 # fastpath case and with lookupmf in the slowpath case.
1348 # fastpath case and with lookupmf in the slowpath case.
1350 def lookupfilelog(x):
1349 def lookupfilelog(x):
1351 return linkrevnodes[x]
1350 return linkrevnodes[x]
1352
1351
1353 frev, flr = filerevlog.rev, filerevlog.linkrev
1352 frev, flr = filerevlog.rev, filerevlog.linkrev
1354 # Skip sending any filenode we know the client already
1353 # Skip sending any filenode we know the client already
1355 # has. This avoids over-sending files relatively
1354 # has. This avoids over-sending files relatively
1356 # inexpensively, so it's not a problem if we under-filter
1355 # inexpensively, so it's not a problem if we under-filter
1357 # here.
1356 # here.
1358 filenodes = [
1357 filenodes = [
1359 n for n in linkrevnodes if flr(frev(n)) not in commonrevs
1358 n for n in linkrevnodes if flr(frev(n)) not in commonrevs
1360 ]
1359 ]
1361
1360
1362 if not filenodes:
1361 if not filenodes:
1363 continue
1362 continue
1364
1363
1365 progress.update(i + 1, item=fname)
1364 progress.update(i + 1, item=fname)
1366
1365
1367 deltas = deltagroup(
1366 deltas = deltagroup(
1368 self._repo,
1367 self._repo,
1369 filerevlog,
1368 filerevlog,
1370 filenodes,
1369 filenodes,
1371 False,
1370 False,
1372 lookupfilelog,
1371 lookupfilelog,
1373 self._forcedeltaparentprev,
1372 self._forcedeltaparentprev,
1374 ellipses=self._ellipses,
1373 ellipses=self._ellipses,
1375 clrevtolocalrev=clrevtolocalrev,
1374 clrevtolocalrev=clrevtolocalrev,
1376 fullclnodes=self._fullclnodes,
1375 fullclnodes=self._fullclnodes,
1377 precomputedellipsis=self._precomputedellipsis,
1376 precomputedellipsis=self._precomputedellipsis,
1378 )
1377 )
1379
1378
1380 yield fname, deltas
1379 yield fname, deltas
1381
1380
1382 progress.complete()
1381 progress.complete()
1383
1382
1384
1383
1385 def _makecg1packer(
1384 def _makecg1packer(
1386 repo,
1385 repo,
1387 oldmatcher,
1386 oldmatcher,
1388 matcher,
1387 matcher,
1389 bundlecaps,
1388 bundlecaps,
1390 ellipses=False,
1389 ellipses=False,
1391 shallow=False,
1390 shallow=False,
1392 ellipsisroots=None,
1391 ellipsisroots=None,
1393 fullnodes=None,
1392 fullnodes=None,
1394 ):
1393 ):
1395 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1394 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1396 d.node, d.p1node, d.p2node, d.linknode
1395 d.node, d.p1node, d.p2node, d.linknode
1397 )
1396 )
1398
1397
1399 return cgpacker(
1398 return cgpacker(
1400 repo,
1399 repo,
1401 oldmatcher,
1400 oldmatcher,
1402 matcher,
1401 matcher,
1403 b'01',
1402 b'01',
1404 builddeltaheader=builddeltaheader,
1403 builddeltaheader=builddeltaheader,
1405 manifestsend=b'',
1404 manifestsend=b'',
1406 forcedeltaparentprev=True,
1405 forcedeltaparentprev=True,
1407 bundlecaps=bundlecaps,
1406 bundlecaps=bundlecaps,
1408 ellipses=ellipses,
1407 ellipses=ellipses,
1409 shallow=shallow,
1408 shallow=shallow,
1410 ellipsisroots=ellipsisroots,
1409 ellipsisroots=ellipsisroots,
1411 fullnodes=fullnodes,
1410 fullnodes=fullnodes,
1412 )
1411 )
1413
1412
1414
1413
1415 def _makecg2packer(
1414 def _makecg2packer(
1416 repo,
1415 repo,
1417 oldmatcher,
1416 oldmatcher,
1418 matcher,
1417 matcher,
1419 bundlecaps,
1418 bundlecaps,
1420 ellipses=False,
1419 ellipses=False,
1421 shallow=False,
1420 shallow=False,
1422 ellipsisroots=None,
1421 ellipsisroots=None,
1423 fullnodes=None,
1422 fullnodes=None,
1424 ):
1423 ):
1425 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1424 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1426 d.node, d.p1node, d.p2node, d.basenode, d.linknode
1425 d.node, d.p1node, d.p2node, d.basenode, d.linknode
1427 )
1426 )
1428
1427
1429 return cgpacker(
1428 return cgpacker(
1430 repo,
1429 repo,
1431 oldmatcher,
1430 oldmatcher,
1432 matcher,
1431 matcher,
1433 b'02',
1432 b'02',
1434 builddeltaheader=builddeltaheader,
1433 builddeltaheader=builddeltaheader,
1435 manifestsend=b'',
1434 manifestsend=b'',
1436 bundlecaps=bundlecaps,
1435 bundlecaps=bundlecaps,
1437 ellipses=ellipses,
1436 ellipses=ellipses,
1438 shallow=shallow,
1437 shallow=shallow,
1439 ellipsisroots=ellipsisroots,
1438 ellipsisroots=ellipsisroots,
1440 fullnodes=fullnodes,
1439 fullnodes=fullnodes,
1441 )
1440 )
1442
1441
1443
1442
1444 def _makecg3packer(
1443 def _makecg3packer(
1445 repo,
1444 repo,
1446 oldmatcher,
1445 oldmatcher,
1447 matcher,
1446 matcher,
1448 bundlecaps,
1447 bundlecaps,
1449 ellipses=False,
1448 ellipses=False,
1450 shallow=False,
1449 shallow=False,
1451 ellipsisroots=None,
1450 ellipsisroots=None,
1452 fullnodes=None,
1451 fullnodes=None,
1453 ):
1452 ):
1454 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1453 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1455 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags
1454 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags
1456 )
1455 )
1457
1456
1458 return cgpacker(
1457 return cgpacker(
1459 repo,
1458 repo,
1460 oldmatcher,
1459 oldmatcher,
1461 matcher,
1460 matcher,
1462 b'03',
1461 b'03',
1463 builddeltaheader=builddeltaheader,
1462 builddeltaheader=builddeltaheader,
1464 manifestsend=closechunk(),
1463 manifestsend=closechunk(),
1465 bundlecaps=bundlecaps,
1464 bundlecaps=bundlecaps,
1466 ellipses=ellipses,
1465 ellipses=ellipses,
1467 shallow=shallow,
1466 shallow=shallow,
1468 ellipsisroots=ellipsisroots,
1467 ellipsisroots=ellipsisroots,
1469 fullnodes=fullnodes,
1468 fullnodes=fullnodes,
1470 )
1469 )
1471
1470
1472
1471
1473 _packermap = {
1472 _packermap = {
1474 b'01': (_makecg1packer, cg1unpacker),
1473 b'01': (_makecg1packer, cg1unpacker),
1475 # cg2 adds support for exchanging generaldelta
1474 # cg2 adds support for exchanging generaldelta
1476 b'02': (_makecg2packer, cg2unpacker),
1475 b'02': (_makecg2packer, cg2unpacker),
1477 # cg3 adds support for exchanging revlog flags and treemanifests
1476 # cg3 adds support for exchanging revlog flags and treemanifests
1478 b'03': (_makecg3packer, cg3unpacker),
1477 b'03': (_makecg3packer, cg3unpacker),
1479 }
1478 }
1480
1479
1481
1480
1482 def allsupportedversions(repo):
1481 def allsupportedversions(repo):
1483 versions = set(_packermap.keys())
1482 versions = set(_packermap.keys())
1484 needv03 = False
1483 needv03 = False
1485 if (
1484 if (
1486 repo.ui.configbool(b'experimental', b'changegroup3')
1485 repo.ui.configbool(b'experimental', b'changegroup3')
1487 or repo.ui.configbool(b'experimental', b'treemanifest')
1486 or repo.ui.configbool(b'experimental', b'treemanifest')
1488 or scmutil.istreemanifest(repo)
1487 or scmutil.istreemanifest(repo)
1489 ):
1488 ):
1490 # we keep version 03 because we need to to exchange treemanifest data
1489 # we keep version 03 because we need to to exchange treemanifest data
1491 #
1490 #
1492 # we also keep vresion 01 and 02, because it is possible for repo to
1491 # we also keep vresion 01 and 02, because it is possible for repo to
1493 # contains both normal and tree manifest at the same time. so using
1492 # contains both normal and tree manifest at the same time. so using
1494 # older version to pull data is viable
1493 # older version to pull data is viable
1495 #
1494 #
1496 # (or even to push subset of history)
1495 # (or even to push subset of history)
1497 needv03 = True
1496 needv03 = True
1498 if b'exp-sidedata-flag' in repo.requirements:
1497 if b'exp-sidedata-flag' in repo.requirements:
1499 needv03 = True
1498 needv03 = True
1500 # don't attempt to use 01/02 until we do sidedata cleaning
1499 # don't attempt to use 01/02 until we do sidedata cleaning
1501 versions.discard(b'01')
1500 versions.discard(b'01')
1502 versions.discard(b'02')
1501 versions.discard(b'02')
1503 if not needv03:
1502 if not needv03:
1504 versions.discard(b'03')
1503 versions.discard(b'03')
1505 return versions
1504 return versions
1506
1505
1507
1506
1508 # Changegroup versions that can be applied to the repo
1507 # Changegroup versions that can be applied to the repo
1509 def supportedincomingversions(repo):
1508 def supportedincomingversions(repo):
1510 return allsupportedversions(repo)
1509 return allsupportedversions(repo)
1511
1510
1512
1511
1513 # Changegroup versions that can be created from the repo
1512 # Changegroup versions that can be created from the repo
1514 def supportedoutgoingversions(repo):
1513 def supportedoutgoingversions(repo):
1515 versions = allsupportedversions(repo)
1514 versions = allsupportedversions(repo)
1516 if scmutil.istreemanifest(repo):
1515 if scmutil.istreemanifest(repo):
1517 # Versions 01 and 02 support only flat manifests and it's just too
1516 # Versions 01 and 02 support only flat manifests and it's just too
1518 # expensive to convert between the flat manifest and tree manifest on
1517 # expensive to convert between the flat manifest and tree manifest on
1519 # the fly. Since tree manifests are hashed differently, all of history
1518 # the fly. Since tree manifests are hashed differently, all of history
1520 # would have to be converted. Instead, we simply don't even pretend to
1519 # would have to be converted. Instead, we simply don't even pretend to
1521 # support versions 01 and 02.
1520 # support versions 01 and 02.
1522 versions.discard(b'01')
1521 versions.discard(b'01')
1523 versions.discard(b'02')
1522 versions.discard(b'02')
1524 if requirements.NARROW_REQUIREMENT in repo.requirements:
1523 if requirements.NARROW_REQUIREMENT in repo.requirements:
1525 # Versions 01 and 02 don't support revlog flags, and we need to
1524 # Versions 01 and 02 don't support revlog flags, and we need to
1526 # support that for stripping and unbundling to work.
1525 # support that for stripping and unbundling to work.
1527 versions.discard(b'01')
1526 versions.discard(b'01')
1528 versions.discard(b'02')
1527 versions.discard(b'02')
1529 if LFS_REQUIREMENT in repo.requirements:
1528 if LFS_REQUIREMENT in repo.requirements:
1530 # Versions 01 and 02 don't support revlog flags, and we need to
1529 # Versions 01 and 02 don't support revlog flags, and we need to
1531 # mark LFS entries with REVIDX_EXTSTORED.
1530 # mark LFS entries with REVIDX_EXTSTORED.
1532 versions.discard(b'01')
1531 versions.discard(b'01')
1533 versions.discard(b'02')
1532 versions.discard(b'02')
1534
1533
1535 return versions
1534 return versions
1536
1535
1537
1536
1538 def localversion(repo):
1537 def localversion(repo):
1539 # Finds the best version to use for bundles that are meant to be used
1538 # Finds the best version to use for bundles that are meant to be used
1540 # locally, such as those from strip and shelve, and temporary bundles.
1539 # locally, such as those from strip and shelve, and temporary bundles.
1541 return max(supportedoutgoingversions(repo))
1540 return max(supportedoutgoingversions(repo))
1542
1541
1543
1542
1544 def safeversion(repo):
1543 def safeversion(repo):
1545 # Finds the smallest version that it's safe to assume clients of the repo
1544 # Finds the smallest version that it's safe to assume clients of the repo
1546 # will support. For example, all hg versions that support generaldelta also
1545 # will support. For example, all hg versions that support generaldelta also
1547 # support changegroup 02.
1546 # support changegroup 02.
1548 versions = supportedoutgoingversions(repo)
1547 versions = supportedoutgoingversions(repo)
1549 if b'generaldelta' in repo.requirements:
1548 if b'generaldelta' in repo.requirements:
1550 versions.discard(b'01')
1549 versions.discard(b'01')
1551 assert versions
1550 assert versions
1552 return min(versions)
1551 return min(versions)
1553
1552
1554
1553
1555 def getbundler(
1554 def getbundler(
1556 version,
1555 version,
1557 repo,
1556 repo,
1558 bundlecaps=None,
1557 bundlecaps=None,
1559 oldmatcher=None,
1558 oldmatcher=None,
1560 matcher=None,
1559 matcher=None,
1561 ellipses=False,
1560 ellipses=False,
1562 shallow=False,
1561 shallow=False,
1563 ellipsisroots=None,
1562 ellipsisroots=None,
1564 fullnodes=None,
1563 fullnodes=None,
1565 ):
1564 ):
1566 assert version in supportedoutgoingversions(repo)
1565 assert version in supportedoutgoingversions(repo)
1567
1566
1568 if matcher is None:
1567 if matcher is None:
1569 matcher = matchmod.always()
1568 matcher = matchmod.always()
1570 if oldmatcher is None:
1569 if oldmatcher is None:
1571 oldmatcher = matchmod.never()
1570 oldmatcher = matchmod.never()
1572
1571
1573 if version == b'01' and not matcher.always():
1572 if version == b'01' and not matcher.always():
1574 raise error.ProgrammingError(
1573 raise error.ProgrammingError(
1575 b'version 01 changegroups do not support sparse file matchers'
1574 b'version 01 changegroups do not support sparse file matchers'
1576 )
1575 )
1577
1576
1578 if ellipses and version in (b'01', b'02'):
1577 if ellipses and version in (b'01', b'02'):
1579 raise error.Abort(
1578 raise error.Abort(
1580 _(
1579 _(
1581 b'ellipsis nodes require at least cg3 on client and server, '
1580 b'ellipsis nodes require at least cg3 on client and server, '
1582 b'but negotiated version %s'
1581 b'but negotiated version %s'
1583 )
1582 )
1584 % version
1583 % version
1585 )
1584 )
1586
1585
1587 # Requested files could include files not in the local store. So
1586 # Requested files could include files not in the local store. So
1588 # filter those out.
1587 # filter those out.
1589 matcher = repo.narrowmatch(matcher)
1588 matcher = repo.narrowmatch(matcher)
1590
1589
1591 fn = _packermap[version][0]
1590 fn = _packermap[version][0]
1592 return fn(
1591 return fn(
1593 repo,
1592 repo,
1594 oldmatcher,
1593 oldmatcher,
1595 matcher,
1594 matcher,
1596 bundlecaps,
1595 bundlecaps,
1597 ellipses=ellipses,
1596 ellipses=ellipses,
1598 shallow=shallow,
1597 shallow=shallow,
1599 ellipsisroots=ellipsisroots,
1598 ellipsisroots=ellipsisroots,
1600 fullnodes=fullnodes,
1599 fullnodes=fullnodes,
1601 )
1600 )
1602
1601
1603
1602
1604 def getunbundler(version, fh, alg, extras=None):
1603 def getunbundler(version, fh, alg, extras=None):
1605 return _packermap[version][1](fh, alg, extras=extras)
1604 return _packermap[version][1](fh, alg, extras=extras)
1606
1605
1607
1606
1608 def _changegroupinfo(repo, nodes, source):
1607 def _changegroupinfo(repo, nodes, source):
1609 if repo.ui.verbose or source == b'bundle':
1608 if repo.ui.verbose or source == b'bundle':
1610 repo.ui.status(_(b"%d changesets found\n") % len(nodes))
1609 repo.ui.status(_(b"%d changesets found\n") % len(nodes))
1611 if repo.ui.debugflag:
1610 if repo.ui.debugflag:
1612 repo.ui.debug(b"list of changesets:\n")
1611 repo.ui.debug(b"list of changesets:\n")
1613 for node in nodes:
1612 for node in nodes:
1614 repo.ui.debug(b"%s\n" % hex(node))
1613 repo.ui.debug(b"%s\n" % hex(node))
1615
1614
1616
1615
1617 def makechangegroup(
1616 def makechangegroup(
1618 repo, outgoing, version, source, fastpath=False, bundlecaps=None
1617 repo, outgoing, version, source, fastpath=False, bundlecaps=None
1619 ):
1618 ):
1620 cgstream = makestream(
1619 cgstream = makestream(
1621 repo,
1620 repo,
1622 outgoing,
1621 outgoing,
1623 version,
1622 version,
1624 source,
1623 source,
1625 fastpath=fastpath,
1624 fastpath=fastpath,
1626 bundlecaps=bundlecaps,
1625 bundlecaps=bundlecaps,
1627 )
1626 )
1628 return getunbundler(
1627 return getunbundler(
1629 version,
1628 version,
1630 util.chunkbuffer(cgstream),
1629 util.chunkbuffer(cgstream),
1631 None,
1630 None,
1632 {b'clcount': len(outgoing.missing)},
1631 {b'clcount': len(outgoing.missing)},
1633 )
1632 )
1634
1633
1635
1634
1636 def makestream(
1635 def makestream(
1637 repo,
1636 repo,
1638 outgoing,
1637 outgoing,
1639 version,
1638 version,
1640 source,
1639 source,
1641 fastpath=False,
1640 fastpath=False,
1642 bundlecaps=None,
1641 bundlecaps=None,
1643 matcher=None,
1642 matcher=None,
1644 ):
1643 ):
1645 bundler = getbundler(version, repo, bundlecaps=bundlecaps, matcher=matcher)
1644 bundler = getbundler(version, repo, bundlecaps=bundlecaps, matcher=matcher)
1646
1645
1647 repo = repo.unfiltered()
1646 repo = repo.unfiltered()
1648 commonrevs = outgoing.common
1647 commonrevs = outgoing.common
1649 csets = outgoing.missing
1648 csets = outgoing.missing
1650 heads = outgoing.ancestorsof
1649 heads = outgoing.ancestorsof
1651 # We go through the fast path if we get told to, or if all (unfiltered
1650 # We go through the fast path if we get told to, or if all (unfiltered
1652 # heads have been requested (since we then know there all linkrevs will
1651 # heads have been requested (since we then know there all linkrevs will
1653 # be pulled by the client).
1652 # be pulled by the client).
1654 heads.sort()
1653 heads.sort()
1655 fastpathlinkrev = fastpath or (
1654 fastpathlinkrev = fastpath or (
1656 repo.filtername is None and heads == sorted(repo.heads())
1655 repo.filtername is None and heads == sorted(repo.heads())
1657 )
1656 )
1658
1657
1659 repo.hook(b'preoutgoing', throw=True, source=source)
1658 repo.hook(b'preoutgoing', throw=True, source=source)
1660 _changegroupinfo(repo, csets, source)
1659 _changegroupinfo(repo, csets, source)
1661 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1660 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1662
1661
1663
1662
1664 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1663 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1665 revisions = 0
1664 revisions = 0
1666 files = 0
1665 files = 0
1667 progress = repo.ui.makeprogress(
1666 progress = repo.ui.makeprogress(
1668 _(b'files'), unit=_(b'files'), total=expectedfiles
1667 _(b'files'), unit=_(b'files'), total=expectedfiles
1669 )
1668 )
1670 for chunkdata in iter(source.filelogheader, {}):
1669 for chunkdata in iter(source.filelogheader, {}):
1671 files += 1
1670 files += 1
1672 f = chunkdata[b"filename"]
1671 f = chunkdata[b"filename"]
1673 repo.ui.debug(b"adding %s revisions\n" % f)
1672 repo.ui.debug(b"adding %s revisions\n" % f)
1674 progress.increment()
1673 progress.increment()
1675 fl = repo.file(f)
1674 fl = repo.file(f)
1676 o = len(fl)
1675 o = len(fl)
1677 try:
1676 try:
1678 deltas = source.deltaiter()
1677 deltas = source.deltaiter()
1679 if not fl.addgroup(deltas, revmap, trp):
1678 if not fl.addgroup(deltas, revmap, trp):
1680 raise error.Abort(_(b"received file revlog group is empty"))
1679 raise error.Abort(_(b"received file revlog group is empty"))
1681 except error.CensoredBaseError as e:
1680 except error.CensoredBaseError as e:
1682 raise error.Abort(_(b"received delta base is censored: %s") % e)
1681 raise error.Abort(_(b"received delta base is censored: %s") % e)
1683 revisions += len(fl) - o
1682 revisions += len(fl) - o
1684 if f in needfiles:
1683 if f in needfiles:
1685 needs = needfiles[f]
1684 needs = needfiles[f]
1686 for new in pycompat.xrange(o, len(fl)):
1685 for new in pycompat.xrange(o, len(fl)):
1687 n = fl.node(new)
1686 n = fl.node(new)
1688 if n in needs:
1687 if n in needs:
1689 needs.remove(n)
1688 needs.remove(n)
1690 else:
1689 else:
1691 raise error.Abort(_(b"received spurious file revlog entry"))
1690 raise error.Abort(_(b"received spurious file revlog entry"))
1692 if not needs:
1691 if not needs:
1693 del needfiles[f]
1692 del needfiles[f]
1694 progress.complete()
1693 progress.complete()
1695
1694
1696 for f, needs in pycompat.iteritems(needfiles):
1695 for f, needs in pycompat.iteritems(needfiles):
1697 fl = repo.file(f)
1696 fl = repo.file(f)
1698 for n in needs:
1697 for n in needs:
1699 try:
1698 try:
1700 fl.rev(n)
1699 fl.rev(n)
1701 except error.LookupError:
1700 except error.LookupError:
1702 raise error.Abort(
1701 raise error.Abort(
1703 _(b'missing file data for %s:%s - run hg verify')
1702 _(b'missing file data for %s:%s - run hg verify')
1704 % (f, hex(n))
1703 % (f, hex(n))
1705 )
1704 )
1706
1705
1707 return revisions, files
1706 return revisions, files
@@ -1,618 +1,618 b''
1 # changelog.py - changelog class for mercurial
1 # changelog.py - changelog class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from .i18n import _
10 from .i18n import _
11 from .node import (
11 from .node import (
12 bin,
12 bin,
13 hex,
13 hex,
14 nullid,
14 nullid,
15 )
15 )
16 from .thirdparty import attr
16 from .thirdparty import attr
17
17
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 error,
21 metadata,
21 metadata,
22 pycompat,
22 pycompat,
23 revlog,
23 revlog,
24 )
24 )
25 from .utils import (
25 from .utils import (
26 dateutil,
26 dateutil,
27 stringutil,
27 stringutil,
28 )
28 )
29 from .revlogutils import flagutil
29 from .revlogutils import flagutil
30
30
31 _defaultextra = {b'branch': b'default'}
31 _defaultextra = {b'branch': b'default'}
32
32
33
33
34 def _string_escape(text):
34 def _string_escape(text):
35 """
35 """
36 >>> from .pycompat import bytechr as chr
36 >>> from .pycompat import bytechr as chr
37 >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
37 >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
38 >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)s12ab%(cr)scd%(bs)s%(nl)s" % d
38 >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)s12ab%(cr)scd%(bs)s%(nl)s" % d
39 >>> s
39 >>> s
40 'ab\\ncd\\\\\\\\n\\x0012ab\\rcd\\\\\\n'
40 'ab\\ncd\\\\\\\\n\\x0012ab\\rcd\\\\\\n'
41 >>> res = _string_escape(s)
41 >>> res = _string_escape(s)
42 >>> s == _string_unescape(res)
42 >>> s == _string_unescape(res)
43 True
43 True
44 """
44 """
45 # subset of the string_escape codec
45 # subset of the string_escape codec
46 text = (
46 text = (
47 text.replace(b'\\', b'\\\\')
47 text.replace(b'\\', b'\\\\')
48 .replace(b'\n', b'\\n')
48 .replace(b'\n', b'\\n')
49 .replace(b'\r', b'\\r')
49 .replace(b'\r', b'\\r')
50 )
50 )
51 return text.replace(b'\0', b'\\0')
51 return text.replace(b'\0', b'\\0')
52
52
53
53
54 def _string_unescape(text):
54 def _string_unescape(text):
55 if b'\\0' in text:
55 if b'\\0' in text:
56 # fix up \0 without getting into trouble with \\0
56 # fix up \0 without getting into trouble with \\0
57 text = text.replace(b'\\\\', b'\\\\\n')
57 text = text.replace(b'\\\\', b'\\\\\n')
58 text = text.replace(b'\\0', b'\0')
58 text = text.replace(b'\\0', b'\0')
59 text = text.replace(b'\n', b'')
59 text = text.replace(b'\n', b'')
60 return stringutil.unescapestr(text)
60 return stringutil.unescapestr(text)
61
61
62
62
63 def decodeextra(text):
63 def decodeextra(text):
64 """
64 """
65 >>> from .pycompat import bytechr as chr
65 >>> from .pycompat import bytechr as chr
66 >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
66 >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
67 ... ).items())
67 ... ).items())
68 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
68 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
69 >>> sorted(decodeextra(encodeextra({b'foo': b'bar',
69 >>> sorted(decodeextra(encodeextra({b'foo': b'bar',
70 ... b'baz': chr(92) + chr(0) + b'2'})
70 ... b'baz': chr(92) + chr(0) + b'2'})
71 ... ).items())
71 ... ).items())
72 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
72 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
73 """
73 """
74 extra = _defaultextra.copy()
74 extra = _defaultextra.copy()
75 for l in text.split(b'\0'):
75 for l in text.split(b'\0'):
76 if l:
76 if l:
77 k, v = _string_unescape(l).split(b':', 1)
77 k, v = _string_unescape(l).split(b':', 1)
78 extra[k] = v
78 extra[k] = v
79 return extra
79 return extra
80
80
81
81
82 def encodeextra(d):
82 def encodeextra(d):
83 # keys must be sorted to produce a deterministic changelog entry
83 # keys must be sorted to produce a deterministic changelog entry
84 items = [_string_escape(b'%s:%s' % (k, d[k])) for k in sorted(d)]
84 items = [_string_escape(b'%s:%s' % (k, d[k])) for k in sorted(d)]
85 return b"\0".join(items)
85 return b"\0".join(items)
86
86
87
87
88 def stripdesc(desc):
88 def stripdesc(desc):
89 """strip trailing whitespace and leading and trailing empty lines"""
89 """strip trailing whitespace and leading and trailing empty lines"""
90 return b'\n'.join([l.rstrip() for l in desc.splitlines()]).strip(b'\n')
90 return b'\n'.join([l.rstrip() for l in desc.splitlines()]).strip(b'\n')
91
91
92
92
93 class appender(object):
93 class appender(object):
94 """the changelog index must be updated last on disk, so we use this class
94 """the changelog index must be updated last on disk, so we use this class
95 to delay writes to it"""
95 to delay writes to it"""
96
96
97 def __init__(self, vfs, name, mode, buf):
97 def __init__(self, vfs, name, mode, buf):
98 self.data = buf
98 self.data = buf
99 fp = vfs(name, mode)
99 fp = vfs(name, mode)
100 self.fp = fp
100 self.fp = fp
101 self.offset = fp.tell()
101 self.offset = fp.tell()
102 self.size = vfs.fstat(fp).st_size
102 self.size = vfs.fstat(fp).st_size
103 self._end = self.size
103 self._end = self.size
104
104
105 def end(self):
105 def end(self):
106 return self._end
106 return self._end
107
107
108 def tell(self):
108 def tell(self):
109 return self.offset
109 return self.offset
110
110
111 def flush(self):
111 def flush(self):
112 pass
112 pass
113
113
114 @property
114 @property
115 def closed(self):
115 def closed(self):
116 return self.fp.closed
116 return self.fp.closed
117
117
118 def close(self):
118 def close(self):
119 self.fp.close()
119 self.fp.close()
120
120
121 def seek(self, offset, whence=0):
121 def seek(self, offset, whence=0):
122 '''virtual file offset spans real file and data'''
122 '''virtual file offset spans real file and data'''
123 if whence == 0:
123 if whence == 0:
124 self.offset = offset
124 self.offset = offset
125 elif whence == 1:
125 elif whence == 1:
126 self.offset += offset
126 self.offset += offset
127 elif whence == 2:
127 elif whence == 2:
128 self.offset = self.end() + offset
128 self.offset = self.end() + offset
129 if self.offset < self.size:
129 if self.offset < self.size:
130 self.fp.seek(self.offset)
130 self.fp.seek(self.offset)
131
131
132 def read(self, count=-1):
132 def read(self, count=-1):
133 '''only trick here is reads that span real file and data'''
133 '''only trick here is reads that span real file and data'''
134 ret = b""
134 ret = b""
135 if self.offset < self.size:
135 if self.offset < self.size:
136 s = self.fp.read(count)
136 s = self.fp.read(count)
137 ret = s
137 ret = s
138 self.offset += len(s)
138 self.offset += len(s)
139 if count > 0:
139 if count > 0:
140 count -= len(s)
140 count -= len(s)
141 if count != 0:
141 if count != 0:
142 doff = self.offset - self.size
142 doff = self.offset - self.size
143 self.data.insert(0, b"".join(self.data))
143 self.data.insert(0, b"".join(self.data))
144 del self.data[1:]
144 del self.data[1:]
145 s = self.data[0][doff : doff + count]
145 s = self.data[0][doff : doff + count]
146 self.offset += len(s)
146 self.offset += len(s)
147 ret += s
147 ret += s
148 return ret
148 return ret
149
149
150 def write(self, s):
150 def write(self, s):
151 self.data.append(bytes(s))
151 self.data.append(bytes(s))
152 self.offset += len(s)
152 self.offset += len(s)
153 self._end += len(s)
153 self._end += len(s)
154
154
155 def __enter__(self):
155 def __enter__(self):
156 self.fp.__enter__()
156 self.fp.__enter__()
157 return self
157 return self
158
158
159 def __exit__(self, *args):
159 def __exit__(self, *args):
160 return self.fp.__exit__(*args)
160 return self.fp.__exit__(*args)
161
161
162
162
163 class _divertopener(object):
163 class _divertopener(object):
164 def __init__(self, opener, target):
164 def __init__(self, opener, target):
165 self._opener = opener
165 self._opener = opener
166 self._target = target
166 self._target = target
167
167
168 def __call__(self, name, mode=b'r', checkambig=False, **kwargs):
168 def __call__(self, name, mode=b'r', checkambig=False, **kwargs):
169 if name != self._target:
169 if name != self._target:
170 return self._opener(name, mode, **kwargs)
170 return self._opener(name, mode, **kwargs)
171 return self._opener(name + b".a", mode, **kwargs)
171 return self._opener(name + b".a", mode, **kwargs)
172
172
173 def __getattr__(self, attr):
173 def __getattr__(self, attr):
174 return getattr(self._opener, attr)
174 return getattr(self._opener, attr)
175
175
176
176
177 def _delayopener(opener, target, buf):
177 def _delayopener(opener, target, buf):
178 """build an opener that stores chunks in 'buf' instead of 'target'"""
178 """build an opener that stores chunks in 'buf' instead of 'target'"""
179
179
180 def _delay(name, mode=b'r', checkambig=False, **kwargs):
180 def _delay(name, mode=b'r', checkambig=False, **kwargs):
181 if name != target:
181 if name != target:
182 return opener(name, mode, **kwargs)
182 return opener(name, mode, **kwargs)
183 assert not kwargs
183 assert not kwargs
184 return appender(opener, name, mode, buf)
184 return appender(opener, name, mode, buf)
185
185
186 return _delay
186 return _delay
187
187
188
188
189 @attr.s
189 @attr.s
190 class _changelogrevision(object):
190 class _changelogrevision(object):
191 # Extensions might modify _defaultextra, so let the constructor below pass
191 # Extensions might modify _defaultextra, so let the constructor below pass
192 # it in
192 # it in
193 extra = attr.ib()
193 extra = attr.ib()
194 manifest = attr.ib(default=nullid)
194 manifest = attr.ib(default=nullid)
195 user = attr.ib(default=b'')
195 user = attr.ib(default=b'')
196 date = attr.ib(default=(0, 0))
196 date = attr.ib(default=(0, 0))
197 files = attr.ib(default=attr.Factory(list))
197 files = attr.ib(default=attr.Factory(list))
198 filesadded = attr.ib(default=None)
198 filesadded = attr.ib(default=None)
199 filesremoved = attr.ib(default=None)
199 filesremoved = attr.ib(default=None)
200 p1copies = attr.ib(default=None)
200 p1copies = attr.ib(default=None)
201 p2copies = attr.ib(default=None)
201 p2copies = attr.ib(default=None)
202 description = attr.ib(default=b'')
202 description = attr.ib(default=b'')
203 branchinfo = attr.ib(default=(_defaultextra[b'branch'], False))
203 branchinfo = attr.ib(default=(_defaultextra[b'branch'], False))
204
204
205
205
206 class changelogrevision(object):
206 class changelogrevision(object):
207 """Holds results of a parsed changelog revision.
207 """Holds results of a parsed changelog revision.
208
208
209 Changelog revisions consist of multiple pieces of data, including
209 Changelog revisions consist of multiple pieces of data, including
210 the manifest node, user, and date. This object exposes a view into
210 the manifest node, user, and date. This object exposes a view into
211 the parsed object.
211 the parsed object.
212 """
212 """
213
213
214 __slots__ = (
214 __slots__ = (
215 '_offsets',
215 '_offsets',
216 '_text',
216 '_text',
217 '_sidedata',
217 '_sidedata',
218 '_cpsd',
218 '_cpsd',
219 '_changes',
219 '_changes',
220 )
220 )
221
221
222 def __new__(cls, text, sidedata, cpsd):
222 def __new__(cls, text, sidedata, cpsd):
223 if not text:
223 if not text:
224 return _changelogrevision(extra=_defaultextra)
224 return _changelogrevision(extra=_defaultextra)
225
225
226 self = super(changelogrevision, cls).__new__(cls)
226 self = super(changelogrevision, cls).__new__(cls)
227 # We could return here and implement the following as an __init__.
227 # We could return here and implement the following as an __init__.
228 # But doing it here is equivalent and saves an extra function call.
228 # But doing it here is equivalent and saves an extra function call.
229
229
230 # format used:
230 # format used:
231 # nodeid\n : manifest node in ascii
231 # nodeid\n : manifest node in ascii
232 # user\n : user, no \n or \r allowed
232 # user\n : user, no \n or \r allowed
233 # time tz extra\n : date (time is int or float, timezone is int)
233 # time tz extra\n : date (time is int or float, timezone is int)
234 # : extra is metadata, encoded and separated by '\0'
234 # : extra is metadata, encoded and separated by '\0'
235 # : older versions ignore it
235 # : older versions ignore it
236 # files\n\n : files modified by the cset, no \n or \r allowed
236 # files\n\n : files modified by the cset, no \n or \r allowed
237 # (.*) : comment (free text, ideally utf-8)
237 # (.*) : comment (free text, ideally utf-8)
238 #
238 #
239 # changelog v0 doesn't use extra
239 # changelog v0 doesn't use extra
240
240
241 nl1 = text.index(b'\n')
241 nl1 = text.index(b'\n')
242 nl2 = text.index(b'\n', nl1 + 1)
242 nl2 = text.index(b'\n', nl1 + 1)
243 nl3 = text.index(b'\n', nl2 + 1)
243 nl3 = text.index(b'\n', nl2 + 1)
244
244
245 # The list of files may be empty. Which means nl3 is the first of the
245 # The list of files may be empty. Which means nl3 is the first of the
246 # double newline that precedes the description.
246 # double newline that precedes the description.
247 if text[nl3 + 1 : nl3 + 2] == b'\n':
247 if text[nl3 + 1 : nl3 + 2] == b'\n':
248 doublenl = nl3
248 doublenl = nl3
249 else:
249 else:
250 doublenl = text.index(b'\n\n', nl3 + 1)
250 doublenl = text.index(b'\n\n', nl3 + 1)
251
251
252 self._offsets = (nl1, nl2, nl3, doublenl)
252 self._offsets = (nl1, nl2, nl3, doublenl)
253 self._text = text
253 self._text = text
254 self._sidedata = sidedata
254 self._sidedata = sidedata
255 self._cpsd = cpsd
255 self._cpsd = cpsd
256 self._changes = None
256 self._changes = None
257
257
258 return self
258 return self
259
259
260 @property
260 @property
261 def manifest(self):
261 def manifest(self):
262 return bin(self._text[0 : self._offsets[0]])
262 return bin(self._text[0 : self._offsets[0]])
263
263
264 @property
264 @property
265 def user(self):
265 def user(self):
266 off = self._offsets
266 off = self._offsets
267 return encoding.tolocal(self._text[off[0] + 1 : off[1]])
267 return encoding.tolocal(self._text[off[0] + 1 : off[1]])
268
268
269 @property
269 @property
270 def _rawdate(self):
270 def _rawdate(self):
271 off = self._offsets
271 off = self._offsets
272 dateextra = self._text[off[1] + 1 : off[2]]
272 dateextra = self._text[off[1] + 1 : off[2]]
273 return dateextra.split(b' ', 2)[0:2]
273 return dateextra.split(b' ', 2)[0:2]
274
274
275 @property
275 @property
276 def _rawextra(self):
276 def _rawextra(self):
277 off = self._offsets
277 off = self._offsets
278 dateextra = self._text[off[1] + 1 : off[2]]
278 dateextra = self._text[off[1] + 1 : off[2]]
279 fields = dateextra.split(b' ', 2)
279 fields = dateextra.split(b' ', 2)
280 if len(fields) != 3:
280 if len(fields) != 3:
281 return None
281 return None
282
282
283 return fields[2]
283 return fields[2]
284
284
285 @property
285 @property
286 def date(self):
286 def date(self):
287 raw = self._rawdate
287 raw = self._rawdate
288 time = float(raw[0])
288 time = float(raw[0])
289 # Various tools did silly things with the timezone.
289 # Various tools did silly things with the timezone.
290 try:
290 try:
291 timezone = int(raw[1])
291 timezone = int(raw[1])
292 except ValueError:
292 except ValueError:
293 timezone = 0
293 timezone = 0
294
294
295 return time, timezone
295 return time, timezone
296
296
297 @property
297 @property
298 def extra(self):
298 def extra(self):
299 raw = self._rawextra
299 raw = self._rawextra
300 if raw is None:
300 if raw is None:
301 return _defaultextra
301 return _defaultextra
302
302
303 return decodeextra(raw)
303 return decodeextra(raw)
304
304
305 @property
305 @property
306 def changes(self):
306 def changes(self):
307 if self._changes is not None:
307 if self._changes is not None:
308 return self._changes
308 return self._changes
309 if self._cpsd:
309 if self._cpsd:
310 changes = metadata.decode_files_sidedata(self._sidedata)
310 changes = metadata.decode_files_sidedata(self._sidedata)
311 else:
311 else:
312 changes = metadata.ChangingFiles(
312 changes = metadata.ChangingFiles(
313 touched=self.files or (),
313 touched=self.files or (),
314 added=self.filesadded or (),
314 added=self.filesadded or (),
315 removed=self.filesremoved or (),
315 removed=self.filesremoved or (),
316 p1_copies=self.p1copies or {},
316 p1_copies=self.p1copies or {},
317 p2_copies=self.p2copies or {},
317 p2_copies=self.p2copies or {},
318 )
318 )
319 self._changes = changes
319 self._changes = changes
320 return changes
320 return changes
321
321
322 @property
322 @property
323 def files(self):
323 def files(self):
324 if self._cpsd:
324 if self._cpsd:
325 return sorted(self.changes.touched)
325 return sorted(self.changes.touched)
326 off = self._offsets
326 off = self._offsets
327 if off[2] == off[3]:
327 if off[2] == off[3]:
328 return []
328 return []
329
329
330 return self._text[off[2] + 1 : off[3]].split(b'\n')
330 return self._text[off[2] + 1 : off[3]].split(b'\n')
331
331
332 @property
332 @property
333 def filesadded(self):
333 def filesadded(self):
334 if self._cpsd:
334 if self._cpsd:
335 return self.changes.added
335 return self.changes.added
336 else:
336 else:
337 rawindices = self.extra.get(b'filesadded')
337 rawindices = self.extra.get(b'filesadded')
338 if rawindices is None:
338 if rawindices is None:
339 return None
339 return None
340 return metadata.decodefileindices(self.files, rawindices)
340 return metadata.decodefileindices(self.files, rawindices)
341
341
342 @property
342 @property
343 def filesremoved(self):
343 def filesremoved(self):
344 if self._cpsd:
344 if self._cpsd:
345 return self.changes.removed
345 return self.changes.removed
346 else:
346 else:
347 rawindices = self.extra.get(b'filesremoved')
347 rawindices = self.extra.get(b'filesremoved')
348 if rawindices is None:
348 if rawindices is None:
349 return None
349 return None
350 return metadata.decodefileindices(self.files, rawindices)
350 return metadata.decodefileindices(self.files, rawindices)
351
351
352 @property
352 @property
353 def p1copies(self):
353 def p1copies(self):
354 if self._cpsd:
354 if self._cpsd:
355 return self.changes.copied_from_p1
355 return self.changes.copied_from_p1
356 else:
356 else:
357 rawcopies = self.extra.get(b'p1copies')
357 rawcopies = self.extra.get(b'p1copies')
358 if rawcopies is None:
358 if rawcopies is None:
359 return None
359 return None
360 return metadata.decodecopies(self.files, rawcopies)
360 return metadata.decodecopies(self.files, rawcopies)
361
361
362 @property
362 @property
363 def p2copies(self):
363 def p2copies(self):
364 if self._cpsd:
364 if self._cpsd:
365 return self.changes.copied_from_p2
365 return self.changes.copied_from_p2
366 else:
366 else:
367 rawcopies = self.extra.get(b'p2copies')
367 rawcopies = self.extra.get(b'p2copies')
368 if rawcopies is None:
368 if rawcopies is None:
369 return None
369 return None
370 return metadata.decodecopies(self.files, rawcopies)
370 return metadata.decodecopies(self.files, rawcopies)
371
371
372 @property
372 @property
373 def description(self):
373 def description(self):
374 return encoding.tolocal(self._text[self._offsets[3] + 2 :])
374 return encoding.tolocal(self._text[self._offsets[3] + 2 :])
375
375
376 @property
376 @property
377 def branchinfo(self):
377 def branchinfo(self):
378 extra = self.extra
378 extra = self.extra
379 return encoding.tolocal(extra.get(b"branch")), b'close' in extra
379 return encoding.tolocal(extra.get(b"branch")), b'close' in extra
380
380
381
381
382 class changelog(revlog.revlog):
382 class changelog(revlog.revlog):
383 def __init__(self, opener, trypending=False):
383 def __init__(self, opener, trypending=False):
384 """Load a changelog revlog using an opener.
384 """Load a changelog revlog using an opener.
385
385
386 If ``trypending`` is true, we attempt to load the index from a
386 If ``trypending`` is true, we attempt to load the index from a
387 ``00changelog.i.a`` file instead of the default ``00changelog.i``.
387 ``00changelog.i.a`` file instead of the default ``00changelog.i``.
388 The ``00changelog.i.a`` file contains index (and possibly inline
388 The ``00changelog.i.a`` file contains index (and possibly inline
389 revision) data for a transaction that hasn't been finalized yet.
389 revision) data for a transaction that hasn't been finalized yet.
390 It exists in a separate file to facilitate readers (such as
390 It exists in a separate file to facilitate readers (such as
391 hooks processes) accessing data before a transaction is finalized.
391 hooks processes) accessing data before a transaction is finalized.
392 """
392 """
393 if trypending and opener.exists(b'00changelog.i.a'):
393 if trypending and opener.exists(b'00changelog.i.a'):
394 indexfile = b'00changelog.i.a'
394 indexfile = b'00changelog.i.a'
395 else:
395 else:
396 indexfile = b'00changelog.i'
396 indexfile = b'00changelog.i'
397
397
398 datafile = b'00changelog.d'
398 datafile = b'00changelog.d'
399 revlog.revlog.__init__(
399 revlog.revlog.__init__(
400 self,
400 self,
401 opener,
401 opener,
402 indexfile,
402 indexfile,
403 datafile=datafile,
403 datafile=datafile,
404 checkambig=True,
404 checkambig=True,
405 mmaplargeindex=True,
405 mmaplargeindex=True,
406 persistentnodemap=opener.options.get(b'persistent-nodemap', False),
406 persistentnodemap=opener.options.get(b'persistent-nodemap', False),
407 )
407 )
408
408
409 if self._initempty and (self.version & 0xFFFF == revlog.REVLOGV1):
409 if self._initempty and (self.version & 0xFFFF == revlog.REVLOGV1):
410 # changelogs don't benefit from generaldelta.
410 # changelogs don't benefit from generaldelta.
411
411
412 self.version &= ~revlog.FLAG_GENERALDELTA
412 self.version &= ~revlog.FLAG_GENERALDELTA
413 self._generaldelta = False
413 self._generaldelta = False
414
414
415 # Delta chains for changelogs tend to be very small because entries
415 # Delta chains for changelogs tend to be very small because entries
416 # tend to be small and don't delta well with each. So disable delta
416 # tend to be small and don't delta well with each. So disable delta
417 # chains.
417 # chains.
418 self._storedeltachains = False
418 self._storedeltachains = False
419
419
420 self._realopener = opener
420 self._realopener = opener
421 self._delayed = False
421 self._delayed = False
422 self._delaybuf = None
422 self._delaybuf = None
423 self._divert = False
423 self._divert = False
424 self._filteredrevs = frozenset()
424 self._filteredrevs = frozenset()
425 self._filteredrevs_hashcache = {}
425 self._filteredrevs_hashcache = {}
426 self._copiesstorage = opener.options.get(b'copies-storage')
426 self._copiesstorage = opener.options.get(b'copies-storage')
427
427
428 @property
428 @property
429 def filteredrevs(self):
429 def filteredrevs(self):
430 return self._filteredrevs
430 return self._filteredrevs
431
431
432 @filteredrevs.setter
432 @filteredrevs.setter
433 def filteredrevs(self, val):
433 def filteredrevs(self, val):
434 # Ensure all updates go through this function
434 # Ensure all updates go through this function
435 assert isinstance(val, frozenset)
435 assert isinstance(val, frozenset)
436 self._filteredrevs = val
436 self._filteredrevs = val
437 self._filteredrevs_hashcache = {}
437 self._filteredrevs_hashcache = {}
438
438
439 def delayupdate(self, tr):
439 def delayupdate(self, tr):
440 """delay visibility of index updates to other readers"""
440 """delay visibility of index updates to other readers"""
441
441
442 if not self._delayed:
442 if not self._delayed:
443 if len(self) == 0:
443 if len(self) == 0:
444 self._divert = True
444 self._divert = True
445 if self._realopener.exists(self.indexfile + b'.a'):
445 if self._realopener.exists(self.indexfile + b'.a'):
446 self._realopener.unlink(self.indexfile + b'.a')
446 self._realopener.unlink(self.indexfile + b'.a')
447 self.opener = _divertopener(self._realopener, self.indexfile)
447 self.opener = _divertopener(self._realopener, self.indexfile)
448 else:
448 else:
449 self._delaybuf = []
449 self._delaybuf = []
450 self.opener = _delayopener(
450 self.opener = _delayopener(
451 self._realopener, self.indexfile, self._delaybuf
451 self._realopener, self.indexfile, self._delaybuf
452 )
452 )
453 self._delayed = True
453 self._delayed = True
454 tr.addpending(b'cl-%i' % id(self), self._writepending)
454 tr.addpending(b'cl-%i' % id(self), self._writepending)
455 tr.addfinalize(b'cl-%i' % id(self), self._finalize)
455 tr.addfinalize(b'cl-%i' % id(self), self._finalize)
456
456
457 def _finalize(self, tr):
457 def _finalize(self, tr):
458 """finalize index updates"""
458 """finalize index updates"""
459 self._delayed = False
459 self._delayed = False
460 self.opener = self._realopener
460 self.opener = self._realopener
461 # move redirected index data back into place
461 # move redirected index data back into place
462 if self._divert:
462 if self._divert:
463 assert not self._delaybuf
463 assert not self._delaybuf
464 tmpname = self.indexfile + b".a"
464 tmpname = self.indexfile + b".a"
465 nfile = self.opener.open(tmpname)
465 nfile = self.opener.open(tmpname)
466 nfile.close()
466 nfile.close()
467 self.opener.rename(tmpname, self.indexfile, checkambig=True)
467 self.opener.rename(tmpname, self.indexfile, checkambig=True)
468 elif self._delaybuf:
468 elif self._delaybuf:
469 fp = self.opener(self.indexfile, b'a', checkambig=True)
469 fp = self.opener(self.indexfile, b'a', checkambig=True)
470 fp.write(b"".join(self._delaybuf))
470 fp.write(b"".join(self._delaybuf))
471 fp.close()
471 fp.close()
472 self._delaybuf = None
472 self._delaybuf = None
473 self._divert = False
473 self._divert = False
474 # split when we're done
474 # split when we're done
475 self._enforceinlinesize(tr)
475 self._enforceinlinesize(tr)
476
476
477 def _writepending(self, tr):
477 def _writepending(self, tr):
478 """create a file containing the unfinalized state for
478 """create a file containing the unfinalized state for
479 pretxnchangegroup"""
479 pretxnchangegroup"""
480 if self._delaybuf:
480 if self._delaybuf:
481 # make a temporary copy of the index
481 # make a temporary copy of the index
482 fp1 = self._realopener(self.indexfile)
482 fp1 = self._realopener(self.indexfile)
483 pendingfilename = self.indexfile + b".a"
483 pendingfilename = self.indexfile + b".a"
484 # register as a temp file to ensure cleanup on failure
484 # register as a temp file to ensure cleanup on failure
485 tr.registertmp(pendingfilename)
485 tr.registertmp(pendingfilename)
486 # write existing data
486 # write existing data
487 fp2 = self._realopener(pendingfilename, b"w")
487 fp2 = self._realopener(pendingfilename, b"w")
488 fp2.write(fp1.read())
488 fp2.write(fp1.read())
489 # add pending data
489 # add pending data
490 fp2.write(b"".join(self._delaybuf))
490 fp2.write(b"".join(self._delaybuf))
491 fp2.close()
491 fp2.close()
492 # switch modes so finalize can simply rename
492 # switch modes so finalize can simply rename
493 self._delaybuf = None
493 self._delaybuf = None
494 self._divert = True
494 self._divert = True
495 self.opener = _divertopener(self._realopener, self.indexfile)
495 self.opener = _divertopener(self._realopener, self.indexfile)
496
496
497 if self._divert:
497 if self._divert:
498 return True
498 return True
499
499
500 return False
500 return False
501
501
502 def _enforceinlinesize(self, tr, fp=None):
502 def _enforceinlinesize(self, tr, fp=None):
503 if not self._delayed:
503 if not self._delayed:
504 revlog.revlog._enforceinlinesize(self, tr, fp)
504 revlog.revlog._enforceinlinesize(self, tr, fp)
505
505
506 def read(self, node):
506 def read(self, node):
507 """Obtain data from a parsed changelog revision.
507 """Obtain data from a parsed changelog revision.
508
508
509 Returns a 6-tuple of:
509 Returns a 6-tuple of:
510
510
511 - manifest node in binary
511 - manifest node in binary
512 - author/user as a localstr
512 - author/user as a localstr
513 - date as a 2-tuple of (time, timezone)
513 - date as a 2-tuple of (time, timezone)
514 - list of files
514 - list of files
515 - commit message as a localstr
515 - commit message as a localstr
516 - dict of extra metadata
516 - dict of extra metadata
517
517
518 Unless you need to access all fields, consider calling
518 Unless you need to access all fields, consider calling
519 ``changelogrevision`` instead, as it is faster for partial object
519 ``changelogrevision`` instead, as it is faster for partial object
520 access.
520 access.
521 """
521 """
522 d, s = self._revisiondata(node)
522 d, s = self._revisiondata(node)
523 c = changelogrevision(
523 c = changelogrevision(
524 d, s, self._copiesstorage == b'changeset-sidedata'
524 d, s, self._copiesstorage == b'changeset-sidedata'
525 )
525 )
526 return (c.manifest, c.user, c.date, c.files, c.description, c.extra)
526 return (c.manifest, c.user, c.date, c.files, c.description, c.extra)
527
527
528 def changelogrevision(self, nodeorrev):
528 def changelogrevision(self, nodeorrev):
529 """Obtain a ``changelogrevision`` for a node or revision."""
529 """Obtain a ``changelogrevision`` for a node or revision."""
530 text, sidedata = self._revisiondata(nodeorrev)
530 text, sidedata = self._revisiondata(nodeorrev)
531 return changelogrevision(
531 return changelogrevision(
532 text, sidedata, self._copiesstorage == b'changeset-sidedata'
532 text, sidedata, self._copiesstorage == b'changeset-sidedata'
533 )
533 )
534
534
535 def readfiles(self, node):
535 def readfiles(self, node):
536 """
536 """
537 short version of read that only returns the files modified by the cset
537 short version of read that only returns the files modified by the cset
538 """
538 """
539 text = self.revision(node)
539 text = self.revision(node)
540 if not text:
540 if not text:
541 return []
541 return []
542 last = text.index(b"\n\n")
542 last = text.index(b"\n\n")
543 l = text[:last].split(b'\n')
543 l = text[:last].split(b'\n')
544 return l[3:]
544 return l[3:]
545
545
546 def add(
546 def add(
547 self,
547 self,
548 manifest,
548 manifest,
549 files,
549 files,
550 desc,
550 desc,
551 transaction,
551 transaction,
552 p1,
552 p1,
553 p2,
553 p2,
554 user,
554 user,
555 date=None,
555 date=None,
556 extra=None,
556 extra=None,
557 ):
557 ):
558 # Convert to UTF-8 encoded bytestrings as the very first
558 # Convert to UTF-8 encoded bytestrings as the very first
559 # thing: calling any method on a localstr object will turn it
559 # thing: calling any method on a localstr object will turn it
560 # into a str object and the cached UTF-8 string is thus lost.
560 # into a str object and the cached UTF-8 string is thus lost.
561 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
561 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
562
562
563 user = user.strip()
563 user = user.strip()
564 # An empty username or a username with a "\n" will make the
564 # An empty username or a username with a "\n" will make the
565 # revision text contain two "\n\n" sequences -> corrupt
565 # revision text contain two "\n\n" sequences -> corrupt
566 # repository since read cannot unpack the revision.
566 # repository since read cannot unpack the revision.
567 if not user:
567 if not user:
568 raise error.StorageError(_(b"empty username"))
568 raise error.StorageError(_(b"empty username"))
569 if b"\n" in user:
569 if b"\n" in user:
570 raise error.StorageError(
570 raise error.StorageError(
571 _(b"username %r contains a newline") % pycompat.bytestr(user)
571 _(b"username %r contains a newline") % pycompat.bytestr(user)
572 )
572 )
573
573
574 desc = stripdesc(desc)
574 desc = stripdesc(desc)
575
575
576 if date:
576 if date:
577 parseddate = b"%d %d" % dateutil.parsedate(date)
577 parseddate = b"%d %d" % dateutil.parsedate(date)
578 else:
578 else:
579 parseddate = b"%d %d" % dateutil.makedate()
579 parseddate = b"%d %d" % dateutil.makedate()
580 if extra:
580 if extra:
581 branch = extra.get(b"branch")
581 branch = extra.get(b"branch")
582 if branch in (b"default", b""):
582 if branch in (b"default", b""):
583 del extra[b"branch"]
583 del extra[b"branch"]
584 elif branch in (b".", b"null", b"tip"):
584 elif branch in (b".", b"null", b"tip"):
585 raise error.StorageError(
585 raise error.StorageError(
586 _(b'the name \'%s\' is reserved') % branch
586 _(b'the name \'%s\' is reserved') % branch
587 )
587 )
588 sortedfiles = sorted(files.touched)
588 sortedfiles = sorted(files.touched)
589 flags = 0
589 flags = 0
590 sidedata = None
590 sidedata = None
591 if self._copiesstorage == b'changeset-sidedata':
591 if self._copiesstorage == b'changeset-sidedata':
592 if files.has_copies_info:
592 if files.has_copies_info:
593 flags |= flagutil.REVIDX_HASCOPIESINFO
593 flags |= flagutil.REVIDX_HASCOPIESINFO
594 sidedata = metadata.encode_files_sidedata(files)
594 sidedata = metadata.encode_files_sidedata(files)
595
595
596 if extra:
596 if extra:
597 extra = encodeextra(extra)
597 extra = encodeextra(extra)
598 parseddate = b"%s %s" % (parseddate, extra)
598 parseddate = b"%s %s" % (parseddate, extra)
599 l = [hex(manifest), user, parseddate] + sortedfiles + [b"", desc]
599 l = [hex(manifest), user, parseddate] + sortedfiles + [b"", desc]
600 text = b"\n".join(l)
600 text = b"\n".join(l)
601 rev = self.addrevision(
601 rev = self.addrevision(
602 text, transaction, len(self), p1, p2, sidedata=sidedata, flags=flags
602 text, transaction, len(self), p1, p2, sidedata=sidedata, flags=flags
603 )
603 )
604 return self.node(rev)
604 return self.node(rev)
605
605
606 def branchinfo(self, rev):
606 def branchinfo(self, rev):
607 """return the branch name and open/close state of a revision
607 """return the branch name and open/close state of a revision
608
608
609 This function exists because creating a changectx object
609 This function exists because creating a changectx object
610 just to access this is costly."""
610 just to access this is costly."""
611 return self.changelogrevision(rev).branchinfo
611 return self.changelogrevision(rev).branchinfo
612
612
613 def _nodeduplicatecallback(self, transaction, node):
613 def _nodeduplicatecallback(self, transaction, rev):
614 # keep track of revisions that got "re-added", eg: unbunde of know rev.
614 # keep track of revisions that got "re-added", eg: unbunde of know rev.
615 #
615 #
616 # We track them in a list to preserve their order from the source bundle
616 # We track them in a list to preserve their order from the source bundle
617 duplicates = transaction.changes.setdefault(b'revduplicates', [])
617 duplicates = transaction.changes.setdefault(b'revduplicates', [])
618 duplicates.append(self.rev(node))
618 duplicates.append(rev)
@@ -1,799 +1,798 b''
1 # exchangev2.py - repository exchange for wire protocol version 2
1 # exchangev2.py - repository exchange for wire protocol version 2
2 #
2 #
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import weakref
11 import weakref
12
12
13 from .i18n import _
13 from .i18n import _
14 from .node import (
14 from .node import (
15 nullid,
15 nullid,
16 short,
16 short,
17 )
17 )
18 from . import (
18 from . import (
19 bookmarks,
19 bookmarks,
20 error,
20 error,
21 mdiff,
21 mdiff,
22 narrowspec,
22 narrowspec,
23 phases,
23 phases,
24 pycompat,
24 pycompat,
25 setdiscovery,
25 setdiscovery,
26 )
26 )
27 from .interfaces import repository
27 from .interfaces import repository
28
28
29
29
30 def pull(pullop):
30 def pull(pullop):
31 """Pull using wire protocol version 2."""
31 """Pull using wire protocol version 2."""
32 repo = pullop.repo
32 repo = pullop.repo
33 remote = pullop.remote
33 remote = pullop.remote
34
34
35 usingrawchangelogandmanifest = _checkuserawstorefiledata(pullop)
35 usingrawchangelogandmanifest = _checkuserawstorefiledata(pullop)
36
36
37 # If this is a clone and it was requested to perform a "stream clone",
37 # If this is a clone and it was requested to perform a "stream clone",
38 # we obtain the raw files data from the remote then fall back to an
38 # we obtain the raw files data from the remote then fall back to an
39 # incremental pull. This is somewhat hacky and is not nearly robust enough
39 # incremental pull. This is somewhat hacky and is not nearly robust enough
40 # for long-term usage.
40 # for long-term usage.
41 if usingrawchangelogandmanifest:
41 if usingrawchangelogandmanifest:
42 with repo.transaction(b'clone'):
42 with repo.transaction(b'clone'):
43 _fetchrawstorefiles(repo, remote)
43 _fetchrawstorefiles(repo, remote)
44 repo.invalidate(clearfilecache=True)
44 repo.invalidate(clearfilecache=True)
45
45
46 tr = pullop.trmanager.transaction()
46 tr = pullop.trmanager.transaction()
47
47
48 # We don't use the repo's narrow matcher here because the patterns passed
48 # We don't use the repo's narrow matcher here because the patterns passed
49 # to exchange.pull() could be different.
49 # to exchange.pull() could be different.
50 narrowmatcher = narrowspec.match(
50 narrowmatcher = narrowspec.match(
51 repo.root,
51 repo.root,
52 # Empty maps to nevermatcher. So always
52 # Empty maps to nevermatcher. So always
53 # set includes if missing.
53 # set includes if missing.
54 pullop.includepats or {b'path:.'},
54 pullop.includepats or {b'path:.'},
55 pullop.excludepats,
55 pullop.excludepats,
56 )
56 )
57
57
58 if pullop.includepats or pullop.excludepats:
58 if pullop.includepats or pullop.excludepats:
59 pathfilter = {}
59 pathfilter = {}
60 if pullop.includepats:
60 if pullop.includepats:
61 pathfilter[b'include'] = sorted(pullop.includepats)
61 pathfilter[b'include'] = sorted(pullop.includepats)
62 if pullop.excludepats:
62 if pullop.excludepats:
63 pathfilter[b'exclude'] = sorted(pullop.excludepats)
63 pathfilter[b'exclude'] = sorted(pullop.excludepats)
64 else:
64 else:
65 pathfilter = None
65 pathfilter = None
66
66
67 # Figure out what needs to be fetched.
67 # Figure out what needs to be fetched.
68 common, fetch, remoteheads = _pullchangesetdiscovery(
68 common, fetch, remoteheads = _pullchangesetdiscovery(
69 repo, remote, pullop.heads, abortwhenunrelated=pullop.force
69 repo, remote, pullop.heads, abortwhenunrelated=pullop.force
70 )
70 )
71
71
72 # And fetch the data.
72 # And fetch the data.
73 pullheads = pullop.heads or remoteheads
73 pullheads = pullop.heads or remoteheads
74 csetres = _fetchchangesets(repo, tr, remote, common, fetch, pullheads)
74 csetres = _fetchchangesets(repo, tr, remote, common, fetch, pullheads)
75
75
76 # New revisions are written to the changelog. But all other updates
76 # New revisions are written to the changelog. But all other updates
77 # are deferred. Do those now.
77 # are deferred. Do those now.
78
78
79 # Ensure all new changesets are draft by default. If the repo is
79 # Ensure all new changesets are draft by default. If the repo is
80 # publishing, the phase will be adjusted by the loop below.
80 # publishing, the phase will be adjusted by the loop below.
81 if csetres[b'added']:
81 if csetres[b'added']:
82 phases.registernew(
82 phases.registernew(
83 repo, tr, phases.draft, [repo[n].rev() for n in csetres[b'added']]
83 repo, tr, phases.draft, [repo[n].rev() for n in csetres[b'added']]
84 )
84 )
85
85
86 # And adjust the phase of all changesets accordingly.
86 # And adjust the phase of all changesets accordingly.
87 for phasenumber, phase in phases.phasenames.items():
87 for phasenumber, phase in phases.phasenames.items():
88 if phase == b'secret' or not csetres[b'nodesbyphase'][phase]:
88 if phase == b'secret' or not csetres[b'nodesbyphase'][phase]:
89 continue
89 continue
90
90
91 phases.advanceboundary(
91 phases.advanceboundary(
92 repo,
92 repo,
93 tr,
93 tr,
94 phasenumber,
94 phasenumber,
95 csetres[b'nodesbyphase'][phase],
95 csetres[b'nodesbyphase'][phase],
96 )
96 )
97
97
98 # Write bookmark updates.
98 # Write bookmark updates.
99 bookmarks.updatefromremote(
99 bookmarks.updatefromremote(
100 repo.ui,
100 repo.ui,
101 repo,
101 repo,
102 csetres[b'bookmarks'],
102 csetres[b'bookmarks'],
103 remote.url(),
103 remote.url(),
104 pullop.gettransaction,
104 pullop.gettransaction,
105 explicit=pullop.explicitbookmarks,
105 explicit=pullop.explicitbookmarks,
106 )
106 )
107
107
108 manres = _fetchmanifests(repo, tr, remote, csetres[b'manifestnodes'])
108 manres = _fetchmanifests(repo, tr, remote, csetres[b'manifestnodes'])
109
109
110 # We don't properly support shallow changeset and manifest yet. So we apply
110 # We don't properly support shallow changeset and manifest yet. So we apply
111 # depth limiting locally.
111 # depth limiting locally.
112 if pullop.depth:
112 if pullop.depth:
113 relevantcsetnodes = set()
113 relevantcsetnodes = set()
114 clnode = repo.changelog.node
114 clnode = repo.changelog.node
115
115
116 for rev in repo.revs(
116 for rev in repo.revs(
117 b'ancestors(%ln, %s)', pullheads, pullop.depth - 1
117 b'ancestors(%ln, %s)', pullheads, pullop.depth - 1
118 ):
118 ):
119 relevantcsetnodes.add(clnode(rev))
119 relevantcsetnodes.add(clnode(rev))
120
120
121 csetrelevantfilter = lambda n: n in relevantcsetnodes
121 csetrelevantfilter = lambda n: n in relevantcsetnodes
122
122
123 else:
123 else:
124 csetrelevantfilter = lambda n: True
124 csetrelevantfilter = lambda n: True
125
125
126 # If obtaining the raw store files, we need to scan the full repo to
126 # If obtaining the raw store files, we need to scan the full repo to
127 # derive all the changesets, manifests, and linkrevs.
127 # derive all the changesets, manifests, and linkrevs.
128 if usingrawchangelogandmanifest:
128 if usingrawchangelogandmanifest:
129 csetsforfiles = []
129 csetsforfiles = []
130 mnodesforfiles = []
130 mnodesforfiles = []
131 manifestlinkrevs = {}
131 manifestlinkrevs = {}
132
132
133 for rev in repo:
133 for rev in repo:
134 ctx = repo[rev]
134 ctx = repo[rev]
135 node = ctx.node()
135 node = ctx.node()
136
136
137 if not csetrelevantfilter(node):
137 if not csetrelevantfilter(node):
138 continue
138 continue
139
139
140 mnode = ctx.manifestnode()
140 mnode = ctx.manifestnode()
141
141
142 csetsforfiles.append(node)
142 csetsforfiles.append(node)
143 mnodesforfiles.append(mnode)
143 mnodesforfiles.append(mnode)
144 manifestlinkrevs[mnode] = rev
144 manifestlinkrevs[mnode] = rev
145
145
146 else:
146 else:
147 csetsforfiles = [n for n in csetres[b'added'] if csetrelevantfilter(n)]
147 csetsforfiles = [n for n in csetres[b'added'] if csetrelevantfilter(n)]
148 mnodesforfiles = manres[b'added']
148 mnodesforfiles = manres[b'added']
149 manifestlinkrevs = manres[b'linkrevs']
149 manifestlinkrevs = manres[b'linkrevs']
150
150
151 # Find all file nodes referenced by added manifests and fetch those
151 # Find all file nodes referenced by added manifests and fetch those
152 # revisions.
152 # revisions.
153 fnodes = _derivefilesfrommanifests(repo, narrowmatcher, mnodesforfiles)
153 fnodes = _derivefilesfrommanifests(repo, narrowmatcher, mnodesforfiles)
154 _fetchfilesfromcsets(
154 _fetchfilesfromcsets(
155 repo,
155 repo,
156 tr,
156 tr,
157 remote,
157 remote,
158 pathfilter,
158 pathfilter,
159 fnodes,
159 fnodes,
160 csetsforfiles,
160 csetsforfiles,
161 manifestlinkrevs,
161 manifestlinkrevs,
162 shallow=bool(pullop.depth),
162 shallow=bool(pullop.depth),
163 )
163 )
164
164
165
165
166 def _checkuserawstorefiledata(pullop):
166 def _checkuserawstorefiledata(pullop):
167 """Check whether we should use rawstorefiledata command to retrieve data."""
167 """Check whether we should use rawstorefiledata command to retrieve data."""
168
168
169 repo = pullop.repo
169 repo = pullop.repo
170 remote = pullop.remote
170 remote = pullop.remote
171
171
172 # Command to obtain raw store data isn't available.
172 # Command to obtain raw store data isn't available.
173 if b'rawstorefiledata' not in remote.apidescriptor[b'commands']:
173 if b'rawstorefiledata' not in remote.apidescriptor[b'commands']:
174 return False
174 return False
175
175
176 # Only honor if user requested stream clone operation.
176 # Only honor if user requested stream clone operation.
177 if not pullop.streamclonerequested:
177 if not pullop.streamclonerequested:
178 return False
178 return False
179
179
180 # Only works on empty repos.
180 # Only works on empty repos.
181 if len(repo):
181 if len(repo):
182 return False
182 return False
183
183
184 # TODO This is super hacky. There needs to be a storage API for this. We
184 # TODO This is super hacky. There needs to be a storage API for this. We
185 # also need to check for compatibility with the remote.
185 # also need to check for compatibility with the remote.
186 if b'revlogv1' not in repo.requirements:
186 if b'revlogv1' not in repo.requirements:
187 return False
187 return False
188
188
189 return True
189 return True
190
190
191
191
192 def _fetchrawstorefiles(repo, remote):
192 def _fetchrawstorefiles(repo, remote):
193 with remote.commandexecutor() as e:
193 with remote.commandexecutor() as e:
194 objs = e.callcommand(
194 objs = e.callcommand(
195 b'rawstorefiledata',
195 b'rawstorefiledata',
196 {
196 {
197 b'files': [b'changelog', b'manifestlog'],
197 b'files': [b'changelog', b'manifestlog'],
198 },
198 },
199 ).result()
199 ).result()
200
200
201 # First object is a summary of files data that follows.
201 # First object is a summary of files data that follows.
202 overall = next(objs)
202 overall = next(objs)
203
203
204 progress = repo.ui.makeprogress(
204 progress = repo.ui.makeprogress(
205 _(b'clone'), total=overall[b'totalsize'], unit=_(b'bytes')
205 _(b'clone'), total=overall[b'totalsize'], unit=_(b'bytes')
206 )
206 )
207 with progress:
207 with progress:
208 progress.update(0)
208 progress.update(0)
209
209
210 # Next are pairs of file metadata, data.
210 # Next are pairs of file metadata, data.
211 while True:
211 while True:
212 try:
212 try:
213 filemeta = next(objs)
213 filemeta = next(objs)
214 except StopIteration:
214 except StopIteration:
215 break
215 break
216
216
217 for k in (b'location', b'path', b'size'):
217 for k in (b'location', b'path', b'size'):
218 if k not in filemeta:
218 if k not in filemeta:
219 raise error.Abort(
219 raise error.Abort(
220 _(b'remote file data missing key: %s') % k
220 _(b'remote file data missing key: %s') % k
221 )
221 )
222
222
223 if filemeta[b'location'] == b'store':
223 if filemeta[b'location'] == b'store':
224 vfs = repo.svfs
224 vfs = repo.svfs
225 else:
225 else:
226 raise error.Abort(
226 raise error.Abort(
227 _(b'invalid location for raw file data: %s')
227 _(b'invalid location for raw file data: %s')
228 % filemeta[b'location']
228 % filemeta[b'location']
229 )
229 )
230
230
231 bytesremaining = filemeta[b'size']
231 bytesremaining = filemeta[b'size']
232
232
233 with vfs.open(filemeta[b'path'], b'wb') as fh:
233 with vfs.open(filemeta[b'path'], b'wb') as fh:
234 while True:
234 while True:
235 try:
235 try:
236 chunk = next(objs)
236 chunk = next(objs)
237 except StopIteration:
237 except StopIteration:
238 break
238 break
239
239
240 bytesremaining -= len(chunk)
240 bytesremaining -= len(chunk)
241
241
242 if bytesremaining < 0:
242 if bytesremaining < 0:
243 raise error.Abort(
243 raise error.Abort(
244 _(
244 _(
245 b'received invalid number of bytes for file '
245 b'received invalid number of bytes for file '
246 b'data; expected %d, got extra'
246 b'data; expected %d, got extra'
247 )
247 )
248 % filemeta[b'size']
248 % filemeta[b'size']
249 )
249 )
250
250
251 progress.increment(step=len(chunk))
251 progress.increment(step=len(chunk))
252 fh.write(chunk)
252 fh.write(chunk)
253
253
254 try:
254 try:
255 if chunk.islast:
255 if chunk.islast:
256 break
256 break
257 except AttributeError:
257 except AttributeError:
258 raise error.Abort(
258 raise error.Abort(
259 _(
259 _(
260 b'did not receive indefinite length bytestring '
260 b'did not receive indefinite length bytestring '
261 b'for file data'
261 b'for file data'
262 )
262 )
263 )
263 )
264
264
265 if bytesremaining:
265 if bytesremaining:
266 raise error.Abort(
266 raise error.Abort(
267 _(
267 _(
268 b'received invalid number of bytes for'
268 b'received invalid number of bytes for'
269 b'file data; expected %d got %d'
269 b'file data; expected %d got %d'
270 )
270 )
271 % (
271 % (
272 filemeta[b'size'],
272 filemeta[b'size'],
273 filemeta[b'size'] - bytesremaining,
273 filemeta[b'size'] - bytesremaining,
274 )
274 )
275 )
275 )
276
276
277
277
278 def _pullchangesetdiscovery(repo, remote, heads, abortwhenunrelated=True):
278 def _pullchangesetdiscovery(repo, remote, heads, abortwhenunrelated=True):
279 """Determine which changesets need to be pulled."""
279 """Determine which changesets need to be pulled."""
280
280
281 if heads:
281 if heads:
282 knownnode = repo.changelog.hasnode
282 knownnode = repo.changelog.hasnode
283 if all(knownnode(head) for head in heads):
283 if all(knownnode(head) for head in heads):
284 return heads, False, heads
284 return heads, False, heads
285
285
286 # TODO wire protocol version 2 is capable of more efficient discovery
286 # TODO wire protocol version 2 is capable of more efficient discovery
287 # than setdiscovery. Consider implementing something better.
287 # than setdiscovery. Consider implementing something better.
288 common, fetch, remoteheads = setdiscovery.findcommonheads(
288 common, fetch, remoteheads = setdiscovery.findcommonheads(
289 repo.ui, repo, remote, abortwhenunrelated=abortwhenunrelated
289 repo.ui, repo, remote, abortwhenunrelated=abortwhenunrelated
290 )
290 )
291
291
292 common = set(common)
292 common = set(common)
293 remoteheads = set(remoteheads)
293 remoteheads = set(remoteheads)
294
294
295 # If a remote head is filtered locally, put it back in the common set.
295 # If a remote head is filtered locally, put it back in the common set.
296 # See the comment in exchange._pulldiscoverychangegroup() for more.
296 # See the comment in exchange._pulldiscoverychangegroup() for more.
297
297
298 if fetch and remoteheads:
298 if fetch and remoteheads:
299 has_node = repo.unfiltered().changelog.index.has_node
299 has_node = repo.unfiltered().changelog.index.has_node
300
300
301 common |= {head for head in remoteheads if has_node(head)}
301 common |= {head for head in remoteheads if has_node(head)}
302
302
303 if set(remoteheads).issubset(common):
303 if set(remoteheads).issubset(common):
304 fetch = []
304 fetch = []
305
305
306 common.discard(nullid)
306 common.discard(nullid)
307
307
308 return common, fetch, remoteheads
308 return common, fetch, remoteheads
309
309
310
310
311 def _fetchchangesets(repo, tr, remote, common, fetch, remoteheads):
311 def _fetchchangesets(repo, tr, remote, common, fetch, remoteheads):
312 # TODO consider adding a step here where we obtain the DAG shape first
312 # TODO consider adding a step here where we obtain the DAG shape first
313 # (or ask the server to slice changesets into chunks for us) so that
313 # (or ask the server to slice changesets into chunks for us) so that
314 # we can perform multiple fetches in batches. This will facilitate
314 # we can perform multiple fetches in batches. This will facilitate
315 # resuming interrupted clones, higher server-side cache hit rates due
315 # resuming interrupted clones, higher server-side cache hit rates due
316 # to smaller segments, etc.
316 # to smaller segments, etc.
317 with remote.commandexecutor() as e:
317 with remote.commandexecutor() as e:
318 objs = e.callcommand(
318 objs = e.callcommand(
319 b'changesetdata',
319 b'changesetdata',
320 {
320 {
321 b'revisions': [
321 b'revisions': [
322 {
322 {
323 b'type': b'changesetdagrange',
323 b'type': b'changesetdagrange',
324 b'roots': sorted(common),
324 b'roots': sorted(common),
325 b'heads': sorted(remoteheads),
325 b'heads': sorted(remoteheads),
326 }
326 }
327 ],
327 ],
328 b'fields': {b'bookmarks', b'parents', b'phase', b'revision'},
328 b'fields': {b'bookmarks', b'parents', b'phase', b'revision'},
329 },
329 },
330 ).result()
330 ).result()
331
331
332 # The context manager waits on all response data when exiting. So
332 # The context manager waits on all response data when exiting. So
333 # we need to remain in the context manager in order to stream data.
333 # we need to remain in the context manager in order to stream data.
334 return _processchangesetdata(repo, tr, objs)
334 return _processchangesetdata(repo, tr, objs)
335
335
336
336
337 def _processchangesetdata(repo, tr, objs):
337 def _processchangesetdata(repo, tr, objs):
338 repo.hook(b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs))
338 repo.hook(b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs))
339
339
340 urepo = repo.unfiltered()
340 urepo = repo.unfiltered()
341 cl = urepo.changelog
341 cl = urepo.changelog
342
342
343 cl.delayupdate(tr)
343 cl.delayupdate(tr)
344
344
345 # The first emitted object is a header describing the data that
345 # The first emitted object is a header describing the data that
346 # follows.
346 # follows.
347 meta = next(objs)
347 meta = next(objs)
348
348
349 progress = repo.ui.makeprogress(
349 progress = repo.ui.makeprogress(
350 _(b'changesets'), unit=_(b'chunks'), total=meta.get(b'totalitems')
350 _(b'changesets'), unit=_(b'chunks'), total=meta.get(b'totalitems')
351 )
351 )
352
352
353 manifestnodes = {}
353 manifestnodes = {}
354 added = []
354 added = []
355
355
356 def linkrev(node):
356 def linkrev(node):
357 repo.ui.debug(b'add changeset %s\n' % short(node))
357 repo.ui.debug(b'add changeset %s\n' % short(node))
358 # Linkrev for changelog is always self.
358 # Linkrev for changelog is always self.
359 return len(cl)
359 return len(cl)
360
360
361 def ondupchangeset(cl, node):
361 def ondupchangeset(cl, rev):
362 added.append(node)
362 added.append(cl.node(rev))
363
363
364 def onchangeset(cl, node):
364 def onchangeset(cl, rev):
365 progress.increment()
365 progress.increment()
366
366
367 rev = cl.rev(node)
368 revision = cl.changelogrevision(rev)
367 revision = cl.changelogrevision(rev)
369 added.append(node)
368 added.append(cl.node(rev))
370
369
371 # We need to preserve the mapping of changelog revision to node
370 # We need to preserve the mapping of changelog revision to node
372 # so we can set the linkrev accordingly when manifests are added.
371 # so we can set the linkrev accordingly when manifests are added.
373 manifestnodes[rev] = revision.manifest
372 manifestnodes[rev] = revision.manifest
374
373
375 repo.register_changeset(rev, revision)
374 repo.register_changeset(rev, revision)
376
375
377 nodesbyphase = {phase: set() for phase in phases.phasenames.values()}
376 nodesbyphase = {phase: set() for phase in phases.phasenames.values()}
378 remotebookmarks = {}
377 remotebookmarks = {}
379
378
380 # addgroup() expects a 7-tuple describing revisions. This normalizes
379 # addgroup() expects a 7-tuple describing revisions. This normalizes
381 # the wire data to that format.
380 # the wire data to that format.
382 #
381 #
383 # This loop also aggregates non-revision metadata, such as phase
382 # This loop also aggregates non-revision metadata, such as phase
384 # data.
383 # data.
385 def iterrevisions():
384 def iterrevisions():
386 for cset in objs:
385 for cset in objs:
387 node = cset[b'node']
386 node = cset[b'node']
388
387
389 if b'phase' in cset:
388 if b'phase' in cset:
390 nodesbyphase[cset[b'phase']].add(node)
389 nodesbyphase[cset[b'phase']].add(node)
391
390
392 for mark in cset.get(b'bookmarks', []):
391 for mark in cset.get(b'bookmarks', []):
393 remotebookmarks[mark] = node
392 remotebookmarks[mark] = node
394
393
395 # TODO add mechanism for extensions to examine records so they
394 # TODO add mechanism for extensions to examine records so they
396 # can siphon off custom data fields.
395 # can siphon off custom data fields.
397
396
398 extrafields = {}
397 extrafields = {}
399
398
400 for field, size in cset.get(b'fieldsfollowing', []):
399 for field, size in cset.get(b'fieldsfollowing', []):
401 extrafields[field] = next(objs)
400 extrafields[field] = next(objs)
402
401
403 # Some entries might only be metadata only updates.
402 # Some entries might only be metadata only updates.
404 if b'revision' not in extrafields:
403 if b'revision' not in extrafields:
405 continue
404 continue
406
405
407 data = extrafields[b'revision']
406 data = extrafields[b'revision']
408
407
409 yield (
408 yield (
410 node,
409 node,
411 cset[b'parents'][0],
410 cset[b'parents'][0],
412 cset[b'parents'][1],
411 cset[b'parents'][1],
413 # Linknode is always itself for changesets.
412 # Linknode is always itself for changesets.
414 cset[b'node'],
413 cset[b'node'],
415 # We always send full revisions. So delta base is not set.
414 # We always send full revisions. So delta base is not set.
416 nullid,
415 nullid,
417 mdiff.trivialdiffheader(len(data)) + data,
416 mdiff.trivialdiffheader(len(data)) + data,
418 # Flags not yet supported.
417 # Flags not yet supported.
419 0,
418 0,
420 )
419 )
421
420
422 cl.addgroup(
421 cl.addgroup(
423 iterrevisions(),
422 iterrevisions(),
424 linkrev,
423 linkrev,
425 weakref.proxy(tr),
424 weakref.proxy(tr),
426 alwayscache=True,
425 alwayscache=True,
427 addrevisioncb=onchangeset,
426 addrevisioncb=onchangeset,
428 duplicaterevisioncb=ondupchangeset,
427 duplicaterevisioncb=ondupchangeset,
429 )
428 )
430
429
431 progress.complete()
430 progress.complete()
432
431
433 return {
432 return {
434 b'added': added,
433 b'added': added,
435 b'nodesbyphase': nodesbyphase,
434 b'nodesbyphase': nodesbyphase,
436 b'bookmarks': remotebookmarks,
435 b'bookmarks': remotebookmarks,
437 b'manifestnodes': manifestnodes,
436 b'manifestnodes': manifestnodes,
438 }
437 }
439
438
440
439
441 def _fetchmanifests(repo, tr, remote, manifestnodes):
440 def _fetchmanifests(repo, tr, remote, manifestnodes):
442 rootmanifest = repo.manifestlog.getstorage(b'')
441 rootmanifest = repo.manifestlog.getstorage(b'')
443
442
444 # Some manifests can be shared between changesets. Filter out revisions
443 # Some manifests can be shared between changesets. Filter out revisions
445 # we already know about.
444 # we already know about.
446 fetchnodes = []
445 fetchnodes = []
447 linkrevs = {}
446 linkrevs = {}
448 seen = set()
447 seen = set()
449
448
450 for clrev, node in sorted(pycompat.iteritems(manifestnodes)):
449 for clrev, node in sorted(pycompat.iteritems(manifestnodes)):
451 if node in seen:
450 if node in seen:
452 continue
451 continue
453
452
454 try:
453 try:
455 rootmanifest.rev(node)
454 rootmanifest.rev(node)
456 except error.LookupError:
455 except error.LookupError:
457 fetchnodes.append(node)
456 fetchnodes.append(node)
458 linkrevs[node] = clrev
457 linkrevs[node] = clrev
459
458
460 seen.add(node)
459 seen.add(node)
461
460
462 # TODO handle tree manifests
461 # TODO handle tree manifests
463
462
464 # addgroup() expects 7-tuple describing revisions. This normalizes
463 # addgroup() expects 7-tuple describing revisions. This normalizes
465 # the wire data to that format.
464 # the wire data to that format.
466 def iterrevisions(objs, progress):
465 def iterrevisions(objs, progress):
467 for manifest in objs:
466 for manifest in objs:
468 node = manifest[b'node']
467 node = manifest[b'node']
469
468
470 extrafields = {}
469 extrafields = {}
471
470
472 for field, size in manifest.get(b'fieldsfollowing', []):
471 for field, size in manifest.get(b'fieldsfollowing', []):
473 extrafields[field] = next(objs)
472 extrafields[field] = next(objs)
474
473
475 if b'delta' in extrafields:
474 if b'delta' in extrafields:
476 basenode = manifest[b'deltabasenode']
475 basenode = manifest[b'deltabasenode']
477 delta = extrafields[b'delta']
476 delta = extrafields[b'delta']
478 elif b'revision' in extrafields:
477 elif b'revision' in extrafields:
479 basenode = nullid
478 basenode = nullid
480 revision = extrafields[b'revision']
479 revision = extrafields[b'revision']
481 delta = mdiff.trivialdiffheader(len(revision)) + revision
480 delta = mdiff.trivialdiffheader(len(revision)) + revision
482 else:
481 else:
483 continue
482 continue
484
483
485 yield (
484 yield (
486 node,
485 node,
487 manifest[b'parents'][0],
486 manifest[b'parents'][0],
488 manifest[b'parents'][1],
487 manifest[b'parents'][1],
489 # The value passed in is passed to the lookup function passed
488 # The value passed in is passed to the lookup function passed
490 # to addgroup(). We already have a map of manifest node to
489 # to addgroup(). We already have a map of manifest node to
491 # changelog revision number. So we just pass in the
490 # changelog revision number. So we just pass in the
492 # manifest node here and use linkrevs.__getitem__ as the
491 # manifest node here and use linkrevs.__getitem__ as the
493 # resolution function.
492 # resolution function.
494 node,
493 node,
495 basenode,
494 basenode,
496 delta,
495 delta,
497 # Flags not yet supported.
496 # Flags not yet supported.
498 0,
497 0,
499 )
498 )
500
499
501 progress.increment()
500 progress.increment()
502
501
503 progress = repo.ui.makeprogress(
502 progress = repo.ui.makeprogress(
504 _(b'manifests'), unit=_(b'chunks'), total=len(fetchnodes)
503 _(b'manifests'), unit=_(b'chunks'), total=len(fetchnodes)
505 )
504 )
506
505
507 commandmeta = remote.apidescriptor[b'commands'][b'manifestdata']
506 commandmeta = remote.apidescriptor[b'commands'][b'manifestdata']
508 batchsize = commandmeta.get(b'recommendedbatchsize', 10000)
507 batchsize = commandmeta.get(b'recommendedbatchsize', 10000)
509 # TODO make size configurable on client?
508 # TODO make size configurable on client?
510
509
511 # We send commands 1 at a time to the remote. This is not the most
510 # We send commands 1 at a time to the remote. This is not the most
512 # efficient because we incur a round trip at the end of each batch.
511 # efficient because we incur a round trip at the end of each batch.
513 # However, the existing frame-based reactor keeps consuming server
512 # However, the existing frame-based reactor keeps consuming server
514 # data in the background. And this results in response data buffering
513 # data in the background. And this results in response data buffering
515 # in memory. This can consume gigabytes of memory.
514 # in memory. This can consume gigabytes of memory.
516 # TODO send multiple commands in a request once background buffering
515 # TODO send multiple commands in a request once background buffering
517 # issues are resolved.
516 # issues are resolved.
518
517
519 added = []
518 added = []
520
519
521 for i in pycompat.xrange(0, len(fetchnodes), batchsize):
520 for i in pycompat.xrange(0, len(fetchnodes), batchsize):
522 batch = [node for node in fetchnodes[i : i + batchsize]]
521 batch = [node for node in fetchnodes[i : i + batchsize]]
523 if not batch:
522 if not batch:
524 continue
523 continue
525
524
526 with remote.commandexecutor() as e:
525 with remote.commandexecutor() as e:
527 objs = e.callcommand(
526 objs = e.callcommand(
528 b'manifestdata',
527 b'manifestdata',
529 {
528 {
530 b'tree': b'',
529 b'tree': b'',
531 b'nodes': batch,
530 b'nodes': batch,
532 b'fields': {b'parents', b'revision'},
531 b'fields': {b'parents', b'revision'},
533 b'haveparents': True,
532 b'haveparents': True,
534 },
533 },
535 ).result()
534 ).result()
536
535
537 # Chomp off header object.
536 # Chomp off header object.
538 next(objs)
537 next(objs)
539
538
540 def onchangeset(cl, node):
539 def onchangeset(cl, rev):
541 added.append(node)
540 added.append(cl.node(rev))
542
541
543 rootmanifest.addgroup(
542 rootmanifest.addgroup(
544 iterrevisions(objs, progress),
543 iterrevisions(objs, progress),
545 linkrevs.__getitem__,
544 linkrevs.__getitem__,
546 weakref.proxy(tr),
545 weakref.proxy(tr),
547 addrevisioncb=onchangeset,
546 addrevisioncb=onchangeset,
548 duplicaterevisioncb=onchangeset,
547 duplicaterevisioncb=onchangeset,
549 )
548 )
550
549
551 progress.complete()
550 progress.complete()
552
551
553 return {
552 return {
554 b'added': added,
553 b'added': added,
555 b'linkrevs': linkrevs,
554 b'linkrevs': linkrevs,
556 }
555 }
557
556
558
557
559 def _derivefilesfrommanifests(repo, matcher, manifestnodes):
558 def _derivefilesfrommanifests(repo, matcher, manifestnodes):
560 """Determine what file nodes are relevant given a set of manifest nodes.
559 """Determine what file nodes are relevant given a set of manifest nodes.
561
560
562 Returns a dict mapping file paths to dicts of file node to first manifest
561 Returns a dict mapping file paths to dicts of file node to first manifest
563 node.
562 node.
564 """
563 """
565 ml = repo.manifestlog
564 ml = repo.manifestlog
566 fnodes = collections.defaultdict(dict)
565 fnodes = collections.defaultdict(dict)
567
566
568 progress = repo.ui.makeprogress(
567 progress = repo.ui.makeprogress(
569 _(b'scanning manifests'), total=len(manifestnodes)
568 _(b'scanning manifests'), total=len(manifestnodes)
570 )
569 )
571
570
572 with progress:
571 with progress:
573 for manifestnode in manifestnodes:
572 for manifestnode in manifestnodes:
574 m = ml.get(b'', manifestnode)
573 m = ml.get(b'', manifestnode)
575
574
576 # TODO this will pull in unwanted nodes because it takes the storage
575 # TODO this will pull in unwanted nodes because it takes the storage
577 # delta into consideration. What we really want is something that
576 # delta into consideration. What we really want is something that
578 # takes the delta between the manifest's parents. And ideally we
577 # takes the delta between the manifest's parents. And ideally we
579 # would ignore file nodes that are known locally. For now, ignore
578 # would ignore file nodes that are known locally. For now, ignore
580 # both these limitations. This will result in incremental fetches
579 # both these limitations. This will result in incremental fetches
581 # requesting data we already have. So this is far from ideal.
580 # requesting data we already have. So this is far from ideal.
582 md = m.readfast()
581 md = m.readfast()
583
582
584 for path, fnode in md.items():
583 for path, fnode in md.items():
585 if matcher(path):
584 if matcher(path):
586 fnodes[path].setdefault(fnode, manifestnode)
585 fnodes[path].setdefault(fnode, manifestnode)
587
586
588 progress.increment()
587 progress.increment()
589
588
590 return fnodes
589 return fnodes
591
590
592
591
593 def _fetchfiles(repo, tr, remote, fnodes, linkrevs):
592 def _fetchfiles(repo, tr, remote, fnodes, linkrevs):
594 """Fetch file data from explicit file revisions."""
593 """Fetch file data from explicit file revisions."""
595
594
596 def iterrevisions(objs, progress):
595 def iterrevisions(objs, progress):
597 for filerevision in objs:
596 for filerevision in objs:
598 node = filerevision[b'node']
597 node = filerevision[b'node']
599
598
600 extrafields = {}
599 extrafields = {}
601
600
602 for field, size in filerevision.get(b'fieldsfollowing', []):
601 for field, size in filerevision.get(b'fieldsfollowing', []):
603 extrafields[field] = next(objs)
602 extrafields[field] = next(objs)
604
603
605 if b'delta' in extrafields:
604 if b'delta' in extrafields:
606 basenode = filerevision[b'deltabasenode']
605 basenode = filerevision[b'deltabasenode']
607 delta = extrafields[b'delta']
606 delta = extrafields[b'delta']
608 elif b'revision' in extrafields:
607 elif b'revision' in extrafields:
609 basenode = nullid
608 basenode = nullid
610 revision = extrafields[b'revision']
609 revision = extrafields[b'revision']
611 delta = mdiff.trivialdiffheader(len(revision)) + revision
610 delta = mdiff.trivialdiffheader(len(revision)) + revision
612 else:
611 else:
613 continue
612 continue
614
613
615 yield (
614 yield (
616 node,
615 node,
617 filerevision[b'parents'][0],
616 filerevision[b'parents'][0],
618 filerevision[b'parents'][1],
617 filerevision[b'parents'][1],
619 node,
618 node,
620 basenode,
619 basenode,
621 delta,
620 delta,
622 # Flags not yet supported.
621 # Flags not yet supported.
623 0,
622 0,
624 )
623 )
625
624
626 progress.increment()
625 progress.increment()
627
626
628 progress = repo.ui.makeprogress(
627 progress = repo.ui.makeprogress(
629 _(b'files'),
628 _(b'files'),
630 unit=_(b'chunks'),
629 unit=_(b'chunks'),
631 total=sum(len(v) for v in pycompat.itervalues(fnodes)),
630 total=sum(len(v) for v in pycompat.itervalues(fnodes)),
632 )
631 )
633
632
634 # TODO make batch size configurable
633 # TODO make batch size configurable
635 batchsize = 10000
634 batchsize = 10000
636 fnodeslist = [x for x in sorted(fnodes.items())]
635 fnodeslist = [x for x in sorted(fnodes.items())]
637
636
638 for i in pycompat.xrange(0, len(fnodeslist), batchsize):
637 for i in pycompat.xrange(0, len(fnodeslist), batchsize):
639 batch = [x for x in fnodeslist[i : i + batchsize]]
638 batch = [x for x in fnodeslist[i : i + batchsize]]
640 if not batch:
639 if not batch:
641 continue
640 continue
642
641
643 with remote.commandexecutor() as e:
642 with remote.commandexecutor() as e:
644 fs = []
643 fs = []
645 locallinkrevs = {}
644 locallinkrevs = {}
646
645
647 for path, nodes in batch:
646 for path, nodes in batch:
648 fs.append(
647 fs.append(
649 (
648 (
650 path,
649 path,
651 e.callcommand(
650 e.callcommand(
652 b'filedata',
651 b'filedata',
653 {
652 {
654 b'path': path,
653 b'path': path,
655 b'nodes': sorted(nodes),
654 b'nodes': sorted(nodes),
656 b'fields': {b'parents', b'revision'},
655 b'fields': {b'parents', b'revision'},
657 b'haveparents': True,
656 b'haveparents': True,
658 },
657 },
659 ),
658 ),
660 )
659 )
661 )
660 )
662
661
663 locallinkrevs[path] = {
662 locallinkrevs[path] = {
664 node: linkrevs[manifestnode]
663 node: linkrevs[manifestnode]
665 for node, manifestnode in pycompat.iteritems(nodes)
664 for node, manifestnode in pycompat.iteritems(nodes)
666 }
665 }
667
666
668 for path, f in fs:
667 for path, f in fs:
669 objs = f.result()
668 objs = f.result()
670
669
671 # Chomp off header objects.
670 # Chomp off header objects.
672 next(objs)
671 next(objs)
673
672
674 store = repo.file(path)
673 store = repo.file(path)
675 store.addgroup(
674 store.addgroup(
676 iterrevisions(objs, progress),
675 iterrevisions(objs, progress),
677 locallinkrevs[path].__getitem__,
676 locallinkrevs[path].__getitem__,
678 weakref.proxy(tr),
677 weakref.proxy(tr),
679 )
678 )
680
679
681
680
682 def _fetchfilesfromcsets(
681 def _fetchfilesfromcsets(
683 repo, tr, remote, pathfilter, fnodes, csets, manlinkrevs, shallow=False
682 repo, tr, remote, pathfilter, fnodes, csets, manlinkrevs, shallow=False
684 ):
683 ):
685 """Fetch file data from explicit changeset revisions."""
684 """Fetch file data from explicit changeset revisions."""
686
685
687 def iterrevisions(objs, remaining, progress):
686 def iterrevisions(objs, remaining, progress):
688 while remaining:
687 while remaining:
689 filerevision = next(objs)
688 filerevision = next(objs)
690
689
691 node = filerevision[b'node']
690 node = filerevision[b'node']
692
691
693 extrafields = {}
692 extrafields = {}
694
693
695 for field, size in filerevision.get(b'fieldsfollowing', []):
694 for field, size in filerevision.get(b'fieldsfollowing', []):
696 extrafields[field] = next(objs)
695 extrafields[field] = next(objs)
697
696
698 if b'delta' in extrafields:
697 if b'delta' in extrafields:
699 basenode = filerevision[b'deltabasenode']
698 basenode = filerevision[b'deltabasenode']
700 delta = extrafields[b'delta']
699 delta = extrafields[b'delta']
701 elif b'revision' in extrafields:
700 elif b'revision' in extrafields:
702 basenode = nullid
701 basenode = nullid
703 revision = extrafields[b'revision']
702 revision = extrafields[b'revision']
704 delta = mdiff.trivialdiffheader(len(revision)) + revision
703 delta = mdiff.trivialdiffheader(len(revision)) + revision
705 else:
704 else:
706 continue
705 continue
707
706
708 if b'linknode' in filerevision:
707 if b'linknode' in filerevision:
709 linknode = filerevision[b'linknode']
708 linknode = filerevision[b'linknode']
710 else:
709 else:
711 linknode = node
710 linknode = node
712
711
713 yield (
712 yield (
714 node,
713 node,
715 filerevision[b'parents'][0],
714 filerevision[b'parents'][0],
716 filerevision[b'parents'][1],
715 filerevision[b'parents'][1],
717 linknode,
716 linknode,
718 basenode,
717 basenode,
719 delta,
718 delta,
720 # Flags not yet supported.
719 # Flags not yet supported.
721 0,
720 0,
722 )
721 )
723
722
724 progress.increment()
723 progress.increment()
725 remaining -= 1
724 remaining -= 1
726
725
727 progress = repo.ui.makeprogress(
726 progress = repo.ui.makeprogress(
728 _(b'files'),
727 _(b'files'),
729 unit=_(b'chunks'),
728 unit=_(b'chunks'),
730 total=sum(len(v) for v in pycompat.itervalues(fnodes)),
729 total=sum(len(v) for v in pycompat.itervalues(fnodes)),
731 )
730 )
732
731
733 commandmeta = remote.apidescriptor[b'commands'][b'filesdata']
732 commandmeta = remote.apidescriptor[b'commands'][b'filesdata']
734 batchsize = commandmeta.get(b'recommendedbatchsize', 50000)
733 batchsize = commandmeta.get(b'recommendedbatchsize', 50000)
735
734
736 shallowfiles = repository.REPO_FEATURE_SHALLOW_FILE_STORAGE in repo.features
735 shallowfiles = repository.REPO_FEATURE_SHALLOW_FILE_STORAGE in repo.features
737 fields = {b'parents', b'revision'}
736 fields = {b'parents', b'revision'}
738 clrev = repo.changelog.rev
737 clrev = repo.changelog.rev
739
738
740 # There are no guarantees that we'll have ancestor revisions if
739 # There are no guarantees that we'll have ancestor revisions if
741 # a) this repo has shallow file storage b) shallow data fetching is enabled.
740 # a) this repo has shallow file storage b) shallow data fetching is enabled.
742 # Force remote to not delta against possibly unknown revisions when these
741 # Force remote to not delta against possibly unknown revisions when these
743 # conditions hold.
742 # conditions hold.
744 haveparents = not (shallowfiles or shallow)
743 haveparents = not (shallowfiles or shallow)
745
744
746 # Similarly, we may not have calculated linkrevs for all incoming file
745 # Similarly, we may not have calculated linkrevs for all incoming file
747 # revisions. Ask the remote to do work for us in this case.
746 # revisions. Ask the remote to do work for us in this case.
748 if not haveparents:
747 if not haveparents:
749 fields.add(b'linknode')
748 fields.add(b'linknode')
750
749
751 for i in pycompat.xrange(0, len(csets), batchsize):
750 for i in pycompat.xrange(0, len(csets), batchsize):
752 batch = [x for x in csets[i : i + batchsize]]
751 batch = [x for x in csets[i : i + batchsize]]
753 if not batch:
752 if not batch:
754 continue
753 continue
755
754
756 with remote.commandexecutor() as e:
755 with remote.commandexecutor() as e:
757 args = {
756 args = {
758 b'revisions': [
757 b'revisions': [
759 {
758 {
760 b'type': b'changesetexplicit',
759 b'type': b'changesetexplicit',
761 b'nodes': batch,
760 b'nodes': batch,
762 }
761 }
763 ],
762 ],
764 b'fields': fields,
763 b'fields': fields,
765 b'haveparents': haveparents,
764 b'haveparents': haveparents,
766 }
765 }
767
766
768 if pathfilter:
767 if pathfilter:
769 args[b'pathfilter'] = pathfilter
768 args[b'pathfilter'] = pathfilter
770
769
771 objs = e.callcommand(b'filesdata', args).result()
770 objs = e.callcommand(b'filesdata', args).result()
772
771
773 # First object is an overall header.
772 # First object is an overall header.
774 overall = next(objs)
773 overall = next(objs)
775
774
776 # We have overall['totalpaths'] segments.
775 # We have overall['totalpaths'] segments.
777 for i in pycompat.xrange(overall[b'totalpaths']):
776 for i in pycompat.xrange(overall[b'totalpaths']):
778 header = next(objs)
777 header = next(objs)
779
778
780 path = header[b'path']
779 path = header[b'path']
781 store = repo.file(path)
780 store = repo.file(path)
782
781
783 linkrevs = {
782 linkrevs = {
784 fnode: manlinkrevs[mnode]
783 fnode: manlinkrevs[mnode]
785 for fnode, mnode in pycompat.iteritems(fnodes[path])
784 for fnode, mnode in pycompat.iteritems(fnodes[path])
786 }
785 }
787
786
788 def getlinkrev(node):
787 def getlinkrev(node):
789 if node in linkrevs:
788 if node in linkrevs:
790 return linkrevs[node]
789 return linkrevs[node]
791 else:
790 else:
792 return clrev(node)
791 return clrev(node)
793
792
794 store.addgroup(
793 store.addgroup(
795 iterrevisions(objs, header[b'totalitems'], progress),
794 iterrevisions(objs, header[b'totalitems'], progress),
796 getlinkrev,
795 getlinkrev,
797 weakref.proxy(tr),
796 weakref.proxy(tr),
798 maybemissingparents=shallow,
797 maybemissingparents=shallow,
799 )
798 )
@@ -1,1993 +1,1994 b''
1 # repository.py - Interfaces and base classes for repositories and peers.
1 # repository.py - Interfaces and base classes for repositories and peers.
2 #
2 #
3 # Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from ..i18n import _
10 from ..i18n import _
11 from .. import error
11 from .. import error
12 from . import util as interfaceutil
12 from . import util as interfaceutil
13
13
14 # Local repository feature string.
14 # Local repository feature string.
15
15
16 # Revlogs are being used for file storage.
16 # Revlogs are being used for file storage.
17 REPO_FEATURE_REVLOG_FILE_STORAGE = b'revlogfilestorage'
17 REPO_FEATURE_REVLOG_FILE_STORAGE = b'revlogfilestorage'
18 # The storage part of the repository is shared from an external source.
18 # The storage part of the repository is shared from an external source.
19 REPO_FEATURE_SHARED_STORAGE = b'sharedstore'
19 REPO_FEATURE_SHARED_STORAGE = b'sharedstore'
20 # LFS supported for backing file storage.
20 # LFS supported for backing file storage.
21 REPO_FEATURE_LFS = b'lfs'
21 REPO_FEATURE_LFS = b'lfs'
22 # Repository supports being stream cloned.
22 # Repository supports being stream cloned.
23 REPO_FEATURE_STREAM_CLONE = b'streamclone'
23 REPO_FEATURE_STREAM_CLONE = b'streamclone'
24 # Files storage may lack data for all ancestors.
24 # Files storage may lack data for all ancestors.
25 REPO_FEATURE_SHALLOW_FILE_STORAGE = b'shallowfilestorage'
25 REPO_FEATURE_SHALLOW_FILE_STORAGE = b'shallowfilestorage'
26
26
27 REVISION_FLAG_CENSORED = 1 << 15
27 REVISION_FLAG_CENSORED = 1 << 15
28 REVISION_FLAG_ELLIPSIS = 1 << 14
28 REVISION_FLAG_ELLIPSIS = 1 << 14
29 REVISION_FLAG_EXTSTORED = 1 << 13
29 REVISION_FLAG_EXTSTORED = 1 << 13
30 REVISION_FLAG_SIDEDATA = 1 << 12
30 REVISION_FLAG_SIDEDATA = 1 << 12
31 REVISION_FLAG_HASCOPIESINFO = 1 << 11
31 REVISION_FLAG_HASCOPIESINFO = 1 << 11
32
32
33 REVISION_FLAGS_KNOWN = (
33 REVISION_FLAGS_KNOWN = (
34 REVISION_FLAG_CENSORED
34 REVISION_FLAG_CENSORED
35 | REVISION_FLAG_ELLIPSIS
35 | REVISION_FLAG_ELLIPSIS
36 | REVISION_FLAG_EXTSTORED
36 | REVISION_FLAG_EXTSTORED
37 | REVISION_FLAG_SIDEDATA
37 | REVISION_FLAG_SIDEDATA
38 | REVISION_FLAG_HASCOPIESINFO
38 | REVISION_FLAG_HASCOPIESINFO
39 )
39 )
40
40
41 CG_DELTAMODE_STD = b'default'
41 CG_DELTAMODE_STD = b'default'
42 CG_DELTAMODE_PREV = b'previous'
42 CG_DELTAMODE_PREV = b'previous'
43 CG_DELTAMODE_FULL = b'fulltext'
43 CG_DELTAMODE_FULL = b'fulltext'
44 CG_DELTAMODE_P1 = b'p1'
44 CG_DELTAMODE_P1 = b'p1'
45
45
46
46
47 class ipeerconnection(interfaceutil.Interface):
47 class ipeerconnection(interfaceutil.Interface):
48 """Represents a "connection" to a repository.
48 """Represents a "connection" to a repository.
49
49
50 This is the base interface for representing a connection to a repository.
50 This is the base interface for representing a connection to a repository.
51 It holds basic properties and methods applicable to all peer types.
51 It holds basic properties and methods applicable to all peer types.
52
52
53 This is not a complete interface definition and should not be used
53 This is not a complete interface definition and should not be used
54 outside of this module.
54 outside of this module.
55 """
55 """
56
56
57 ui = interfaceutil.Attribute("""ui.ui instance""")
57 ui = interfaceutil.Attribute("""ui.ui instance""")
58
58
59 def url():
59 def url():
60 """Returns a URL string representing this peer.
60 """Returns a URL string representing this peer.
61
61
62 Currently, implementations expose the raw URL used to construct the
62 Currently, implementations expose the raw URL used to construct the
63 instance. It may contain credentials as part of the URL. The
63 instance. It may contain credentials as part of the URL. The
64 expectations of the value aren't well-defined and this could lead to
64 expectations of the value aren't well-defined and this could lead to
65 data leakage.
65 data leakage.
66
66
67 TODO audit/clean consumers and more clearly define the contents of this
67 TODO audit/clean consumers and more clearly define the contents of this
68 value.
68 value.
69 """
69 """
70
70
71 def local():
71 def local():
72 """Returns a local repository instance.
72 """Returns a local repository instance.
73
73
74 If the peer represents a local repository, returns an object that
74 If the peer represents a local repository, returns an object that
75 can be used to interface with it. Otherwise returns ``None``.
75 can be used to interface with it. Otherwise returns ``None``.
76 """
76 """
77
77
78 def peer():
78 def peer():
79 """Returns an object conforming to this interface.
79 """Returns an object conforming to this interface.
80
80
81 Most implementations will ``return self``.
81 Most implementations will ``return self``.
82 """
82 """
83
83
84 def canpush():
84 def canpush():
85 """Returns a boolean indicating if this peer can be pushed to."""
85 """Returns a boolean indicating if this peer can be pushed to."""
86
86
87 def close():
87 def close():
88 """Close the connection to this peer.
88 """Close the connection to this peer.
89
89
90 This is called when the peer will no longer be used. Resources
90 This is called when the peer will no longer be used. Resources
91 associated with the peer should be cleaned up.
91 associated with the peer should be cleaned up.
92 """
92 """
93
93
94
94
95 class ipeercapabilities(interfaceutil.Interface):
95 class ipeercapabilities(interfaceutil.Interface):
96 """Peer sub-interface related to capabilities."""
96 """Peer sub-interface related to capabilities."""
97
97
98 def capable(name):
98 def capable(name):
99 """Determine support for a named capability.
99 """Determine support for a named capability.
100
100
101 Returns ``False`` if capability not supported.
101 Returns ``False`` if capability not supported.
102
102
103 Returns ``True`` if boolean capability is supported. Returns a string
103 Returns ``True`` if boolean capability is supported. Returns a string
104 if capability support is non-boolean.
104 if capability support is non-boolean.
105
105
106 Capability strings may or may not map to wire protocol capabilities.
106 Capability strings may or may not map to wire protocol capabilities.
107 """
107 """
108
108
109 def requirecap(name, purpose):
109 def requirecap(name, purpose):
110 """Require a capability to be present.
110 """Require a capability to be present.
111
111
112 Raises a ``CapabilityError`` if the capability isn't present.
112 Raises a ``CapabilityError`` if the capability isn't present.
113 """
113 """
114
114
115
115
116 class ipeercommands(interfaceutil.Interface):
116 class ipeercommands(interfaceutil.Interface):
117 """Client-side interface for communicating over the wire protocol.
117 """Client-side interface for communicating over the wire protocol.
118
118
119 This interface is used as a gateway to the Mercurial wire protocol.
119 This interface is used as a gateway to the Mercurial wire protocol.
120 methods commonly call wire protocol commands of the same name.
120 methods commonly call wire protocol commands of the same name.
121 """
121 """
122
122
123 def branchmap():
123 def branchmap():
124 """Obtain heads in named branches.
124 """Obtain heads in named branches.
125
125
126 Returns a dict mapping branch name to an iterable of nodes that are
126 Returns a dict mapping branch name to an iterable of nodes that are
127 heads on that branch.
127 heads on that branch.
128 """
128 """
129
129
130 def capabilities():
130 def capabilities():
131 """Obtain capabilities of the peer.
131 """Obtain capabilities of the peer.
132
132
133 Returns a set of string capabilities.
133 Returns a set of string capabilities.
134 """
134 """
135
135
136 def clonebundles():
136 def clonebundles():
137 """Obtains the clone bundles manifest for the repo.
137 """Obtains the clone bundles manifest for the repo.
138
138
139 Returns the manifest as unparsed bytes.
139 Returns the manifest as unparsed bytes.
140 """
140 """
141
141
142 def debugwireargs(one, two, three=None, four=None, five=None):
142 def debugwireargs(one, two, three=None, four=None, five=None):
143 """Used to facilitate debugging of arguments passed over the wire."""
143 """Used to facilitate debugging of arguments passed over the wire."""
144
144
145 def getbundle(source, **kwargs):
145 def getbundle(source, **kwargs):
146 """Obtain remote repository data as a bundle.
146 """Obtain remote repository data as a bundle.
147
147
148 This command is how the bulk of repository data is transferred from
148 This command is how the bulk of repository data is transferred from
149 the peer to the local repository
149 the peer to the local repository
150
150
151 Returns a generator of bundle data.
151 Returns a generator of bundle data.
152 """
152 """
153
153
154 def heads():
154 def heads():
155 """Determine all known head revisions in the peer.
155 """Determine all known head revisions in the peer.
156
156
157 Returns an iterable of binary nodes.
157 Returns an iterable of binary nodes.
158 """
158 """
159
159
160 def known(nodes):
160 def known(nodes):
161 """Determine whether multiple nodes are known.
161 """Determine whether multiple nodes are known.
162
162
163 Accepts an iterable of nodes whose presence to check for.
163 Accepts an iterable of nodes whose presence to check for.
164
164
165 Returns an iterable of booleans indicating of the corresponding node
165 Returns an iterable of booleans indicating of the corresponding node
166 at that index is known to the peer.
166 at that index is known to the peer.
167 """
167 """
168
168
169 def listkeys(namespace):
169 def listkeys(namespace):
170 """Obtain all keys in a pushkey namespace.
170 """Obtain all keys in a pushkey namespace.
171
171
172 Returns an iterable of key names.
172 Returns an iterable of key names.
173 """
173 """
174
174
175 def lookup(key):
175 def lookup(key):
176 """Resolve a value to a known revision.
176 """Resolve a value to a known revision.
177
177
178 Returns a binary node of the resolved revision on success.
178 Returns a binary node of the resolved revision on success.
179 """
179 """
180
180
181 def pushkey(namespace, key, old, new):
181 def pushkey(namespace, key, old, new):
182 """Set a value using the ``pushkey`` protocol.
182 """Set a value using the ``pushkey`` protocol.
183
183
184 Arguments correspond to the pushkey namespace and key to operate on and
184 Arguments correspond to the pushkey namespace and key to operate on and
185 the old and new values for that key.
185 the old and new values for that key.
186
186
187 Returns a string with the peer result. The value inside varies by the
187 Returns a string with the peer result. The value inside varies by the
188 namespace.
188 namespace.
189 """
189 """
190
190
191 def stream_out():
191 def stream_out():
192 """Obtain streaming clone data.
192 """Obtain streaming clone data.
193
193
194 Successful result should be a generator of data chunks.
194 Successful result should be a generator of data chunks.
195 """
195 """
196
196
197 def unbundle(bundle, heads, url):
197 def unbundle(bundle, heads, url):
198 """Transfer repository data to the peer.
198 """Transfer repository data to the peer.
199
199
200 This is how the bulk of data during a push is transferred.
200 This is how the bulk of data during a push is transferred.
201
201
202 Returns the integer number of heads added to the peer.
202 Returns the integer number of heads added to the peer.
203 """
203 """
204
204
205
205
206 class ipeerlegacycommands(interfaceutil.Interface):
206 class ipeerlegacycommands(interfaceutil.Interface):
207 """Interface for implementing support for legacy wire protocol commands.
207 """Interface for implementing support for legacy wire protocol commands.
208
208
209 Wire protocol commands transition to legacy status when they are no longer
209 Wire protocol commands transition to legacy status when they are no longer
210 used by modern clients. To facilitate identifying which commands are
210 used by modern clients. To facilitate identifying which commands are
211 legacy, the interfaces are split.
211 legacy, the interfaces are split.
212 """
212 """
213
213
214 def between(pairs):
214 def between(pairs):
215 """Obtain nodes between pairs of nodes.
215 """Obtain nodes between pairs of nodes.
216
216
217 ``pairs`` is an iterable of node pairs.
217 ``pairs`` is an iterable of node pairs.
218
218
219 Returns an iterable of iterables of nodes corresponding to each
219 Returns an iterable of iterables of nodes corresponding to each
220 requested pair.
220 requested pair.
221 """
221 """
222
222
223 def branches(nodes):
223 def branches(nodes):
224 """Obtain ancestor changesets of specific nodes back to a branch point.
224 """Obtain ancestor changesets of specific nodes back to a branch point.
225
225
226 For each requested node, the peer finds the first ancestor node that is
226 For each requested node, the peer finds the first ancestor node that is
227 a DAG root or is a merge.
227 a DAG root or is a merge.
228
228
229 Returns an iterable of iterables with the resolved values for each node.
229 Returns an iterable of iterables with the resolved values for each node.
230 """
230 """
231
231
232 def changegroup(nodes, source):
232 def changegroup(nodes, source):
233 """Obtain a changegroup with data for descendants of specified nodes."""
233 """Obtain a changegroup with data for descendants of specified nodes."""
234
234
235 def changegroupsubset(bases, heads, source):
235 def changegroupsubset(bases, heads, source):
236 pass
236 pass
237
237
238
238
239 class ipeercommandexecutor(interfaceutil.Interface):
239 class ipeercommandexecutor(interfaceutil.Interface):
240 """Represents a mechanism to execute remote commands.
240 """Represents a mechanism to execute remote commands.
241
241
242 This is the primary interface for requesting that wire protocol commands
242 This is the primary interface for requesting that wire protocol commands
243 be executed. Instances of this interface are active in a context manager
243 be executed. Instances of this interface are active in a context manager
244 and have a well-defined lifetime. When the context manager exits, all
244 and have a well-defined lifetime. When the context manager exits, all
245 outstanding requests are waited on.
245 outstanding requests are waited on.
246 """
246 """
247
247
248 def callcommand(name, args):
248 def callcommand(name, args):
249 """Request that a named command be executed.
249 """Request that a named command be executed.
250
250
251 Receives the command name and a dictionary of command arguments.
251 Receives the command name and a dictionary of command arguments.
252
252
253 Returns a ``concurrent.futures.Future`` that will resolve to the
253 Returns a ``concurrent.futures.Future`` that will resolve to the
254 result of that command request. That exact value is left up to
254 result of that command request. That exact value is left up to
255 the implementation and possibly varies by command.
255 the implementation and possibly varies by command.
256
256
257 Not all commands can coexist with other commands in an executor
257 Not all commands can coexist with other commands in an executor
258 instance: it depends on the underlying wire protocol transport being
258 instance: it depends on the underlying wire protocol transport being
259 used and the command itself.
259 used and the command itself.
260
260
261 Implementations MAY call ``sendcommands()`` automatically if the
261 Implementations MAY call ``sendcommands()`` automatically if the
262 requested command can not coexist with other commands in this executor.
262 requested command can not coexist with other commands in this executor.
263
263
264 Implementations MAY call ``sendcommands()`` automatically when the
264 Implementations MAY call ``sendcommands()`` automatically when the
265 future's ``result()`` is called. So, consumers using multiple
265 future's ``result()`` is called. So, consumers using multiple
266 commands with an executor MUST ensure that ``result()`` is not called
266 commands with an executor MUST ensure that ``result()`` is not called
267 until all command requests have been issued.
267 until all command requests have been issued.
268 """
268 """
269
269
270 def sendcommands():
270 def sendcommands():
271 """Trigger submission of queued command requests.
271 """Trigger submission of queued command requests.
272
272
273 Not all transports submit commands as soon as they are requested to
273 Not all transports submit commands as soon as they are requested to
274 run. When called, this method forces queued command requests to be
274 run. When called, this method forces queued command requests to be
275 issued. It will no-op if all commands have already been sent.
275 issued. It will no-op if all commands have already been sent.
276
276
277 When called, no more new commands may be issued with this executor.
277 When called, no more new commands may be issued with this executor.
278 """
278 """
279
279
280 def close():
280 def close():
281 """Signal that this command request is finished.
281 """Signal that this command request is finished.
282
282
283 When called, no more new commands may be issued. All outstanding
283 When called, no more new commands may be issued. All outstanding
284 commands that have previously been issued are waited on before
284 commands that have previously been issued are waited on before
285 returning. This not only includes waiting for the futures to resolve,
285 returning. This not only includes waiting for the futures to resolve,
286 but also waiting for all response data to arrive. In other words,
286 but also waiting for all response data to arrive. In other words,
287 calling this waits for all on-wire state for issued command requests
287 calling this waits for all on-wire state for issued command requests
288 to finish.
288 to finish.
289
289
290 When used as a context manager, this method is called when exiting the
290 When used as a context manager, this method is called when exiting the
291 context manager.
291 context manager.
292
292
293 This method may call ``sendcommands()`` if there are buffered commands.
293 This method may call ``sendcommands()`` if there are buffered commands.
294 """
294 """
295
295
296
296
297 class ipeerrequests(interfaceutil.Interface):
297 class ipeerrequests(interfaceutil.Interface):
298 """Interface for executing commands on a peer."""
298 """Interface for executing commands on a peer."""
299
299
300 limitedarguments = interfaceutil.Attribute(
300 limitedarguments = interfaceutil.Attribute(
301 """True if the peer cannot receive large argument value for commands."""
301 """True if the peer cannot receive large argument value for commands."""
302 )
302 )
303
303
304 def commandexecutor():
304 def commandexecutor():
305 """A context manager that resolves to an ipeercommandexecutor.
305 """A context manager that resolves to an ipeercommandexecutor.
306
306
307 The object this resolves to can be used to issue command requests
307 The object this resolves to can be used to issue command requests
308 to the peer.
308 to the peer.
309
309
310 Callers should call its ``callcommand`` method to issue command
310 Callers should call its ``callcommand`` method to issue command
311 requests.
311 requests.
312
312
313 A new executor should be obtained for each distinct set of commands
313 A new executor should be obtained for each distinct set of commands
314 (possibly just a single command) that the consumer wants to execute
314 (possibly just a single command) that the consumer wants to execute
315 as part of a single operation or round trip. This is because some
315 as part of a single operation or round trip. This is because some
316 peers are half-duplex and/or don't support persistent connections.
316 peers are half-duplex and/or don't support persistent connections.
317 e.g. in the case of HTTP peers, commands sent to an executor represent
317 e.g. in the case of HTTP peers, commands sent to an executor represent
318 a single HTTP request. While some peers may support multiple command
318 a single HTTP request. While some peers may support multiple command
319 sends over the wire per executor, consumers need to code to the least
319 sends over the wire per executor, consumers need to code to the least
320 capable peer. So it should be assumed that command executors buffer
320 capable peer. So it should be assumed that command executors buffer
321 called commands until they are told to send them and that each
321 called commands until they are told to send them and that each
322 command executor could result in a new connection or wire-level request
322 command executor could result in a new connection or wire-level request
323 being issued.
323 being issued.
324 """
324 """
325
325
326
326
327 class ipeerbase(ipeerconnection, ipeercapabilities, ipeerrequests):
327 class ipeerbase(ipeerconnection, ipeercapabilities, ipeerrequests):
328 """Unified interface for peer repositories.
328 """Unified interface for peer repositories.
329
329
330 All peer instances must conform to this interface.
330 All peer instances must conform to this interface.
331 """
331 """
332
332
333
333
334 class ipeerv2(ipeerconnection, ipeercapabilities, ipeerrequests):
334 class ipeerv2(ipeerconnection, ipeercapabilities, ipeerrequests):
335 """Unified peer interface for wire protocol version 2 peers."""
335 """Unified peer interface for wire protocol version 2 peers."""
336
336
337 apidescriptor = interfaceutil.Attribute(
337 apidescriptor = interfaceutil.Attribute(
338 """Data structure holding description of server API."""
338 """Data structure holding description of server API."""
339 )
339 )
340
340
341
341
342 @interfaceutil.implementer(ipeerbase)
342 @interfaceutil.implementer(ipeerbase)
343 class peer(object):
343 class peer(object):
344 """Base class for peer repositories."""
344 """Base class for peer repositories."""
345
345
346 limitedarguments = False
346 limitedarguments = False
347
347
348 def capable(self, name):
348 def capable(self, name):
349 caps = self.capabilities()
349 caps = self.capabilities()
350 if name in caps:
350 if name in caps:
351 return True
351 return True
352
352
353 name = b'%s=' % name
353 name = b'%s=' % name
354 for cap in caps:
354 for cap in caps:
355 if cap.startswith(name):
355 if cap.startswith(name):
356 return cap[len(name) :]
356 return cap[len(name) :]
357
357
358 return False
358 return False
359
359
360 def requirecap(self, name, purpose):
360 def requirecap(self, name, purpose):
361 if self.capable(name):
361 if self.capable(name):
362 return
362 return
363
363
364 raise error.CapabilityError(
364 raise error.CapabilityError(
365 _(
365 _(
366 b'cannot %s; remote repository does not support the '
366 b'cannot %s; remote repository does not support the '
367 b'\'%s\' capability'
367 b'\'%s\' capability'
368 )
368 )
369 % (purpose, name)
369 % (purpose, name)
370 )
370 )
371
371
372
372
373 class iverifyproblem(interfaceutil.Interface):
373 class iverifyproblem(interfaceutil.Interface):
374 """Represents a problem with the integrity of the repository.
374 """Represents a problem with the integrity of the repository.
375
375
376 Instances of this interface are emitted to describe an integrity issue
376 Instances of this interface are emitted to describe an integrity issue
377 with a repository (e.g. corrupt storage, missing data, etc).
377 with a repository (e.g. corrupt storage, missing data, etc).
378
378
379 Instances are essentially messages associated with severity.
379 Instances are essentially messages associated with severity.
380 """
380 """
381
381
382 warning = interfaceutil.Attribute(
382 warning = interfaceutil.Attribute(
383 """Message indicating a non-fatal problem."""
383 """Message indicating a non-fatal problem."""
384 )
384 )
385
385
386 error = interfaceutil.Attribute("""Message indicating a fatal problem.""")
386 error = interfaceutil.Attribute("""Message indicating a fatal problem.""")
387
387
388 node = interfaceutil.Attribute(
388 node = interfaceutil.Attribute(
389 """Revision encountering the problem.
389 """Revision encountering the problem.
390
390
391 ``None`` means the problem doesn't apply to a single revision.
391 ``None`` means the problem doesn't apply to a single revision.
392 """
392 """
393 )
393 )
394
394
395
395
396 class irevisiondelta(interfaceutil.Interface):
396 class irevisiondelta(interfaceutil.Interface):
397 """Represents a delta between one revision and another.
397 """Represents a delta between one revision and another.
398
398
399 Instances convey enough information to allow a revision to be exchanged
399 Instances convey enough information to allow a revision to be exchanged
400 with another repository.
400 with another repository.
401
401
402 Instances represent the fulltext revision data or a delta against
402 Instances represent the fulltext revision data or a delta against
403 another revision. Therefore the ``revision`` and ``delta`` attributes
403 another revision. Therefore the ``revision`` and ``delta`` attributes
404 are mutually exclusive.
404 are mutually exclusive.
405
405
406 Typically used for changegroup generation.
406 Typically used for changegroup generation.
407 """
407 """
408
408
409 node = interfaceutil.Attribute("""20 byte node of this revision.""")
409 node = interfaceutil.Attribute("""20 byte node of this revision.""")
410
410
411 p1node = interfaceutil.Attribute(
411 p1node = interfaceutil.Attribute(
412 """20 byte node of 1st parent of this revision."""
412 """20 byte node of 1st parent of this revision."""
413 )
413 )
414
414
415 p2node = interfaceutil.Attribute(
415 p2node = interfaceutil.Attribute(
416 """20 byte node of 2nd parent of this revision."""
416 """20 byte node of 2nd parent of this revision."""
417 )
417 )
418
418
419 linknode = interfaceutil.Attribute(
419 linknode = interfaceutil.Attribute(
420 """20 byte node of the changelog revision this node is linked to."""
420 """20 byte node of the changelog revision this node is linked to."""
421 )
421 )
422
422
423 flags = interfaceutil.Attribute(
423 flags = interfaceutil.Attribute(
424 """2 bytes of integer flags that apply to this revision.
424 """2 bytes of integer flags that apply to this revision.
425
425
426 This is a bitwise composition of the ``REVISION_FLAG_*`` constants.
426 This is a bitwise composition of the ``REVISION_FLAG_*`` constants.
427 """
427 """
428 )
428 )
429
429
430 basenode = interfaceutil.Attribute(
430 basenode = interfaceutil.Attribute(
431 """20 byte node of the revision this data is a delta against.
431 """20 byte node of the revision this data is a delta against.
432
432
433 ``nullid`` indicates that the revision is a full revision and not
433 ``nullid`` indicates that the revision is a full revision and not
434 a delta.
434 a delta.
435 """
435 """
436 )
436 )
437
437
438 baserevisionsize = interfaceutil.Attribute(
438 baserevisionsize = interfaceutil.Attribute(
439 """Size of base revision this delta is against.
439 """Size of base revision this delta is against.
440
440
441 May be ``None`` if ``basenode`` is ``nullid``.
441 May be ``None`` if ``basenode`` is ``nullid``.
442 """
442 """
443 )
443 )
444
444
445 revision = interfaceutil.Attribute(
445 revision = interfaceutil.Attribute(
446 """Raw fulltext of revision data for this node."""
446 """Raw fulltext of revision data for this node."""
447 )
447 )
448
448
449 delta = interfaceutil.Attribute(
449 delta = interfaceutil.Attribute(
450 """Delta between ``basenode`` and ``node``.
450 """Delta between ``basenode`` and ``node``.
451
451
452 Stored in the bdiff delta format.
452 Stored in the bdiff delta format.
453 """
453 """
454 )
454 )
455
455
456
456
457 class ifilerevisionssequence(interfaceutil.Interface):
457 class ifilerevisionssequence(interfaceutil.Interface):
458 """Contains index data for all revisions of a file.
458 """Contains index data for all revisions of a file.
459
459
460 Types implementing this behave like lists of tuples. The index
460 Types implementing this behave like lists of tuples. The index
461 in the list corresponds to the revision number. The values contain
461 in the list corresponds to the revision number. The values contain
462 index metadata.
462 index metadata.
463
463
464 The *null* revision (revision number -1) is always the last item
464 The *null* revision (revision number -1) is always the last item
465 in the index.
465 in the index.
466 """
466 """
467
467
468 def __len__():
468 def __len__():
469 """The total number of revisions."""
469 """The total number of revisions."""
470
470
471 def __getitem__(rev):
471 def __getitem__(rev):
472 """Returns the object having a specific revision number.
472 """Returns the object having a specific revision number.
473
473
474 Returns an 8-tuple with the following fields:
474 Returns an 8-tuple with the following fields:
475
475
476 offset+flags
476 offset+flags
477 Contains the offset and flags for the revision. 64-bit unsigned
477 Contains the offset and flags for the revision. 64-bit unsigned
478 integer where first 6 bytes are the offset and the next 2 bytes
478 integer where first 6 bytes are the offset and the next 2 bytes
479 are flags. The offset can be 0 if it is not used by the store.
479 are flags. The offset can be 0 if it is not used by the store.
480 compressed size
480 compressed size
481 Size of the revision data in the store. It can be 0 if it isn't
481 Size of the revision data in the store. It can be 0 if it isn't
482 needed by the store.
482 needed by the store.
483 uncompressed size
483 uncompressed size
484 Fulltext size. It can be 0 if it isn't needed by the store.
484 Fulltext size. It can be 0 if it isn't needed by the store.
485 base revision
485 base revision
486 Revision number of revision the delta for storage is encoded
486 Revision number of revision the delta for storage is encoded
487 against. -1 indicates not encoded against a base revision.
487 against. -1 indicates not encoded against a base revision.
488 link revision
488 link revision
489 Revision number of changelog revision this entry is related to.
489 Revision number of changelog revision this entry is related to.
490 p1 revision
490 p1 revision
491 Revision number of 1st parent. -1 if no 1st parent.
491 Revision number of 1st parent. -1 if no 1st parent.
492 p2 revision
492 p2 revision
493 Revision number of 2nd parent. -1 if no 1st parent.
493 Revision number of 2nd parent. -1 if no 1st parent.
494 node
494 node
495 Binary node value for this revision number.
495 Binary node value for this revision number.
496
496
497 Negative values should index off the end of the sequence. ``-1``
497 Negative values should index off the end of the sequence. ``-1``
498 should return the null revision. ``-2`` should return the most
498 should return the null revision. ``-2`` should return the most
499 recent revision.
499 recent revision.
500 """
500 """
501
501
502 def __contains__(rev):
502 def __contains__(rev):
503 """Whether a revision number exists."""
503 """Whether a revision number exists."""
504
504
505 def insert(self, i, entry):
505 def insert(self, i, entry):
506 """Add an item to the index at specific revision."""
506 """Add an item to the index at specific revision."""
507
507
508
508
509 class ifileindex(interfaceutil.Interface):
509 class ifileindex(interfaceutil.Interface):
510 """Storage interface for index data of a single file.
510 """Storage interface for index data of a single file.
511
511
512 File storage data is divided into index metadata and data storage.
512 File storage data is divided into index metadata and data storage.
513 This interface defines the index portion of the interface.
513 This interface defines the index portion of the interface.
514
514
515 The index logically consists of:
515 The index logically consists of:
516
516
517 * A mapping between revision numbers and nodes.
517 * A mapping between revision numbers and nodes.
518 * DAG data (storing and querying the relationship between nodes).
518 * DAG data (storing and querying the relationship between nodes).
519 * Metadata to facilitate storage.
519 * Metadata to facilitate storage.
520 """
520 """
521
521
522 def __len__():
522 def __len__():
523 """Obtain the number of revisions stored for this file."""
523 """Obtain the number of revisions stored for this file."""
524
524
525 def __iter__():
525 def __iter__():
526 """Iterate over revision numbers for this file."""
526 """Iterate over revision numbers for this file."""
527
527
528 def hasnode(node):
528 def hasnode(node):
529 """Returns a bool indicating if a node is known to this store.
529 """Returns a bool indicating if a node is known to this store.
530
530
531 Implementations must only return True for full, binary node values:
531 Implementations must only return True for full, binary node values:
532 hex nodes, revision numbers, and partial node matches must be
532 hex nodes, revision numbers, and partial node matches must be
533 rejected.
533 rejected.
534
534
535 The null node is never present.
535 The null node is never present.
536 """
536 """
537
537
538 def revs(start=0, stop=None):
538 def revs(start=0, stop=None):
539 """Iterate over revision numbers for this file, with control."""
539 """Iterate over revision numbers for this file, with control."""
540
540
541 def parents(node):
541 def parents(node):
542 """Returns a 2-tuple of parent nodes for a revision.
542 """Returns a 2-tuple of parent nodes for a revision.
543
543
544 Values will be ``nullid`` if the parent is empty.
544 Values will be ``nullid`` if the parent is empty.
545 """
545 """
546
546
547 def parentrevs(rev):
547 def parentrevs(rev):
548 """Like parents() but operates on revision numbers."""
548 """Like parents() but operates on revision numbers."""
549
549
550 def rev(node):
550 def rev(node):
551 """Obtain the revision number given a node.
551 """Obtain the revision number given a node.
552
552
553 Raises ``error.LookupError`` if the node is not known.
553 Raises ``error.LookupError`` if the node is not known.
554 """
554 """
555
555
556 def node(rev):
556 def node(rev):
557 """Obtain the node value given a revision number.
557 """Obtain the node value given a revision number.
558
558
559 Raises ``IndexError`` if the node is not known.
559 Raises ``IndexError`` if the node is not known.
560 """
560 """
561
561
562 def lookup(node):
562 def lookup(node):
563 """Attempt to resolve a value to a node.
563 """Attempt to resolve a value to a node.
564
564
565 Value can be a binary node, hex node, revision number, or a string
565 Value can be a binary node, hex node, revision number, or a string
566 that can be converted to an integer.
566 that can be converted to an integer.
567
567
568 Raises ``error.LookupError`` if a node could not be resolved.
568 Raises ``error.LookupError`` if a node could not be resolved.
569 """
569 """
570
570
571 def linkrev(rev):
571 def linkrev(rev):
572 """Obtain the changeset revision number a revision is linked to."""
572 """Obtain the changeset revision number a revision is linked to."""
573
573
574 def iscensored(rev):
574 def iscensored(rev):
575 """Return whether a revision's content has been censored."""
575 """Return whether a revision's content has been censored."""
576
576
577 def commonancestorsheads(node1, node2):
577 def commonancestorsheads(node1, node2):
578 """Obtain an iterable of nodes containing heads of common ancestors.
578 """Obtain an iterable of nodes containing heads of common ancestors.
579
579
580 See ``ancestor.commonancestorsheads()``.
580 See ``ancestor.commonancestorsheads()``.
581 """
581 """
582
582
583 def descendants(revs):
583 def descendants(revs):
584 """Obtain descendant revision numbers for a set of revision numbers.
584 """Obtain descendant revision numbers for a set of revision numbers.
585
585
586 If ``nullrev`` is in the set, this is equivalent to ``revs()``.
586 If ``nullrev`` is in the set, this is equivalent to ``revs()``.
587 """
587 """
588
588
589 def heads(start=None, stop=None):
589 def heads(start=None, stop=None):
590 """Obtain a list of nodes that are DAG heads, with control.
590 """Obtain a list of nodes that are DAG heads, with control.
591
591
592 The set of revisions examined can be limited by specifying
592 The set of revisions examined can be limited by specifying
593 ``start`` and ``stop``. ``start`` is a node. ``stop`` is an
593 ``start`` and ``stop``. ``start`` is a node. ``stop`` is an
594 iterable of nodes. DAG traversal starts at earlier revision
594 iterable of nodes. DAG traversal starts at earlier revision
595 ``start`` and iterates forward until any node in ``stop`` is
595 ``start`` and iterates forward until any node in ``stop`` is
596 encountered.
596 encountered.
597 """
597 """
598
598
599 def children(node):
599 def children(node):
600 """Obtain nodes that are children of a node.
600 """Obtain nodes that are children of a node.
601
601
602 Returns a list of nodes.
602 Returns a list of nodes.
603 """
603 """
604
604
605
605
606 class ifiledata(interfaceutil.Interface):
606 class ifiledata(interfaceutil.Interface):
607 """Storage interface for data storage of a specific file.
607 """Storage interface for data storage of a specific file.
608
608
609 This complements ``ifileindex`` and provides an interface for accessing
609 This complements ``ifileindex`` and provides an interface for accessing
610 data for a tracked file.
610 data for a tracked file.
611 """
611 """
612
612
613 def size(rev):
613 def size(rev):
614 """Obtain the fulltext size of file data.
614 """Obtain the fulltext size of file data.
615
615
616 Any metadata is excluded from size measurements.
616 Any metadata is excluded from size measurements.
617 """
617 """
618
618
619 def revision(node, raw=False):
619 def revision(node, raw=False):
620 """Obtain fulltext data for a node.
620 """Obtain fulltext data for a node.
621
621
622 By default, any storage transformations are applied before the data
622 By default, any storage transformations are applied before the data
623 is returned. If ``raw`` is True, non-raw storage transformations
623 is returned. If ``raw`` is True, non-raw storage transformations
624 are not applied.
624 are not applied.
625
625
626 The fulltext data may contain a header containing metadata. Most
626 The fulltext data may contain a header containing metadata. Most
627 consumers should use ``read()`` to obtain the actual file data.
627 consumers should use ``read()`` to obtain the actual file data.
628 """
628 """
629
629
630 def rawdata(node):
630 def rawdata(node):
631 """Obtain raw data for a node."""
631 """Obtain raw data for a node."""
632
632
633 def read(node):
633 def read(node):
634 """Resolve file fulltext data.
634 """Resolve file fulltext data.
635
635
636 This is similar to ``revision()`` except any metadata in the data
636 This is similar to ``revision()`` except any metadata in the data
637 headers is stripped.
637 headers is stripped.
638 """
638 """
639
639
640 def renamed(node):
640 def renamed(node):
641 """Obtain copy metadata for a node.
641 """Obtain copy metadata for a node.
642
642
643 Returns ``False`` if no copy metadata is stored or a 2-tuple of
643 Returns ``False`` if no copy metadata is stored or a 2-tuple of
644 (path, node) from which this revision was copied.
644 (path, node) from which this revision was copied.
645 """
645 """
646
646
647 def cmp(node, fulltext):
647 def cmp(node, fulltext):
648 """Compare fulltext to another revision.
648 """Compare fulltext to another revision.
649
649
650 Returns True if the fulltext is different from what is stored.
650 Returns True if the fulltext is different from what is stored.
651
651
652 This takes copy metadata into account.
652 This takes copy metadata into account.
653
653
654 TODO better document the copy metadata and censoring logic.
654 TODO better document the copy metadata and censoring logic.
655 """
655 """
656
656
657 def emitrevisions(
657 def emitrevisions(
658 nodes,
658 nodes,
659 nodesorder=None,
659 nodesorder=None,
660 revisiondata=False,
660 revisiondata=False,
661 assumehaveparentrevisions=False,
661 assumehaveparentrevisions=False,
662 deltamode=CG_DELTAMODE_STD,
662 deltamode=CG_DELTAMODE_STD,
663 ):
663 ):
664 """Produce ``irevisiondelta`` for revisions.
664 """Produce ``irevisiondelta`` for revisions.
665
665
666 Given an iterable of nodes, emits objects conforming to the
666 Given an iterable of nodes, emits objects conforming to the
667 ``irevisiondelta`` interface that describe revisions in storage.
667 ``irevisiondelta`` interface that describe revisions in storage.
668
668
669 This method is a generator.
669 This method is a generator.
670
670
671 The input nodes may be unordered. Implementations must ensure that a
671 The input nodes may be unordered. Implementations must ensure that a
672 node's parents are emitted before the node itself. Transitively, this
672 node's parents are emitted before the node itself. Transitively, this
673 means that a node may only be emitted once all its ancestors in
673 means that a node may only be emitted once all its ancestors in
674 ``nodes`` have also been emitted.
674 ``nodes`` have also been emitted.
675
675
676 By default, emits "index" data (the ``node``, ``p1node``, and
676 By default, emits "index" data (the ``node``, ``p1node``, and
677 ``p2node`` attributes). If ``revisiondata`` is set, revision data
677 ``p2node`` attributes). If ``revisiondata`` is set, revision data
678 will also be present on the emitted objects.
678 will also be present on the emitted objects.
679
679
680 With default argument values, implementations can choose to emit
680 With default argument values, implementations can choose to emit
681 either fulltext revision data or a delta. When emitting deltas,
681 either fulltext revision data or a delta. When emitting deltas,
682 implementations must consider whether the delta's base revision
682 implementations must consider whether the delta's base revision
683 fulltext is available to the receiver.
683 fulltext is available to the receiver.
684
684
685 The base revision fulltext is guaranteed to be available if any of
685 The base revision fulltext is guaranteed to be available if any of
686 the following are met:
686 the following are met:
687
687
688 * Its fulltext revision was emitted by this method call.
688 * Its fulltext revision was emitted by this method call.
689 * A delta for that revision was emitted by this method call.
689 * A delta for that revision was emitted by this method call.
690 * ``assumehaveparentrevisions`` is True and the base revision is a
690 * ``assumehaveparentrevisions`` is True and the base revision is a
691 parent of the node.
691 parent of the node.
692
692
693 ``nodesorder`` can be used to control the order that revisions are
693 ``nodesorder`` can be used to control the order that revisions are
694 emitted. By default, revisions can be reordered as long as they are
694 emitted. By default, revisions can be reordered as long as they are
695 in DAG topological order (see above). If the value is ``nodes``,
695 in DAG topological order (see above). If the value is ``nodes``,
696 the iteration order from ``nodes`` should be used. If the value is
696 the iteration order from ``nodes`` should be used. If the value is
697 ``storage``, then the native order from the backing storage layer
697 ``storage``, then the native order from the backing storage layer
698 is used. (Not all storage layers will have strong ordering and behavior
698 is used. (Not all storage layers will have strong ordering and behavior
699 of this mode is storage-dependent.) ``nodes`` ordering can force
699 of this mode is storage-dependent.) ``nodes`` ordering can force
700 revisions to be emitted before their ancestors, so consumers should
700 revisions to be emitted before their ancestors, so consumers should
701 use it with care.
701 use it with care.
702
702
703 The ``linknode`` attribute on the returned ``irevisiondelta`` may not
703 The ``linknode`` attribute on the returned ``irevisiondelta`` may not
704 be set and it is the caller's responsibility to resolve it, if needed.
704 be set and it is the caller's responsibility to resolve it, if needed.
705
705
706 If ``deltamode`` is CG_DELTAMODE_PREV and revision data is requested,
706 If ``deltamode`` is CG_DELTAMODE_PREV and revision data is requested,
707 all revision data should be emitted as deltas against the revision
707 all revision data should be emitted as deltas against the revision
708 emitted just prior. The initial revision should be a delta against its
708 emitted just prior. The initial revision should be a delta against its
709 1st parent.
709 1st parent.
710 """
710 """
711
711
712
712
713 class ifilemutation(interfaceutil.Interface):
713 class ifilemutation(interfaceutil.Interface):
714 """Storage interface for mutation events of a tracked file."""
714 """Storage interface for mutation events of a tracked file."""
715
715
716 def add(filedata, meta, transaction, linkrev, p1, p2):
716 def add(filedata, meta, transaction, linkrev, p1, p2):
717 """Add a new revision to the store.
717 """Add a new revision to the store.
718
718
719 Takes file data, dictionary of metadata, a transaction, linkrev,
719 Takes file data, dictionary of metadata, a transaction, linkrev,
720 and parent nodes.
720 and parent nodes.
721
721
722 Returns the node that was added.
722 Returns the node that was added.
723
723
724 May no-op if a revision matching the supplied data is already stored.
724 May no-op if a revision matching the supplied data is already stored.
725 """
725 """
726
726
727 def addrevision(
727 def addrevision(
728 revisiondata,
728 revisiondata,
729 transaction,
729 transaction,
730 linkrev,
730 linkrev,
731 p1,
731 p1,
732 p2,
732 p2,
733 node=None,
733 node=None,
734 flags=0,
734 flags=0,
735 cachedelta=None,
735 cachedelta=None,
736 ):
736 ):
737 """Add a new revision to the store and return its number.
737 """Add a new revision to the store and return its number.
738
738
739 This is similar to ``add()`` except it operates at a lower level.
739 This is similar to ``add()`` except it operates at a lower level.
740
740
741 The data passed in already contains a metadata header, if any.
741 The data passed in already contains a metadata header, if any.
742
742
743 ``node`` and ``flags`` can be used to define the expected node and
743 ``node`` and ``flags`` can be used to define the expected node and
744 the flags to use with storage. ``flags`` is a bitwise value composed
744 the flags to use with storage. ``flags`` is a bitwise value composed
745 of the various ``REVISION_FLAG_*`` constants.
745 of the various ``REVISION_FLAG_*`` constants.
746
746
747 ``add()`` is usually called when adding files from e.g. the working
747 ``add()`` is usually called when adding files from e.g. the working
748 directory. ``addrevision()`` is often called by ``add()`` and for
748 directory. ``addrevision()`` is often called by ``add()`` and for
749 scenarios where revision data has already been computed, such as when
749 scenarios where revision data has already been computed, such as when
750 applying raw data from a peer repo.
750 applying raw data from a peer repo.
751 """
751 """
752
752
753 def addgroup(
753 def addgroup(
754 deltas,
754 deltas,
755 linkmapper,
755 linkmapper,
756 transaction,
756 transaction,
757 addrevisioncb=None,
757 addrevisioncb=None,
758 duplicaterevisioncb=None,
758 duplicaterevisioncb=None,
759 maybemissingparents=False,
759 maybemissingparents=False,
760 ):
760 ):
761 """Process a series of deltas for storage.
761 """Process a series of deltas for storage.
762
762
763 ``deltas`` is an iterable of 7-tuples of
763 ``deltas`` is an iterable of 7-tuples of
764 (node, p1, p2, linknode, deltabase, delta, flags) defining revisions
764 (node, p1, p2, linknode, deltabase, delta, flags) defining revisions
765 to add.
765 to add.
766
766
767 The ``delta`` field contains ``mpatch`` data to apply to a base
767 The ``delta`` field contains ``mpatch`` data to apply to a base
768 revision, identified by ``deltabase``. The base node can be
768 revision, identified by ``deltabase``. The base node can be
769 ``nullid``, in which case the header from the delta can be ignored
769 ``nullid``, in which case the header from the delta can be ignored
770 and the delta used as the fulltext.
770 and the delta used as the fulltext.
771
771
772 ``alwayscache`` instructs the lower layers to cache the content of the
772 ``alwayscache`` instructs the lower layers to cache the content of the
773 newly added revision, even if it needs to be explicitly computed.
773 newly added revision, even if it needs to be explicitly computed.
774 This used to be the default when ``addrevisioncb`` was provided up to
774 This used to be the default when ``addrevisioncb`` was provided up to
775 Mercurial 5.8.
775 Mercurial 5.8.
776
776
777 ``addrevisioncb`` should be called for each node as it is committed.
777 ``addrevisioncb`` should be called for each new rev as it is committed.
778 ``duplicaterevisioncb`` should be called for each pre-existing node.
778 ``duplicaterevisioncb`` should be called for all revs with a
779 pre-existing node.
779
780
780 ``maybemissingparents`` is a bool indicating whether the incoming
781 ``maybemissingparents`` is a bool indicating whether the incoming
781 data may reference parents/ancestor revisions that aren't present.
782 data may reference parents/ancestor revisions that aren't present.
782 This flag is set when receiving data into a "shallow" store that
783 This flag is set when receiving data into a "shallow" store that
783 doesn't hold all history.
784 doesn't hold all history.
784
785
785 Returns a list of nodes that were processed. A node will be in the list
786 Returns a list of nodes that were processed. A node will be in the list
786 even if it existed in the store previously.
787 even if it existed in the store previously.
787 """
788 """
788
789
789 def censorrevision(tr, node, tombstone=b''):
790 def censorrevision(tr, node, tombstone=b''):
790 """Remove the content of a single revision.
791 """Remove the content of a single revision.
791
792
792 The specified ``node`` will have its content purged from storage.
793 The specified ``node`` will have its content purged from storage.
793 Future attempts to access the revision data for this node will
794 Future attempts to access the revision data for this node will
794 result in failure.
795 result in failure.
795
796
796 A ``tombstone`` message can optionally be stored. This message may be
797 A ``tombstone`` message can optionally be stored. This message may be
797 displayed to users when they attempt to access the missing revision
798 displayed to users when they attempt to access the missing revision
798 data.
799 data.
799
800
800 Storage backends may have stored deltas against the previous content
801 Storage backends may have stored deltas against the previous content
801 in this revision. As part of censoring a revision, these storage
802 in this revision. As part of censoring a revision, these storage
802 backends are expected to rewrite any internally stored deltas such
803 backends are expected to rewrite any internally stored deltas such
803 that they no longer reference the deleted content.
804 that they no longer reference the deleted content.
804 """
805 """
805
806
806 def getstrippoint(minlink):
807 def getstrippoint(minlink):
807 """Find the minimum revision that must be stripped to strip a linkrev.
808 """Find the minimum revision that must be stripped to strip a linkrev.
808
809
809 Returns a 2-tuple containing the minimum revision number and a set
810 Returns a 2-tuple containing the minimum revision number and a set
810 of all revisions numbers that would be broken by this strip.
811 of all revisions numbers that would be broken by this strip.
811
812
812 TODO this is highly revlog centric and should be abstracted into
813 TODO this is highly revlog centric and should be abstracted into
813 a higher-level deletion API. ``repair.strip()`` relies on this.
814 a higher-level deletion API. ``repair.strip()`` relies on this.
814 """
815 """
815
816
816 def strip(minlink, transaction):
817 def strip(minlink, transaction):
817 """Remove storage of items starting at a linkrev.
818 """Remove storage of items starting at a linkrev.
818
819
819 This uses ``getstrippoint()`` to determine the first node to remove.
820 This uses ``getstrippoint()`` to determine the first node to remove.
820 Then it effectively truncates storage for all revisions after that.
821 Then it effectively truncates storage for all revisions after that.
821
822
822 TODO this is highly revlog centric and should be abstracted into a
823 TODO this is highly revlog centric and should be abstracted into a
823 higher-level deletion API.
824 higher-level deletion API.
824 """
825 """
825
826
826
827
827 class ifilestorage(ifileindex, ifiledata, ifilemutation):
828 class ifilestorage(ifileindex, ifiledata, ifilemutation):
828 """Complete storage interface for a single tracked file."""
829 """Complete storage interface for a single tracked file."""
829
830
830 def files():
831 def files():
831 """Obtain paths that are backing storage for this file.
832 """Obtain paths that are backing storage for this file.
832
833
833 TODO this is used heavily by verify code and there should probably
834 TODO this is used heavily by verify code and there should probably
834 be a better API for that.
835 be a better API for that.
835 """
836 """
836
837
837 def storageinfo(
838 def storageinfo(
838 exclusivefiles=False,
839 exclusivefiles=False,
839 sharedfiles=False,
840 sharedfiles=False,
840 revisionscount=False,
841 revisionscount=False,
841 trackedsize=False,
842 trackedsize=False,
842 storedsize=False,
843 storedsize=False,
843 ):
844 ):
844 """Obtain information about storage for this file's data.
845 """Obtain information about storage for this file's data.
845
846
846 Returns a dict describing storage for this tracked path. The keys
847 Returns a dict describing storage for this tracked path. The keys
847 in the dict map to arguments of the same. The arguments are bools
848 in the dict map to arguments of the same. The arguments are bools
848 indicating whether to calculate and obtain that data.
849 indicating whether to calculate and obtain that data.
849
850
850 exclusivefiles
851 exclusivefiles
851 Iterable of (vfs, path) describing files that are exclusively
852 Iterable of (vfs, path) describing files that are exclusively
852 used to back storage for this tracked path.
853 used to back storage for this tracked path.
853
854
854 sharedfiles
855 sharedfiles
855 Iterable of (vfs, path) describing files that are used to back
856 Iterable of (vfs, path) describing files that are used to back
856 storage for this tracked path. Those files may also provide storage
857 storage for this tracked path. Those files may also provide storage
857 for other stored entities.
858 for other stored entities.
858
859
859 revisionscount
860 revisionscount
860 Number of revisions available for retrieval.
861 Number of revisions available for retrieval.
861
862
862 trackedsize
863 trackedsize
863 Total size in bytes of all tracked revisions. This is a sum of the
864 Total size in bytes of all tracked revisions. This is a sum of the
864 length of the fulltext of all revisions.
865 length of the fulltext of all revisions.
865
866
866 storedsize
867 storedsize
867 Total size in bytes used to store data for all tracked revisions.
868 Total size in bytes used to store data for all tracked revisions.
868 This is commonly less than ``trackedsize`` due to internal usage
869 This is commonly less than ``trackedsize`` due to internal usage
869 of deltas rather than fulltext revisions.
870 of deltas rather than fulltext revisions.
870
871
871 Not all storage backends may support all queries are have a reasonable
872 Not all storage backends may support all queries are have a reasonable
872 value to use. In that case, the value should be set to ``None`` and
873 value to use. In that case, the value should be set to ``None`` and
873 callers are expected to handle this special value.
874 callers are expected to handle this special value.
874 """
875 """
875
876
876 def verifyintegrity(state):
877 def verifyintegrity(state):
877 """Verifies the integrity of file storage.
878 """Verifies the integrity of file storage.
878
879
879 ``state`` is a dict holding state of the verifier process. It can be
880 ``state`` is a dict holding state of the verifier process. It can be
880 used to communicate data between invocations of multiple storage
881 used to communicate data between invocations of multiple storage
881 primitives.
882 primitives.
882
883
883 If individual revisions cannot have their revision content resolved,
884 If individual revisions cannot have their revision content resolved,
884 the method is expected to set the ``skipread`` key to a set of nodes
885 the method is expected to set the ``skipread`` key to a set of nodes
885 that encountered problems. If set, the method can also add the node(s)
886 that encountered problems. If set, the method can also add the node(s)
886 to ``safe_renamed`` in order to indicate nodes that may perform the
887 to ``safe_renamed`` in order to indicate nodes that may perform the
887 rename checks with currently accessible data.
888 rename checks with currently accessible data.
888
889
889 The method yields objects conforming to the ``iverifyproblem``
890 The method yields objects conforming to the ``iverifyproblem``
890 interface.
891 interface.
891 """
892 """
892
893
893
894
894 class idirs(interfaceutil.Interface):
895 class idirs(interfaceutil.Interface):
895 """Interface representing a collection of directories from paths.
896 """Interface representing a collection of directories from paths.
896
897
897 This interface is essentially a derived data structure representing
898 This interface is essentially a derived data structure representing
898 directories from a collection of paths.
899 directories from a collection of paths.
899 """
900 """
900
901
901 def addpath(path):
902 def addpath(path):
902 """Add a path to the collection.
903 """Add a path to the collection.
903
904
904 All directories in the path will be added to the collection.
905 All directories in the path will be added to the collection.
905 """
906 """
906
907
907 def delpath(path):
908 def delpath(path):
908 """Remove a path from the collection.
909 """Remove a path from the collection.
909
910
910 If the removal was the last path in a particular directory, the
911 If the removal was the last path in a particular directory, the
911 directory is removed from the collection.
912 directory is removed from the collection.
912 """
913 """
913
914
914 def __iter__():
915 def __iter__():
915 """Iterate over the directories in this collection of paths."""
916 """Iterate over the directories in this collection of paths."""
916
917
917 def __contains__(path):
918 def __contains__(path):
918 """Whether a specific directory is in this collection."""
919 """Whether a specific directory is in this collection."""
919
920
920
921
921 class imanifestdict(interfaceutil.Interface):
922 class imanifestdict(interfaceutil.Interface):
922 """Interface representing a manifest data structure.
923 """Interface representing a manifest data structure.
923
924
924 A manifest is effectively a dict mapping paths to entries. Each entry
925 A manifest is effectively a dict mapping paths to entries. Each entry
925 consists of a binary node and extra flags affecting that entry.
926 consists of a binary node and extra flags affecting that entry.
926 """
927 """
927
928
928 def __getitem__(path):
929 def __getitem__(path):
929 """Returns the binary node value for a path in the manifest.
930 """Returns the binary node value for a path in the manifest.
930
931
931 Raises ``KeyError`` if the path does not exist in the manifest.
932 Raises ``KeyError`` if the path does not exist in the manifest.
932
933
933 Equivalent to ``self.find(path)[0]``.
934 Equivalent to ``self.find(path)[0]``.
934 """
935 """
935
936
936 def find(path):
937 def find(path):
937 """Returns the entry for a path in the manifest.
938 """Returns the entry for a path in the manifest.
938
939
939 Returns a 2-tuple of (node, flags).
940 Returns a 2-tuple of (node, flags).
940
941
941 Raises ``KeyError`` if the path does not exist in the manifest.
942 Raises ``KeyError`` if the path does not exist in the manifest.
942 """
943 """
943
944
944 def __len__():
945 def __len__():
945 """Return the number of entries in the manifest."""
946 """Return the number of entries in the manifest."""
946
947
947 def __nonzero__():
948 def __nonzero__():
948 """Returns True if the manifest has entries, False otherwise."""
949 """Returns True if the manifest has entries, False otherwise."""
949
950
950 __bool__ = __nonzero__
951 __bool__ = __nonzero__
951
952
952 def __setitem__(path, node):
953 def __setitem__(path, node):
953 """Define the node value for a path in the manifest.
954 """Define the node value for a path in the manifest.
954
955
955 If the path is already in the manifest, its flags will be copied to
956 If the path is already in the manifest, its flags will be copied to
956 the new entry.
957 the new entry.
957 """
958 """
958
959
959 def __contains__(path):
960 def __contains__(path):
960 """Whether a path exists in the manifest."""
961 """Whether a path exists in the manifest."""
961
962
962 def __delitem__(path):
963 def __delitem__(path):
963 """Remove a path from the manifest.
964 """Remove a path from the manifest.
964
965
965 Raises ``KeyError`` if the path is not in the manifest.
966 Raises ``KeyError`` if the path is not in the manifest.
966 """
967 """
967
968
968 def __iter__():
969 def __iter__():
969 """Iterate over paths in the manifest."""
970 """Iterate over paths in the manifest."""
970
971
971 def iterkeys():
972 def iterkeys():
972 """Iterate over paths in the manifest."""
973 """Iterate over paths in the manifest."""
973
974
974 def keys():
975 def keys():
975 """Obtain a list of paths in the manifest."""
976 """Obtain a list of paths in the manifest."""
976
977
977 def filesnotin(other, match=None):
978 def filesnotin(other, match=None):
978 """Obtain the set of paths in this manifest but not in another.
979 """Obtain the set of paths in this manifest but not in another.
979
980
980 ``match`` is an optional matcher function to be applied to both
981 ``match`` is an optional matcher function to be applied to both
981 manifests.
982 manifests.
982
983
983 Returns a set of paths.
984 Returns a set of paths.
984 """
985 """
985
986
986 def dirs():
987 def dirs():
987 """Returns an object implementing the ``idirs`` interface."""
988 """Returns an object implementing the ``idirs`` interface."""
988
989
989 def hasdir(dir):
990 def hasdir(dir):
990 """Returns a bool indicating if a directory is in this manifest."""
991 """Returns a bool indicating if a directory is in this manifest."""
991
992
992 def walk(match):
993 def walk(match):
993 """Generator of paths in manifest satisfying a matcher.
994 """Generator of paths in manifest satisfying a matcher.
994
995
995 If the matcher has explicit files listed and they don't exist in
996 If the matcher has explicit files listed and they don't exist in
996 the manifest, ``match.bad()`` is called for each missing file.
997 the manifest, ``match.bad()`` is called for each missing file.
997 """
998 """
998
999
999 def diff(other, match=None, clean=False):
1000 def diff(other, match=None, clean=False):
1000 """Find differences between this manifest and another.
1001 """Find differences between this manifest and another.
1001
1002
1002 This manifest is compared to ``other``.
1003 This manifest is compared to ``other``.
1003
1004
1004 If ``match`` is provided, the two manifests are filtered against this
1005 If ``match`` is provided, the two manifests are filtered against this
1005 matcher and only entries satisfying the matcher are compared.
1006 matcher and only entries satisfying the matcher are compared.
1006
1007
1007 If ``clean`` is True, unchanged files are included in the returned
1008 If ``clean`` is True, unchanged files are included in the returned
1008 object.
1009 object.
1009
1010
1010 Returns a dict with paths as keys and values of 2-tuples of 2-tuples of
1011 Returns a dict with paths as keys and values of 2-tuples of 2-tuples of
1011 the form ``((node1, flag1), (node2, flag2))`` where ``(node1, flag1)``
1012 the form ``((node1, flag1), (node2, flag2))`` where ``(node1, flag1)``
1012 represents the node and flags for this manifest and ``(node2, flag2)``
1013 represents the node and flags for this manifest and ``(node2, flag2)``
1013 are the same for the other manifest.
1014 are the same for the other manifest.
1014 """
1015 """
1015
1016
1016 def setflag(path, flag):
1017 def setflag(path, flag):
1017 """Set the flag value for a given path.
1018 """Set the flag value for a given path.
1018
1019
1019 Raises ``KeyError`` if the path is not already in the manifest.
1020 Raises ``KeyError`` if the path is not already in the manifest.
1020 """
1021 """
1021
1022
1022 def get(path, default=None):
1023 def get(path, default=None):
1023 """Obtain the node value for a path or a default value if missing."""
1024 """Obtain the node value for a path or a default value if missing."""
1024
1025
1025 def flags(path):
1026 def flags(path):
1026 """Return the flags value for a path (default: empty bytestring)."""
1027 """Return the flags value for a path (default: empty bytestring)."""
1027
1028
1028 def copy():
1029 def copy():
1029 """Return a copy of this manifest."""
1030 """Return a copy of this manifest."""
1030
1031
1031 def items():
1032 def items():
1032 """Returns an iterable of (path, node) for items in this manifest."""
1033 """Returns an iterable of (path, node) for items in this manifest."""
1033
1034
1034 def iteritems():
1035 def iteritems():
1035 """Identical to items()."""
1036 """Identical to items()."""
1036
1037
1037 def iterentries():
1038 def iterentries():
1038 """Returns an iterable of (path, node, flags) for this manifest.
1039 """Returns an iterable of (path, node, flags) for this manifest.
1039
1040
1040 Similar to ``iteritems()`` except items are a 3-tuple and include
1041 Similar to ``iteritems()`` except items are a 3-tuple and include
1041 flags.
1042 flags.
1042 """
1043 """
1043
1044
1044 def text():
1045 def text():
1045 """Obtain the raw data representation for this manifest.
1046 """Obtain the raw data representation for this manifest.
1046
1047
1047 Result is used to create a manifest revision.
1048 Result is used to create a manifest revision.
1048 """
1049 """
1049
1050
1050 def fastdelta(base, changes):
1051 def fastdelta(base, changes):
1051 """Obtain a delta between this manifest and another given changes.
1052 """Obtain a delta between this manifest and another given changes.
1052
1053
1053 ``base`` in the raw data representation for another manifest.
1054 ``base`` in the raw data representation for another manifest.
1054
1055
1055 ``changes`` is an iterable of ``(path, to_delete)``.
1056 ``changes`` is an iterable of ``(path, to_delete)``.
1056
1057
1057 Returns a 2-tuple containing ``bytearray(self.text())`` and the
1058 Returns a 2-tuple containing ``bytearray(self.text())`` and the
1058 delta between ``base`` and this manifest.
1059 delta between ``base`` and this manifest.
1059
1060
1060 If this manifest implementation can't support ``fastdelta()``,
1061 If this manifest implementation can't support ``fastdelta()``,
1061 raise ``mercurial.manifest.FastdeltaUnavailable``.
1062 raise ``mercurial.manifest.FastdeltaUnavailable``.
1062 """
1063 """
1063
1064
1064
1065
1065 class imanifestrevisionbase(interfaceutil.Interface):
1066 class imanifestrevisionbase(interfaceutil.Interface):
1066 """Base interface representing a single revision of a manifest.
1067 """Base interface representing a single revision of a manifest.
1067
1068
1068 Should not be used as a primary interface: should always be inherited
1069 Should not be used as a primary interface: should always be inherited
1069 as part of a larger interface.
1070 as part of a larger interface.
1070 """
1071 """
1071
1072
1072 def copy():
1073 def copy():
1073 """Obtain a copy of this manifest instance.
1074 """Obtain a copy of this manifest instance.
1074
1075
1075 Returns an object conforming to the ``imanifestrevisionwritable``
1076 Returns an object conforming to the ``imanifestrevisionwritable``
1076 interface. The instance will be associated with the same
1077 interface. The instance will be associated with the same
1077 ``imanifestlog`` collection as this instance.
1078 ``imanifestlog`` collection as this instance.
1078 """
1079 """
1079
1080
1080 def read():
1081 def read():
1081 """Obtain the parsed manifest data structure.
1082 """Obtain the parsed manifest data structure.
1082
1083
1083 The returned object conforms to the ``imanifestdict`` interface.
1084 The returned object conforms to the ``imanifestdict`` interface.
1084 """
1085 """
1085
1086
1086
1087
1087 class imanifestrevisionstored(imanifestrevisionbase):
1088 class imanifestrevisionstored(imanifestrevisionbase):
1088 """Interface representing a manifest revision committed to storage."""
1089 """Interface representing a manifest revision committed to storage."""
1089
1090
1090 def node():
1091 def node():
1091 """The binary node for this manifest."""
1092 """The binary node for this manifest."""
1092
1093
1093 parents = interfaceutil.Attribute(
1094 parents = interfaceutil.Attribute(
1094 """List of binary nodes that are parents for this manifest revision."""
1095 """List of binary nodes that are parents for this manifest revision."""
1095 )
1096 )
1096
1097
1097 def readdelta(shallow=False):
1098 def readdelta(shallow=False):
1098 """Obtain the manifest data structure representing changes from parent.
1099 """Obtain the manifest data structure representing changes from parent.
1099
1100
1100 This manifest is compared to its 1st parent. A new manifest representing
1101 This manifest is compared to its 1st parent. A new manifest representing
1101 those differences is constructed.
1102 those differences is constructed.
1102
1103
1103 The returned object conforms to the ``imanifestdict`` interface.
1104 The returned object conforms to the ``imanifestdict`` interface.
1104 """
1105 """
1105
1106
1106 def readfast(shallow=False):
1107 def readfast(shallow=False):
1107 """Calls either ``read()`` or ``readdelta()``.
1108 """Calls either ``read()`` or ``readdelta()``.
1108
1109
1109 The faster of the two options is called.
1110 The faster of the two options is called.
1110 """
1111 """
1111
1112
1112 def find(key):
1113 def find(key):
1113 """Calls self.read().find(key)``.
1114 """Calls self.read().find(key)``.
1114
1115
1115 Returns a 2-tuple of ``(node, flags)`` or raises ``KeyError``.
1116 Returns a 2-tuple of ``(node, flags)`` or raises ``KeyError``.
1116 """
1117 """
1117
1118
1118
1119
1119 class imanifestrevisionwritable(imanifestrevisionbase):
1120 class imanifestrevisionwritable(imanifestrevisionbase):
1120 """Interface representing a manifest revision that can be committed."""
1121 """Interface representing a manifest revision that can be committed."""
1121
1122
1122 def write(transaction, linkrev, p1node, p2node, added, removed, match=None):
1123 def write(transaction, linkrev, p1node, p2node, added, removed, match=None):
1123 """Add this revision to storage.
1124 """Add this revision to storage.
1124
1125
1125 Takes a transaction object, the changeset revision number it will
1126 Takes a transaction object, the changeset revision number it will
1126 be associated with, its parent nodes, and lists of added and
1127 be associated with, its parent nodes, and lists of added and
1127 removed paths.
1128 removed paths.
1128
1129
1129 If match is provided, storage can choose not to inspect or write out
1130 If match is provided, storage can choose not to inspect or write out
1130 items that do not match. Storage is still required to be able to provide
1131 items that do not match. Storage is still required to be able to provide
1131 the full manifest in the future for any directories written (these
1132 the full manifest in the future for any directories written (these
1132 manifests should not be "narrowed on disk").
1133 manifests should not be "narrowed on disk").
1133
1134
1134 Returns the binary node of the created revision.
1135 Returns the binary node of the created revision.
1135 """
1136 """
1136
1137
1137
1138
1138 class imanifeststorage(interfaceutil.Interface):
1139 class imanifeststorage(interfaceutil.Interface):
1139 """Storage interface for manifest data."""
1140 """Storage interface for manifest data."""
1140
1141
1141 tree = interfaceutil.Attribute(
1142 tree = interfaceutil.Attribute(
1142 """The path to the directory this manifest tracks.
1143 """The path to the directory this manifest tracks.
1143
1144
1144 The empty bytestring represents the root manifest.
1145 The empty bytestring represents the root manifest.
1145 """
1146 """
1146 )
1147 )
1147
1148
1148 index = interfaceutil.Attribute(
1149 index = interfaceutil.Attribute(
1149 """An ``ifilerevisionssequence`` instance."""
1150 """An ``ifilerevisionssequence`` instance."""
1150 )
1151 )
1151
1152
1152 indexfile = interfaceutil.Attribute(
1153 indexfile = interfaceutil.Attribute(
1153 """Path of revlog index file.
1154 """Path of revlog index file.
1154
1155
1155 TODO this is revlog specific and should not be exposed.
1156 TODO this is revlog specific and should not be exposed.
1156 """
1157 """
1157 )
1158 )
1158
1159
1159 opener = interfaceutil.Attribute(
1160 opener = interfaceutil.Attribute(
1160 """VFS opener to use to access underlying files used for storage.
1161 """VFS opener to use to access underlying files used for storage.
1161
1162
1162 TODO this is revlog specific and should not be exposed.
1163 TODO this is revlog specific and should not be exposed.
1163 """
1164 """
1164 )
1165 )
1165
1166
1166 version = interfaceutil.Attribute(
1167 version = interfaceutil.Attribute(
1167 """Revlog version number.
1168 """Revlog version number.
1168
1169
1169 TODO this is revlog specific and should not be exposed.
1170 TODO this is revlog specific and should not be exposed.
1170 """
1171 """
1171 )
1172 )
1172
1173
1173 _generaldelta = interfaceutil.Attribute(
1174 _generaldelta = interfaceutil.Attribute(
1174 """Whether generaldelta storage is being used.
1175 """Whether generaldelta storage is being used.
1175
1176
1176 TODO this is revlog specific and should not be exposed.
1177 TODO this is revlog specific and should not be exposed.
1177 """
1178 """
1178 )
1179 )
1179
1180
1180 fulltextcache = interfaceutil.Attribute(
1181 fulltextcache = interfaceutil.Attribute(
1181 """Dict with cache of fulltexts.
1182 """Dict with cache of fulltexts.
1182
1183
1183 TODO this doesn't feel appropriate for the storage interface.
1184 TODO this doesn't feel appropriate for the storage interface.
1184 """
1185 """
1185 )
1186 )
1186
1187
1187 def __len__():
1188 def __len__():
1188 """Obtain the number of revisions stored for this manifest."""
1189 """Obtain the number of revisions stored for this manifest."""
1189
1190
1190 def __iter__():
1191 def __iter__():
1191 """Iterate over revision numbers for this manifest."""
1192 """Iterate over revision numbers for this manifest."""
1192
1193
1193 def rev(node):
1194 def rev(node):
1194 """Obtain the revision number given a binary node.
1195 """Obtain the revision number given a binary node.
1195
1196
1196 Raises ``error.LookupError`` if the node is not known.
1197 Raises ``error.LookupError`` if the node is not known.
1197 """
1198 """
1198
1199
1199 def node(rev):
1200 def node(rev):
1200 """Obtain the node value given a revision number.
1201 """Obtain the node value given a revision number.
1201
1202
1202 Raises ``error.LookupError`` if the revision is not known.
1203 Raises ``error.LookupError`` if the revision is not known.
1203 """
1204 """
1204
1205
1205 def lookup(value):
1206 def lookup(value):
1206 """Attempt to resolve a value to a node.
1207 """Attempt to resolve a value to a node.
1207
1208
1208 Value can be a binary node, hex node, revision number, or a bytes
1209 Value can be a binary node, hex node, revision number, or a bytes
1209 that can be converted to an integer.
1210 that can be converted to an integer.
1210
1211
1211 Raises ``error.LookupError`` if a ndoe could not be resolved.
1212 Raises ``error.LookupError`` if a ndoe could not be resolved.
1212 """
1213 """
1213
1214
1214 def parents(node):
1215 def parents(node):
1215 """Returns a 2-tuple of parent nodes for a node.
1216 """Returns a 2-tuple of parent nodes for a node.
1216
1217
1217 Values will be ``nullid`` if the parent is empty.
1218 Values will be ``nullid`` if the parent is empty.
1218 """
1219 """
1219
1220
1220 def parentrevs(rev):
1221 def parentrevs(rev):
1221 """Like parents() but operates on revision numbers."""
1222 """Like parents() but operates on revision numbers."""
1222
1223
1223 def linkrev(rev):
1224 def linkrev(rev):
1224 """Obtain the changeset revision number a revision is linked to."""
1225 """Obtain the changeset revision number a revision is linked to."""
1225
1226
1226 def revision(node, _df=None, raw=False):
1227 def revision(node, _df=None, raw=False):
1227 """Obtain fulltext data for a node."""
1228 """Obtain fulltext data for a node."""
1228
1229
1229 def rawdata(node, _df=None):
1230 def rawdata(node, _df=None):
1230 """Obtain raw data for a node."""
1231 """Obtain raw data for a node."""
1231
1232
1232 def revdiff(rev1, rev2):
1233 def revdiff(rev1, rev2):
1233 """Obtain a delta between two revision numbers.
1234 """Obtain a delta between two revision numbers.
1234
1235
1235 The returned data is the result of ``bdiff.bdiff()`` on the raw
1236 The returned data is the result of ``bdiff.bdiff()`` on the raw
1236 revision data.
1237 revision data.
1237 """
1238 """
1238
1239
1239 def cmp(node, fulltext):
1240 def cmp(node, fulltext):
1240 """Compare fulltext to another revision.
1241 """Compare fulltext to another revision.
1241
1242
1242 Returns True if the fulltext is different from what is stored.
1243 Returns True if the fulltext is different from what is stored.
1243 """
1244 """
1244
1245
1245 def emitrevisions(
1246 def emitrevisions(
1246 nodes,
1247 nodes,
1247 nodesorder=None,
1248 nodesorder=None,
1248 revisiondata=False,
1249 revisiondata=False,
1249 assumehaveparentrevisions=False,
1250 assumehaveparentrevisions=False,
1250 ):
1251 ):
1251 """Produce ``irevisiondelta`` describing revisions.
1252 """Produce ``irevisiondelta`` describing revisions.
1252
1253
1253 See the documentation for ``ifiledata`` for more.
1254 See the documentation for ``ifiledata`` for more.
1254 """
1255 """
1255
1256
1256 def addgroup(
1257 def addgroup(
1257 deltas,
1258 deltas,
1258 linkmapper,
1259 linkmapper,
1259 transaction,
1260 transaction,
1260 addrevisioncb=None,
1261 addrevisioncb=None,
1261 duplicaterevisioncb=None,
1262 duplicaterevisioncb=None,
1262 ):
1263 ):
1263 """Process a series of deltas for storage.
1264 """Process a series of deltas for storage.
1264
1265
1265 See the documentation in ``ifilemutation`` for more.
1266 See the documentation in ``ifilemutation`` for more.
1266 """
1267 """
1267
1268
1268 def rawsize(rev):
1269 def rawsize(rev):
1269 """Obtain the size of tracked data.
1270 """Obtain the size of tracked data.
1270
1271
1271 Is equivalent to ``len(m.rawdata(node))``.
1272 Is equivalent to ``len(m.rawdata(node))``.
1272
1273
1273 TODO this method is only used by upgrade code and may be removed.
1274 TODO this method is only used by upgrade code and may be removed.
1274 """
1275 """
1275
1276
1276 def getstrippoint(minlink):
1277 def getstrippoint(minlink):
1277 """Find minimum revision that must be stripped to strip a linkrev.
1278 """Find minimum revision that must be stripped to strip a linkrev.
1278
1279
1279 See the documentation in ``ifilemutation`` for more.
1280 See the documentation in ``ifilemutation`` for more.
1280 """
1281 """
1281
1282
1282 def strip(minlink, transaction):
1283 def strip(minlink, transaction):
1283 """Remove storage of items starting at a linkrev.
1284 """Remove storage of items starting at a linkrev.
1284
1285
1285 See the documentation in ``ifilemutation`` for more.
1286 See the documentation in ``ifilemutation`` for more.
1286 """
1287 """
1287
1288
1288 def checksize():
1289 def checksize():
1289 """Obtain the expected sizes of backing files.
1290 """Obtain the expected sizes of backing files.
1290
1291
1291 TODO this is used by verify and it should not be part of the interface.
1292 TODO this is used by verify and it should not be part of the interface.
1292 """
1293 """
1293
1294
1294 def files():
1295 def files():
1295 """Obtain paths that are backing storage for this manifest.
1296 """Obtain paths that are backing storage for this manifest.
1296
1297
1297 TODO this is used by verify and there should probably be a better API
1298 TODO this is used by verify and there should probably be a better API
1298 for this functionality.
1299 for this functionality.
1299 """
1300 """
1300
1301
1301 def deltaparent(rev):
1302 def deltaparent(rev):
1302 """Obtain the revision that a revision is delta'd against.
1303 """Obtain the revision that a revision is delta'd against.
1303
1304
1304 TODO delta encoding is an implementation detail of storage and should
1305 TODO delta encoding is an implementation detail of storage and should
1305 not be exposed to the storage interface.
1306 not be exposed to the storage interface.
1306 """
1307 """
1307
1308
1308 def clone(tr, dest, **kwargs):
1309 def clone(tr, dest, **kwargs):
1309 """Clone this instance to another."""
1310 """Clone this instance to another."""
1310
1311
1311 def clearcaches(clear_persisted_data=False):
1312 def clearcaches(clear_persisted_data=False):
1312 """Clear any caches associated with this instance."""
1313 """Clear any caches associated with this instance."""
1313
1314
1314 def dirlog(d):
1315 def dirlog(d):
1315 """Obtain a manifest storage instance for a tree."""
1316 """Obtain a manifest storage instance for a tree."""
1316
1317
1317 def add(
1318 def add(
1318 m, transaction, link, p1, p2, added, removed, readtree=None, match=None
1319 m, transaction, link, p1, p2, added, removed, readtree=None, match=None
1319 ):
1320 ):
1320 """Add a revision to storage.
1321 """Add a revision to storage.
1321
1322
1322 ``m`` is an object conforming to ``imanifestdict``.
1323 ``m`` is an object conforming to ``imanifestdict``.
1323
1324
1324 ``link`` is the linkrev revision number.
1325 ``link`` is the linkrev revision number.
1325
1326
1326 ``p1`` and ``p2`` are the parent revision numbers.
1327 ``p1`` and ``p2`` are the parent revision numbers.
1327
1328
1328 ``added`` and ``removed`` are iterables of added and removed paths,
1329 ``added`` and ``removed`` are iterables of added and removed paths,
1329 respectively.
1330 respectively.
1330
1331
1331 ``readtree`` is a function that can be used to read the child tree(s)
1332 ``readtree`` is a function that can be used to read the child tree(s)
1332 when recursively writing the full tree structure when using
1333 when recursively writing the full tree structure when using
1333 treemanifets.
1334 treemanifets.
1334
1335
1335 ``match`` is a matcher that can be used to hint to storage that not all
1336 ``match`` is a matcher that can be used to hint to storage that not all
1336 paths must be inspected; this is an optimization and can be safely
1337 paths must be inspected; this is an optimization and can be safely
1337 ignored. Note that the storage must still be able to reproduce a full
1338 ignored. Note that the storage must still be able to reproduce a full
1338 manifest including files that did not match.
1339 manifest including files that did not match.
1339 """
1340 """
1340
1341
1341 def storageinfo(
1342 def storageinfo(
1342 exclusivefiles=False,
1343 exclusivefiles=False,
1343 sharedfiles=False,
1344 sharedfiles=False,
1344 revisionscount=False,
1345 revisionscount=False,
1345 trackedsize=False,
1346 trackedsize=False,
1346 storedsize=False,
1347 storedsize=False,
1347 ):
1348 ):
1348 """Obtain information about storage for this manifest's data.
1349 """Obtain information about storage for this manifest's data.
1349
1350
1350 See ``ifilestorage.storageinfo()`` for a description of this method.
1351 See ``ifilestorage.storageinfo()`` for a description of this method.
1351 This one behaves the same way, except for manifest data.
1352 This one behaves the same way, except for manifest data.
1352 """
1353 """
1353
1354
1354
1355
1355 class imanifestlog(interfaceutil.Interface):
1356 class imanifestlog(interfaceutil.Interface):
1356 """Interface representing a collection of manifest snapshots.
1357 """Interface representing a collection of manifest snapshots.
1357
1358
1358 Represents the root manifest in a repository.
1359 Represents the root manifest in a repository.
1359
1360
1360 Also serves as a means to access nested tree manifests and to cache
1361 Also serves as a means to access nested tree manifests and to cache
1361 tree manifests.
1362 tree manifests.
1362 """
1363 """
1363
1364
1364 def __getitem__(node):
1365 def __getitem__(node):
1365 """Obtain a manifest instance for a given binary node.
1366 """Obtain a manifest instance for a given binary node.
1366
1367
1367 Equivalent to calling ``self.get('', node)``.
1368 Equivalent to calling ``self.get('', node)``.
1368
1369
1369 The returned object conforms to the ``imanifestrevisionstored``
1370 The returned object conforms to the ``imanifestrevisionstored``
1370 interface.
1371 interface.
1371 """
1372 """
1372
1373
1373 def get(tree, node, verify=True):
1374 def get(tree, node, verify=True):
1374 """Retrieve the manifest instance for a given directory and binary node.
1375 """Retrieve the manifest instance for a given directory and binary node.
1375
1376
1376 ``node`` always refers to the node of the root manifest (which will be
1377 ``node`` always refers to the node of the root manifest (which will be
1377 the only manifest if flat manifests are being used).
1378 the only manifest if flat manifests are being used).
1378
1379
1379 If ``tree`` is the empty string, the root manifest is returned.
1380 If ``tree`` is the empty string, the root manifest is returned.
1380 Otherwise the manifest for the specified directory will be returned
1381 Otherwise the manifest for the specified directory will be returned
1381 (requires tree manifests).
1382 (requires tree manifests).
1382
1383
1383 If ``verify`` is True, ``LookupError`` is raised if the node is not
1384 If ``verify`` is True, ``LookupError`` is raised if the node is not
1384 known.
1385 known.
1385
1386
1386 The returned object conforms to the ``imanifestrevisionstored``
1387 The returned object conforms to the ``imanifestrevisionstored``
1387 interface.
1388 interface.
1388 """
1389 """
1389
1390
1390 def getstorage(tree):
1391 def getstorage(tree):
1391 """Retrieve an interface to storage for a particular tree.
1392 """Retrieve an interface to storage for a particular tree.
1392
1393
1393 If ``tree`` is the empty bytestring, storage for the root manifest will
1394 If ``tree`` is the empty bytestring, storage for the root manifest will
1394 be returned. Otherwise storage for a tree manifest is returned.
1395 be returned. Otherwise storage for a tree manifest is returned.
1395
1396
1396 TODO formalize interface for returned object.
1397 TODO formalize interface for returned object.
1397 """
1398 """
1398
1399
1399 def clearcaches():
1400 def clearcaches():
1400 """Clear caches associated with this collection."""
1401 """Clear caches associated with this collection."""
1401
1402
1402 def rev(node):
1403 def rev(node):
1403 """Obtain the revision number for a binary node.
1404 """Obtain the revision number for a binary node.
1404
1405
1405 Raises ``error.LookupError`` if the node is not known.
1406 Raises ``error.LookupError`` if the node is not known.
1406 """
1407 """
1407
1408
1408 def update_caches(transaction):
1409 def update_caches(transaction):
1409 """update whatever cache are relevant for the used storage."""
1410 """update whatever cache are relevant for the used storage."""
1410
1411
1411
1412
1412 class ilocalrepositoryfilestorage(interfaceutil.Interface):
1413 class ilocalrepositoryfilestorage(interfaceutil.Interface):
1413 """Local repository sub-interface providing access to tracked file storage.
1414 """Local repository sub-interface providing access to tracked file storage.
1414
1415
1415 This interface defines how a repository accesses storage for a single
1416 This interface defines how a repository accesses storage for a single
1416 tracked file path.
1417 tracked file path.
1417 """
1418 """
1418
1419
1419 def file(f):
1420 def file(f):
1420 """Obtain a filelog for a tracked path.
1421 """Obtain a filelog for a tracked path.
1421
1422
1422 The returned type conforms to the ``ifilestorage`` interface.
1423 The returned type conforms to the ``ifilestorage`` interface.
1423 """
1424 """
1424
1425
1425
1426
1426 class ilocalrepositorymain(interfaceutil.Interface):
1427 class ilocalrepositorymain(interfaceutil.Interface):
1427 """Main interface for local repositories.
1428 """Main interface for local repositories.
1428
1429
1429 This currently captures the reality of things - not how things should be.
1430 This currently captures the reality of things - not how things should be.
1430 """
1431 """
1431
1432
1432 supportedformats = interfaceutil.Attribute(
1433 supportedformats = interfaceutil.Attribute(
1433 """Set of requirements that apply to stream clone.
1434 """Set of requirements that apply to stream clone.
1434
1435
1435 This is actually a class attribute and is shared among all instances.
1436 This is actually a class attribute and is shared among all instances.
1436 """
1437 """
1437 )
1438 )
1438
1439
1439 supported = interfaceutil.Attribute(
1440 supported = interfaceutil.Attribute(
1440 """Set of requirements that this repo is capable of opening."""
1441 """Set of requirements that this repo is capable of opening."""
1441 )
1442 )
1442
1443
1443 requirements = interfaceutil.Attribute(
1444 requirements = interfaceutil.Attribute(
1444 """Set of requirements this repo uses."""
1445 """Set of requirements this repo uses."""
1445 )
1446 )
1446
1447
1447 features = interfaceutil.Attribute(
1448 features = interfaceutil.Attribute(
1448 """Set of "features" this repository supports.
1449 """Set of "features" this repository supports.
1449
1450
1450 A "feature" is a loosely-defined term. It can refer to a feature
1451 A "feature" is a loosely-defined term. It can refer to a feature
1451 in the classical sense or can describe an implementation detail
1452 in the classical sense or can describe an implementation detail
1452 of the repository. For example, a ``readonly`` feature may denote
1453 of the repository. For example, a ``readonly`` feature may denote
1453 the repository as read-only. Or a ``revlogfilestore`` feature may
1454 the repository as read-only. Or a ``revlogfilestore`` feature may
1454 denote that the repository is using revlogs for file storage.
1455 denote that the repository is using revlogs for file storage.
1455
1456
1456 The intent of features is to provide a machine-queryable mechanism
1457 The intent of features is to provide a machine-queryable mechanism
1457 for repo consumers to test for various repository characteristics.
1458 for repo consumers to test for various repository characteristics.
1458
1459
1459 Features are similar to ``requirements``. The main difference is that
1460 Features are similar to ``requirements``. The main difference is that
1460 requirements are stored on-disk and represent requirements to open the
1461 requirements are stored on-disk and represent requirements to open the
1461 repository. Features are more run-time capabilities of the repository
1462 repository. Features are more run-time capabilities of the repository
1462 and more granular capabilities (which may be derived from requirements).
1463 and more granular capabilities (which may be derived from requirements).
1463 """
1464 """
1464 )
1465 )
1465
1466
1466 filtername = interfaceutil.Attribute(
1467 filtername = interfaceutil.Attribute(
1467 """Name of the repoview that is active on this repo."""
1468 """Name of the repoview that is active on this repo."""
1468 )
1469 )
1469
1470
1470 wvfs = interfaceutil.Attribute(
1471 wvfs = interfaceutil.Attribute(
1471 """VFS used to access the working directory."""
1472 """VFS used to access the working directory."""
1472 )
1473 )
1473
1474
1474 vfs = interfaceutil.Attribute(
1475 vfs = interfaceutil.Attribute(
1475 """VFS rooted at the .hg directory.
1476 """VFS rooted at the .hg directory.
1476
1477
1477 Used to access repository data not in the store.
1478 Used to access repository data not in the store.
1478 """
1479 """
1479 )
1480 )
1480
1481
1481 svfs = interfaceutil.Attribute(
1482 svfs = interfaceutil.Attribute(
1482 """VFS rooted at the store.
1483 """VFS rooted at the store.
1483
1484
1484 Used to access repository data in the store. Typically .hg/store.
1485 Used to access repository data in the store. Typically .hg/store.
1485 But can point elsewhere if the store is shared.
1486 But can point elsewhere if the store is shared.
1486 """
1487 """
1487 )
1488 )
1488
1489
1489 root = interfaceutil.Attribute(
1490 root = interfaceutil.Attribute(
1490 """Path to the root of the working directory."""
1491 """Path to the root of the working directory."""
1491 )
1492 )
1492
1493
1493 path = interfaceutil.Attribute("""Path to the .hg directory.""")
1494 path = interfaceutil.Attribute("""Path to the .hg directory.""")
1494
1495
1495 origroot = interfaceutil.Attribute(
1496 origroot = interfaceutil.Attribute(
1496 """The filesystem path that was used to construct the repo."""
1497 """The filesystem path that was used to construct the repo."""
1497 )
1498 )
1498
1499
1499 auditor = interfaceutil.Attribute(
1500 auditor = interfaceutil.Attribute(
1500 """A pathauditor for the working directory.
1501 """A pathauditor for the working directory.
1501
1502
1502 This checks if a path refers to a nested repository.
1503 This checks if a path refers to a nested repository.
1503
1504
1504 Operates on the filesystem.
1505 Operates on the filesystem.
1505 """
1506 """
1506 )
1507 )
1507
1508
1508 nofsauditor = interfaceutil.Attribute(
1509 nofsauditor = interfaceutil.Attribute(
1509 """A pathauditor for the working directory.
1510 """A pathauditor for the working directory.
1510
1511
1511 This is like ``auditor`` except it doesn't do filesystem checks.
1512 This is like ``auditor`` except it doesn't do filesystem checks.
1512 """
1513 """
1513 )
1514 )
1514
1515
1515 baseui = interfaceutil.Attribute(
1516 baseui = interfaceutil.Attribute(
1516 """Original ui instance passed into constructor."""
1517 """Original ui instance passed into constructor."""
1517 )
1518 )
1518
1519
1519 ui = interfaceutil.Attribute("""Main ui instance for this instance.""")
1520 ui = interfaceutil.Attribute("""Main ui instance for this instance.""")
1520
1521
1521 sharedpath = interfaceutil.Attribute(
1522 sharedpath = interfaceutil.Attribute(
1522 """Path to the .hg directory of the repo this repo was shared from."""
1523 """Path to the .hg directory of the repo this repo was shared from."""
1523 )
1524 )
1524
1525
1525 store = interfaceutil.Attribute("""A store instance.""")
1526 store = interfaceutil.Attribute("""A store instance.""")
1526
1527
1527 spath = interfaceutil.Attribute("""Path to the store.""")
1528 spath = interfaceutil.Attribute("""Path to the store.""")
1528
1529
1529 sjoin = interfaceutil.Attribute("""Alias to self.store.join.""")
1530 sjoin = interfaceutil.Attribute("""Alias to self.store.join.""")
1530
1531
1531 cachevfs = interfaceutil.Attribute(
1532 cachevfs = interfaceutil.Attribute(
1532 """A VFS used to access the cache directory.
1533 """A VFS used to access the cache directory.
1533
1534
1534 Typically .hg/cache.
1535 Typically .hg/cache.
1535 """
1536 """
1536 )
1537 )
1537
1538
1538 wcachevfs = interfaceutil.Attribute(
1539 wcachevfs = interfaceutil.Attribute(
1539 """A VFS used to access the cache directory dedicated to working copy
1540 """A VFS used to access the cache directory dedicated to working copy
1540
1541
1541 Typically .hg/wcache.
1542 Typically .hg/wcache.
1542 """
1543 """
1543 )
1544 )
1544
1545
1545 filteredrevcache = interfaceutil.Attribute(
1546 filteredrevcache = interfaceutil.Attribute(
1546 """Holds sets of revisions to be filtered."""
1547 """Holds sets of revisions to be filtered."""
1547 )
1548 )
1548
1549
1549 names = interfaceutil.Attribute("""A ``namespaces`` instance.""")
1550 names = interfaceutil.Attribute("""A ``namespaces`` instance.""")
1550
1551
1551 filecopiesmode = interfaceutil.Attribute(
1552 filecopiesmode = interfaceutil.Attribute(
1552 """The way files copies should be dealt with in this repo."""
1553 """The way files copies should be dealt with in this repo."""
1553 )
1554 )
1554
1555
1555 def close():
1556 def close():
1556 """Close the handle on this repository."""
1557 """Close the handle on this repository."""
1557
1558
1558 def peer():
1559 def peer():
1559 """Obtain an object conforming to the ``peer`` interface."""
1560 """Obtain an object conforming to the ``peer`` interface."""
1560
1561
1561 def unfiltered():
1562 def unfiltered():
1562 """Obtain an unfiltered/raw view of this repo."""
1563 """Obtain an unfiltered/raw view of this repo."""
1563
1564
1564 def filtered(name, visibilityexceptions=None):
1565 def filtered(name, visibilityexceptions=None):
1565 """Obtain a named view of this repository."""
1566 """Obtain a named view of this repository."""
1566
1567
1567 obsstore = interfaceutil.Attribute("""A store of obsolescence data.""")
1568 obsstore = interfaceutil.Attribute("""A store of obsolescence data.""")
1568
1569
1569 changelog = interfaceutil.Attribute("""A handle on the changelog revlog.""")
1570 changelog = interfaceutil.Attribute("""A handle on the changelog revlog.""")
1570
1571
1571 manifestlog = interfaceutil.Attribute(
1572 manifestlog = interfaceutil.Attribute(
1572 """An instance conforming to the ``imanifestlog`` interface.
1573 """An instance conforming to the ``imanifestlog`` interface.
1573
1574
1574 Provides access to manifests for the repository.
1575 Provides access to manifests for the repository.
1575 """
1576 """
1576 )
1577 )
1577
1578
1578 dirstate = interfaceutil.Attribute("""Working directory state.""")
1579 dirstate = interfaceutil.Attribute("""Working directory state.""")
1579
1580
1580 narrowpats = interfaceutil.Attribute(
1581 narrowpats = interfaceutil.Attribute(
1581 """Matcher patterns for this repository's narrowspec."""
1582 """Matcher patterns for this repository's narrowspec."""
1582 )
1583 )
1583
1584
1584 def narrowmatch(match=None, includeexact=False):
1585 def narrowmatch(match=None, includeexact=False):
1585 """Obtain a matcher for the narrowspec."""
1586 """Obtain a matcher for the narrowspec."""
1586
1587
1587 def setnarrowpats(newincludes, newexcludes):
1588 def setnarrowpats(newincludes, newexcludes):
1588 """Define the narrowspec for this repository."""
1589 """Define the narrowspec for this repository."""
1589
1590
1590 def __getitem__(changeid):
1591 def __getitem__(changeid):
1591 """Try to resolve a changectx."""
1592 """Try to resolve a changectx."""
1592
1593
1593 def __contains__(changeid):
1594 def __contains__(changeid):
1594 """Whether a changeset exists."""
1595 """Whether a changeset exists."""
1595
1596
1596 def __nonzero__():
1597 def __nonzero__():
1597 """Always returns True."""
1598 """Always returns True."""
1598 return True
1599 return True
1599
1600
1600 __bool__ = __nonzero__
1601 __bool__ = __nonzero__
1601
1602
1602 def __len__():
1603 def __len__():
1603 """Returns the number of changesets in the repo."""
1604 """Returns the number of changesets in the repo."""
1604
1605
1605 def __iter__():
1606 def __iter__():
1606 """Iterate over revisions in the changelog."""
1607 """Iterate over revisions in the changelog."""
1607
1608
1608 def revs(expr, *args):
1609 def revs(expr, *args):
1609 """Evaluate a revset.
1610 """Evaluate a revset.
1610
1611
1611 Emits revisions.
1612 Emits revisions.
1612 """
1613 """
1613
1614
1614 def set(expr, *args):
1615 def set(expr, *args):
1615 """Evaluate a revset.
1616 """Evaluate a revset.
1616
1617
1617 Emits changectx instances.
1618 Emits changectx instances.
1618 """
1619 """
1619
1620
1620 def anyrevs(specs, user=False, localalias=None):
1621 def anyrevs(specs, user=False, localalias=None):
1621 """Find revisions matching one of the given revsets."""
1622 """Find revisions matching one of the given revsets."""
1622
1623
1623 def url():
1624 def url():
1624 """Returns a string representing the location of this repo."""
1625 """Returns a string representing the location of this repo."""
1625
1626
1626 def hook(name, throw=False, **args):
1627 def hook(name, throw=False, **args):
1627 """Call a hook."""
1628 """Call a hook."""
1628
1629
1629 def tags():
1630 def tags():
1630 """Return a mapping of tag to node."""
1631 """Return a mapping of tag to node."""
1631
1632
1632 def tagtype(tagname):
1633 def tagtype(tagname):
1633 """Return the type of a given tag."""
1634 """Return the type of a given tag."""
1634
1635
1635 def tagslist():
1636 def tagslist():
1636 """Return a list of tags ordered by revision."""
1637 """Return a list of tags ordered by revision."""
1637
1638
1638 def nodetags(node):
1639 def nodetags(node):
1639 """Return the tags associated with a node."""
1640 """Return the tags associated with a node."""
1640
1641
1641 def nodebookmarks(node):
1642 def nodebookmarks(node):
1642 """Return the list of bookmarks pointing to the specified node."""
1643 """Return the list of bookmarks pointing to the specified node."""
1643
1644
1644 def branchmap():
1645 def branchmap():
1645 """Return a mapping of branch to heads in that branch."""
1646 """Return a mapping of branch to heads in that branch."""
1646
1647
1647 def revbranchcache():
1648 def revbranchcache():
1648 pass
1649 pass
1649
1650
1650 def register_changeset(rev, changelogrevision):
1651 def register_changeset(rev, changelogrevision):
1651 """Extension point for caches for new nodes.
1652 """Extension point for caches for new nodes.
1652
1653
1653 Multiple consumers are expected to need parts of the changelogrevision,
1654 Multiple consumers are expected to need parts of the changelogrevision,
1654 so it is provided as optimization to avoid duplicate lookups. A simple
1655 so it is provided as optimization to avoid duplicate lookups. A simple
1655 cache would be fragile when other revisions are accessed, too."""
1656 cache would be fragile when other revisions are accessed, too."""
1656 pass
1657 pass
1657
1658
1658 def branchtip(branchtip, ignoremissing=False):
1659 def branchtip(branchtip, ignoremissing=False):
1659 """Return the tip node for a given branch."""
1660 """Return the tip node for a given branch."""
1660
1661
1661 def lookup(key):
1662 def lookup(key):
1662 """Resolve the node for a revision."""
1663 """Resolve the node for a revision."""
1663
1664
1664 def lookupbranch(key):
1665 def lookupbranch(key):
1665 """Look up the branch name of the given revision or branch name."""
1666 """Look up the branch name of the given revision or branch name."""
1666
1667
1667 def known(nodes):
1668 def known(nodes):
1668 """Determine whether a series of nodes is known.
1669 """Determine whether a series of nodes is known.
1669
1670
1670 Returns a list of bools.
1671 Returns a list of bools.
1671 """
1672 """
1672
1673
1673 def local():
1674 def local():
1674 """Whether the repository is local."""
1675 """Whether the repository is local."""
1675 return True
1676 return True
1676
1677
1677 def publishing():
1678 def publishing():
1678 """Whether the repository is a publishing repository."""
1679 """Whether the repository is a publishing repository."""
1679
1680
1680 def cancopy():
1681 def cancopy():
1681 pass
1682 pass
1682
1683
1683 def shared():
1684 def shared():
1684 """The type of shared repository or None."""
1685 """The type of shared repository or None."""
1685
1686
1686 def wjoin(f, *insidef):
1687 def wjoin(f, *insidef):
1687 """Calls self.vfs.reljoin(self.root, f, *insidef)"""
1688 """Calls self.vfs.reljoin(self.root, f, *insidef)"""
1688
1689
1689 def setparents(p1, p2):
1690 def setparents(p1, p2):
1690 """Set the parent nodes of the working directory."""
1691 """Set the parent nodes of the working directory."""
1691
1692
1692 def filectx(path, changeid=None, fileid=None):
1693 def filectx(path, changeid=None, fileid=None):
1693 """Obtain a filectx for the given file revision."""
1694 """Obtain a filectx for the given file revision."""
1694
1695
1695 def getcwd():
1696 def getcwd():
1696 """Obtain the current working directory from the dirstate."""
1697 """Obtain the current working directory from the dirstate."""
1697
1698
1698 def pathto(f, cwd=None):
1699 def pathto(f, cwd=None):
1699 """Obtain the relative path to a file."""
1700 """Obtain the relative path to a file."""
1700
1701
1701 def adddatafilter(name, fltr):
1702 def adddatafilter(name, fltr):
1702 pass
1703 pass
1703
1704
1704 def wread(filename):
1705 def wread(filename):
1705 """Read a file from wvfs, using data filters."""
1706 """Read a file from wvfs, using data filters."""
1706
1707
1707 def wwrite(filename, data, flags, backgroundclose=False, **kwargs):
1708 def wwrite(filename, data, flags, backgroundclose=False, **kwargs):
1708 """Write data to a file in the wvfs, using data filters."""
1709 """Write data to a file in the wvfs, using data filters."""
1709
1710
1710 def wwritedata(filename, data):
1711 def wwritedata(filename, data):
1711 """Resolve data for writing to the wvfs, using data filters."""
1712 """Resolve data for writing to the wvfs, using data filters."""
1712
1713
1713 def currenttransaction():
1714 def currenttransaction():
1714 """Obtain the current transaction instance or None."""
1715 """Obtain the current transaction instance or None."""
1715
1716
1716 def transaction(desc, report=None):
1717 def transaction(desc, report=None):
1717 """Open a new transaction to write to the repository."""
1718 """Open a new transaction to write to the repository."""
1718
1719
1719 def undofiles():
1720 def undofiles():
1720 """Returns a list of (vfs, path) for files to undo transactions."""
1721 """Returns a list of (vfs, path) for files to undo transactions."""
1721
1722
1722 def recover():
1723 def recover():
1723 """Roll back an interrupted transaction."""
1724 """Roll back an interrupted transaction."""
1724
1725
1725 def rollback(dryrun=False, force=False):
1726 def rollback(dryrun=False, force=False):
1726 """Undo the last transaction.
1727 """Undo the last transaction.
1727
1728
1728 DANGEROUS.
1729 DANGEROUS.
1729 """
1730 """
1730
1731
1731 def updatecaches(tr=None, full=False):
1732 def updatecaches(tr=None, full=False):
1732 """Warm repo caches."""
1733 """Warm repo caches."""
1733
1734
1734 def invalidatecaches():
1735 def invalidatecaches():
1735 """Invalidate cached data due to the repository mutating."""
1736 """Invalidate cached data due to the repository mutating."""
1736
1737
1737 def invalidatevolatilesets():
1738 def invalidatevolatilesets():
1738 pass
1739 pass
1739
1740
1740 def invalidatedirstate():
1741 def invalidatedirstate():
1741 """Invalidate the dirstate."""
1742 """Invalidate the dirstate."""
1742
1743
1743 def invalidate(clearfilecache=False):
1744 def invalidate(clearfilecache=False):
1744 pass
1745 pass
1745
1746
1746 def invalidateall():
1747 def invalidateall():
1747 pass
1748 pass
1748
1749
1749 def lock(wait=True):
1750 def lock(wait=True):
1750 """Lock the repository store and return a lock instance."""
1751 """Lock the repository store and return a lock instance."""
1751
1752
1752 def wlock(wait=True):
1753 def wlock(wait=True):
1753 """Lock the non-store parts of the repository."""
1754 """Lock the non-store parts of the repository."""
1754
1755
1755 def currentwlock():
1756 def currentwlock():
1756 """Return the wlock if it's held or None."""
1757 """Return the wlock if it's held or None."""
1757
1758
1758 def checkcommitpatterns(wctx, match, status, fail):
1759 def checkcommitpatterns(wctx, match, status, fail):
1759 pass
1760 pass
1760
1761
1761 def commit(
1762 def commit(
1762 text=b'',
1763 text=b'',
1763 user=None,
1764 user=None,
1764 date=None,
1765 date=None,
1765 match=None,
1766 match=None,
1766 force=False,
1767 force=False,
1767 editor=False,
1768 editor=False,
1768 extra=None,
1769 extra=None,
1769 ):
1770 ):
1770 """Add a new revision to the repository."""
1771 """Add a new revision to the repository."""
1771
1772
1772 def commitctx(ctx, error=False, origctx=None):
1773 def commitctx(ctx, error=False, origctx=None):
1773 """Commit a commitctx instance to the repository."""
1774 """Commit a commitctx instance to the repository."""
1774
1775
1775 def destroying():
1776 def destroying():
1776 """Inform the repository that nodes are about to be destroyed."""
1777 """Inform the repository that nodes are about to be destroyed."""
1777
1778
1778 def destroyed():
1779 def destroyed():
1779 """Inform the repository that nodes have been destroyed."""
1780 """Inform the repository that nodes have been destroyed."""
1780
1781
1781 def status(
1782 def status(
1782 node1=b'.',
1783 node1=b'.',
1783 node2=None,
1784 node2=None,
1784 match=None,
1785 match=None,
1785 ignored=False,
1786 ignored=False,
1786 clean=False,
1787 clean=False,
1787 unknown=False,
1788 unknown=False,
1788 listsubrepos=False,
1789 listsubrepos=False,
1789 ):
1790 ):
1790 """Convenience method to call repo[x].status()."""
1791 """Convenience method to call repo[x].status()."""
1791
1792
1792 def addpostdsstatus(ps):
1793 def addpostdsstatus(ps):
1793 pass
1794 pass
1794
1795
1795 def postdsstatus():
1796 def postdsstatus():
1796 pass
1797 pass
1797
1798
1798 def clearpostdsstatus():
1799 def clearpostdsstatus():
1799 pass
1800 pass
1800
1801
1801 def heads(start=None):
1802 def heads(start=None):
1802 """Obtain list of nodes that are DAG heads."""
1803 """Obtain list of nodes that are DAG heads."""
1803
1804
1804 def branchheads(branch=None, start=None, closed=False):
1805 def branchheads(branch=None, start=None, closed=False):
1805 pass
1806 pass
1806
1807
1807 def branches(nodes):
1808 def branches(nodes):
1808 pass
1809 pass
1809
1810
1810 def between(pairs):
1811 def between(pairs):
1811 pass
1812 pass
1812
1813
1813 def checkpush(pushop):
1814 def checkpush(pushop):
1814 pass
1815 pass
1815
1816
1816 prepushoutgoinghooks = interfaceutil.Attribute("""util.hooks instance.""")
1817 prepushoutgoinghooks = interfaceutil.Attribute("""util.hooks instance.""")
1817
1818
1818 def pushkey(namespace, key, old, new):
1819 def pushkey(namespace, key, old, new):
1819 pass
1820 pass
1820
1821
1821 def listkeys(namespace):
1822 def listkeys(namespace):
1822 pass
1823 pass
1823
1824
1824 def debugwireargs(one, two, three=None, four=None, five=None):
1825 def debugwireargs(one, two, three=None, four=None, five=None):
1825 pass
1826 pass
1826
1827
1827 def savecommitmessage(text):
1828 def savecommitmessage(text):
1828 pass
1829 pass
1829
1830
1830
1831
1831 class completelocalrepository(
1832 class completelocalrepository(
1832 ilocalrepositorymain, ilocalrepositoryfilestorage
1833 ilocalrepositorymain, ilocalrepositoryfilestorage
1833 ):
1834 ):
1834 """Complete interface for a local repository."""
1835 """Complete interface for a local repository."""
1835
1836
1836
1837
1837 class iwireprotocolcommandcacher(interfaceutil.Interface):
1838 class iwireprotocolcommandcacher(interfaceutil.Interface):
1838 """Represents a caching backend for wire protocol commands.
1839 """Represents a caching backend for wire protocol commands.
1839
1840
1840 Wire protocol version 2 supports transparent caching of many commands.
1841 Wire protocol version 2 supports transparent caching of many commands.
1841 To leverage this caching, servers can activate objects that cache
1842 To leverage this caching, servers can activate objects that cache
1842 command responses. Objects handle both cache writing and reading.
1843 command responses. Objects handle both cache writing and reading.
1843 This interface defines how that response caching mechanism works.
1844 This interface defines how that response caching mechanism works.
1844
1845
1845 Wire protocol version 2 commands emit a series of objects that are
1846 Wire protocol version 2 commands emit a series of objects that are
1846 serialized and sent to the client. The caching layer exists between
1847 serialized and sent to the client. The caching layer exists between
1847 the invocation of the command function and the sending of its output
1848 the invocation of the command function and the sending of its output
1848 objects to an output layer.
1849 objects to an output layer.
1849
1850
1850 Instances of this interface represent a binding to a cache that
1851 Instances of this interface represent a binding to a cache that
1851 can serve a response (in place of calling a command function) and/or
1852 can serve a response (in place of calling a command function) and/or
1852 write responses to a cache for subsequent use.
1853 write responses to a cache for subsequent use.
1853
1854
1854 When a command request arrives, the following happens with regards
1855 When a command request arrives, the following happens with regards
1855 to this interface:
1856 to this interface:
1856
1857
1857 1. The server determines whether the command request is cacheable.
1858 1. The server determines whether the command request is cacheable.
1858 2. If it is, an instance of this interface is spawned.
1859 2. If it is, an instance of this interface is spawned.
1859 3. The cacher is activated in a context manager (``__enter__`` is called).
1860 3. The cacher is activated in a context manager (``__enter__`` is called).
1860 4. A cache *key* for that request is derived. This will call the
1861 4. A cache *key* for that request is derived. This will call the
1861 instance's ``adjustcachekeystate()`` method so the derivation
1862 instance's ``adjustcachekeystate()`` method so the derivation
1862 can be influenced.
1863 can be influenced.
1863 5. The cacher is informed of the derived cache key via a call to
1864 5. The cacher is informed of the derived cache key via a call to
1864 ``setcachekey()``.
1865 ``setcachekey()``.
1865 6. The cacher's ``lookup()`` method is called to test for presence of
1866 6. The cacher's ``lookup()`` method is called to test for presence of
1866 the derived key in the cache.
1867 the derived key in the cache.
1867 7. If ``lookup()`` returns a hit, that cached result is used in place
1868 7. If ``lookup()`` returns a hit, that cached result is used in place
1868 of invoking the command function. ``__exit__`` is called and the instance
1869 of invoking the command function. ``__exit__`` is called and the instance
1869 is discarded.
1870 is discarded.
1870 8. The command function is invoked.
1871 8. The command function is invoked.
1871 9. ``onobject()`` is called for each object emitted by the command
1872 9. ``onobject()`` is called for each object emitted by the command
1872 function.
1873 function.
1873 10. After the final object is seen, ``onfinished()`` is called.
1874 10. After the final object is seen, ``onfinished()`` is called.
1874 11. ``__exit__`` is called to signal the end of use of the instance.
1875 11. ``__exit__`` is called to signal the end of use of the instance.
1875
1876
1876 Cache *key* derivation can be influenced by the instance.
1877 Cache *key* derivation can be influenced by the instance.
1877
1878
1878 Cache keys are initially derived by a deterministic representation of
1879 Cache keys are initially derived by a deterministic representation of
1879 the command request. This includes the command name, arguments, protocol
1880 the command request. This includes the command name, arguments, protocol
1880 version, etc. This initial key derivation is performed by CBOR-encoding a
1881 version, etc. This initial key derivation is performed by CBOR-encoding a
1881 data structure and feeding that output into a hasher.
1882 data structure and feeding that output into a hasher.
1882
1883
1883 Instances of this interface can influence this initial key derivation
1884 Instances of this interface can influence this initial key derivation
1884 via ``adjustcachekeystate()``.
1885 via ``adjustcachekeystate()``.
1885
1886
1886 The instance is informed of the derived cache key via a call to
1887 The instance is informed of the derived cache key via a call to
1887 ``setcachekey()``. The instance must store the key locally so it can
1888 ``setcachekey()``. The instance must store the key locally so it can
1888 be consulted on subsequent operations that may require it.
1889 be consulted on subsequent operations that may require it.
1889
1890
1890 When constructed, the instance has access to a callable that can be used
1891 When constructed, the instance has access to a callable that can be used
1891 for encoding response objects. This callable receives as its single
1892 for encoding response objects. This callable receives as its single
1892 argument an object emitted by a command function. It returns an iterable
1893 argument an object emitted by a command function. It returns an iterable
1893 of bytes chunks representing the encoded object. Unless the cacher is
1894 of bytes chunks representing the encoded object. Unless the cacher is
1894 caching native Python objects in memory or has a way of reconstructing
1895 caching native Python objects in memory or has a way of reconstructing
1895 the original Python objects, implementations typically call this function
1896 the original Python objects, implementations typically call this function
1896 to produce bytes from the output objects and then store those bytes in
1897 to produce bytes from the output objects and then store those bytes in
1897 the cache. When it comes time to re-emit those bytes, they are wrapped
1898 the cache. When it comes time to re-emit those bytes, they are wrapped
1898 in a ``wireprototypes.encodedresponse`` instance to tell the output
1899 in a ``wireprototypes.encodedresponse`` instance to tell the output
1899 layer that they are pre-encoded.
1900 layer that they are pre-encoded.
1900
1901
1901 When receiving the objects emitted by the command function, instances
1902 When receiving the objects emitted by the command function, instances
1902 can choose what to do with those objects. The simplest thing to do is
1903 can choose what to do with those objects. The simplest thing to do is
1903 re-emit the original objects. They will be forwarded to the output
1904 re-emit the original objects. They will be forwarded to the output
1904 layer and will be processed as if the cacher did not exist.
1905 layer and will be processed as if the cacher did not exist.
1905
1906
1906 Implementations could also choose to not emit objects - instead locally
1907 Implementations could also choose to not emit objects - instead locally
1907 buffering objects or their encoded representation. They could then emit
1908 buffering objects or their encoded representation. They could then emit
1908 a single "coalesced" object when ``onfinished()`` is called. In
1909 a single "coalesced" object when ``onfinished()`` is called. In
1909 this way, the implementation would function as a filtering layer of
1910 this way, the implementation would function as a filtering layer of
1910 sorts.
1911 sorts.
1911
1912
1912 When caching objects, typically the encoded form of the object will
1913 When caching objects, typically the encoded form of the object will
1913 be stored. Keep in mind that if the original object is forwarded to
1914 be stored. Keep in mind that if the original object is forwarded to
1914 the output layer, it will need to be encoded there as well. For large
1915 the output layer, it will need to be encoded there as well. For large
1915 output, this redundant encoding could add overhead. Implementations
1916 output, this redundant encoding could add overhead. Implementations
1916 could wrap the encoded object data in ``wireprototypes.encodedresponse``
1917 could wrap the encoded object data in ``wireprototypes.encodedresponse``
1917 instances to avoid this overhead.
1918 instances to avoid this overhead.
1918 """
1919 """
1919
1920
1920 def __enter__():
1921 def __enter__():
1921 """Marks the instance as active.
1922 """Marks the instance as active.
1922
1923
1923 Should return self.
1924 Should return self.
1924 """
1925 """
1925
1926
1926 def __exit__(exctype, excvalue, exctb):
1927 def __exit__(exctype, excvalue, exctb):
1927 """Called when cacher is no longer used.
1928 """Called when cacher is no longer used.
1928
1929
1929 This can be used by implementations to perform cleanup actions (e.g.
1930 This can be used by implementations to perform cleanup actions (e.g.
1930 disconnecting network sockets, aborting a partially cached response.
1931 disconnecting network sockets, aborting a partially cached response.
1931 """
1932 """
1932
1933
1933 def adjustcachekeystate(state):
1934 def adjustcachekeystate(state):
1934 """Influences cache key derivation by adjusting state to derive key.
1935 """Influences cache key derivation by adjusting state to derive key.
1935
1936
1936 A dict defining the state used to derive the cache key is passed.
1937 A dict defining the state used to derive the cache key is passed.
1937
1938
1938 Implementations can modify this dict to record additional state that
1939 Implementations can modify this dict to record additional state that
1939 is wanted to influence key derivation.
1940 is wanted to influence key derivation.
1940
1941
1941 Implementations are *highly* encouraged to not modify or delete
1942 Implementations are *highly* encouraged to not modify or delete
1942 existing keys.
1943 existing keys.
1943 """
1944 """
1944
1945
1945 def setcachekey(key):
1946 def setcachekey(key):
1946 """Record the derived cache key for this request.
1947 """Record the derived cache key for this request.
1947
1948
1948 Instances may mutate the key for internal usage, as desired. e.g.
1949 Instances may mutate the key for internal usage, as desired. e.g.
1949 instances may wish to prepend the repo name, introduce path
1950 instances may wish to prepend the repo name, introduce path
1950 components for filesystem or URL addressing, etc. Behavior is up to
1951 components for filesystem or URL addressing, etc. Behavior is up to
1951 the cache.
1952 the cache.
1952
1953
1953 Returns a bool indicating if the request is cacheable by this
1954 Returns a bool indicating if the request is cacheable by this
1954 instance.
1955 instance.
1955 """
1956 """
1956
1957
1957 def lookup():
1958 def lookup():
1958 """Attempt to resolve an entry in the cache.
1959 """Attempt to resolve an entry in the cache.
1959
1960
1960 The instance is instructed to look for the cache key that it was
1961 The instance is instructed to look for the cache key that it was
1961 informed about via the call to ``setcachekey()``.
1962 informed about via the call to ``setcachekey()``.
1962
1963
1963 If there's no cache hit or the cacher doesn't wish to use the cached
1964 If there's no cache hit or the cacher doesn't wish to use the cached
1964 entry, ``None`` should be returned.
1965 entry, ``None`` should be returned.
1965
1966
1966 Else, a dict defining the cached result should be returned. The
1967 Else, a dict defining the cached result should be returned. The
1967 dict may have the following keys:
1968 dict may have the following keys:
1968
1969
1969 objs
1970 objs
1970 An iterable of objects that should be sent to the client. That
1971 An iterable of objects that should be sent to the client. That
1971 iterable of objects is expected to be what the command function
1972 iterable of objects is expected to be what the command function
1972 would return if invoked or an equivalent representation thereof.
1973 would return if invoked or an equivalent representation thereof.
1973 """
1974 """
1974
1975
1975 def onobject(obj):
1976 def onobject(obj):
1976 """Called when a new object is emitted from the command function.
1977 """Called when a new object is emitted from the command function.
1977
1978
1978 Receives as its argument the object that was emitted from the
1979 Receives as its argument the object that was emitted from the
1979 command function.
1980 command function.
1980
1981
1981 This method returns an iterator of objects to forward to the output
1982 This method returns an iterator of objects to forward to the output
1982 layer. The easiest implementation is a generator that just
1983 layer. The easiest implementation is a generator that just
1983 ``yield obj``.
1984 ``yield obj``.
1984 """
1985 """
1985
1986
1986 def onfinished():
1987 def onfinished():
1987 """Called after all objects have been emitted from the command function.
1988 """Called after all objects have been emitted from the command function.
1988
1989
1989 Implementations should return an iterator of objects to forward to
1990 Implementations should return an iterator of objects to forward to
1990 the output layer.
1991 the output layer.
1991
1992
1992 This method can be a generator.
1993 This method can be a generator.
1993 """
1994 """
@@ -1,3086 +1,3087 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Storage back-end for Mercurial.
8 """Storage back-end for Mercurial.
9
9
10 This provides efficient delta storage with O(1) retrieve and append
10 This provides efficient delta storage with O(1) retrieve and append
11 and O(changes) merge between branches.
11 and O(changes) merge between branches.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import collections
16 import collections
17 import contextlib
17 import contextlib
18 import errno
18 import errno
19 import io
19 import io
20 import os
20 import os
21 import struct
21 import struct
22 import zlib
22 import zlib
23
23
24 # import stuff from node for others to import from revlog
24 # import stuff from node for others to import from revlog
25 from .node import (
25 from .node import (
26 bin,
26 bin,
27 hex,
27 hex,
28 nullhex,
28 nullhex,
29 nullid,
29 nullid,
30 nullrev,
30 nullrev,
31 short,
31 short,
32 wdirfilenodeids,
32 wdirfilenodeids,
33 wdirhex,
33 wdirhex,
34 wdirid,
34 wdirid,
35 wdirrev,
35 wdirrev,
36 )
36 )
37 from .i18n import _
37 from .i18n import _
38 from .pycompat import getattr
38 from .pycompat import getattr
39 from .revlogutils.constants import (
39 from .revlogutils.constants import (
40 FLAG_GENERALDELTA,
40 FLAG_GENERALDELTA,
41 FLAG_INLINE_DATA,
41 FLAG_INLINE_DATA,
42 REVLOGV0,
42 REVLOGV0,
43 REVLOGV1,
43 REVLOGV1,
44 REVLOGV1_FLAGS,
44 REVLOGV1_FLAGS,
45 REVLOGV2,
45 REVLOGV2,
46 REVLOGV2_FLAGS,
46 REVLOGV2_FLAGS,
47 REVLOG_DEFAULT_FLAGS,
47 REVLOG_DEFAULT_FLAGS,
48 REVLOG_DEFAULT_FORMAT,
48 REVLOG_DEFAULT_FORMAT,
49 REVLOG_DEFAULT_VERSION,
49 REVLOG_DEFAULT_VERSION,
50 )
50 )
51 from .revlogutils.flagutil import (
51 from .revlogutils.flagutil import (
52 REVIDX_DEFAULT_FLAGS,
52 REVIDX_DEFAULT_FLAGS,
53 REVIDX_ELLIPSIS,
53 REVIDX_ELLIPSIS,
54 REVIDX_EXTSTORED,
54 REVIDX_EXTSTORED,
55 REVIDX_FLAGS_ORDER,
55 REVIDX_FLAGS_ORDER,
56 REVIDX_HASCOPIESINFO,
56 REVIDX_HASCOPIESINFO,
57 REVIDX_ISCENSORED,
57 REVIDX_ISCENSORED,
58 REVIDX_RAWTEXT_CHANGING_FLAGS,
58 REVIDX_RAWTEXT_CHANGING_FLAGS,
59 REVIDX_SIDEDATA,
59 REVIDX_SIDEDATA,
60 )
60 )
61 from .thirdparty import attr
61 from .thirdparty import attr
62 from . import (
62 from . import (
63 ancestor,
63 ancestor,
64 dagop,
64 dagop,
65 error,
65 error,
66 mdiff,
66 mdiff,
67 policy,
67 policy,
68 pycompat,
68 pycompat,
69 templatefilters,
69 templatefilters,
70 util,
70 util,
71 )
71 )
72 from .interfaces import (
72 from .interfaces import (
73 repository,
73 repository,
74 util as interfaceutil,
74 util as interfaceutil,
75 )
75 )
76 from .revlogutils import (
76 from .revlogutils import (
77 deltas as deltautil,
77 deltas as deltautil,
78 flagutil,
78 flagutil,
79 nodemap as nodemaputil,
79 nodemap as nodemaputil,
80 sidedata as sidedatautil,
80 sidedata as sidedatautil,
81 )
81 )
82 from .utils import (
82 from .utils import (
83 storageutil,
83 storageutil,
84 stringutil,
84 stringutil,
85 )
85 )
86
86
87 # blanked usage of all the name to prevent pyflakes constraints
87 # blanked usage of all the name to prevent pyflakes constraints
88 # We need these name available in the module for extensions.
88 # We need these name available in the module for extensions.
89 REVLOGV0
89 REVLOGV0
90 REVLOGV1
90 REVLOGV1
91 REVLOGV2
91 REVLOGV2
92 FLAG_INLINE_DATA
92 FLAG_INLINE_DATA
93 FLAG_GENERALDELTA
93 FLAG_GENERALDELTA
94 REVLOG_DEFAULT_FLAGS
94 REVLOG_DEFAULT_FLAGS
95 REVLOG_DEFAULT_FORMAT
95 REVLOG_DEFAULT_FORMAT
96 REVLOG_DEFAULT_VERSION
96 REVLOG_DEFAULT_VERSION
97 REVLOGV1_FLAGS
97 REVLOGV1_FLAGS
98 REVLOGV2_FLAGS
98 REVLOGV2_FLAGS
99 REVIDX_ISCENSORED
99 REVIDX_ISCENSORED
100 REVIDX_ELLIPSIS
100 REVIDX_ELLIPSIS
101 REVIDX_SIDEDATA
101 REVIDX_SIDEDATA
102 REVIDX_HASCOPIESINFO
102 REVIDX_HASCOPIESINFO
103 REVIDX_EXTSTORED
103 REVIDX_EXTSTORED
104 REVIDX_DEFAULT_FLAGS
104 REVIDX_DEFAULT_FLAGS
105 REVIDX_FLAGS_ORDER
105 REVIDX_FLAGS_ORDER
106 REVIDX_RAWTEXT_CHANGING_FLAGS
106 REVIDX_RAWTEXT_CHANGING_FLAGS
107
107
108 parsers = policy.importmod('parsers')
108 parsers = policy.importmod('parsers')
109 rustancestor = policy.importrust('ancestor')
109 rustancestor = policy.importrust('ancestor')
110 rustdagop = policy.importrust('dagop')
110 rustdagop = policy.importrust('dagop')
111 rustrevlog = policy.importrust('revlog')
111 rustrevlog = policy.importrust('revlog')
112
112
113 # Aliased for performance.
113 # Aliased for performance.
114 _zlibdecompress = zlib.decompress
114 _zlibdecompress = zlib.decompress
115
115
116 # max size of revlog with inline data
116 # max size of revlog with inline data
117 _maxinline = 131072
117 _maxinline = 131072
118 _chunksize = 1048576
118 _chunksize = 1048576
119
119
120 # Flag processors for REVIDX_ELLIPSIS.
120 # Flag processors for REVIDX_ELLIPSIS.
121 def ellipsisreadprocessor(rl, text):
121 def ellipsisreadprocessor(rl, text):
122 return text, False, {}
122 return text, False, {}
123
123
124
124
125 def ellipsiswriteprocessor(rl, text, sidedata):
125 def ellipsiswriteprocessor(rl, text, sidedata):
126 return text, False
126 return text, False
127
127
128
128
129 def ellipsisrawprocessor(rl, text):
129 def ellipsisrawprocessor(rl, text):
130 return False
130 return False
131
131
132
132
133 ellipsisprocessor = (
133 ellipsisprocessor = (
134 ellipsisreadprocessor,
134 ellipsisreadprocessor,
135 ellipsiswriteprocessor,
135 ellipsiswriteprocessor,
136 ellipsisrawprocessor,
136 ellipsisrawprocessor,
137 )
137 )
138
138
139
139
140 def getoffset(q):
140 def getoffset(q):
141 return int(q >> 16)
141 return int(q >> 16)
142
142
143
143
144 def gettype(q):
144 def gettype(q):
145 return int(q & 0xFFFF)
145 return int(q & 0xFFFF)
146
146
147
147
148 def offset_type(offset, type):
148 def offset_type(offset, type):
149 if (type & ~flagutil.REVIDX_KNOWN_FLAGS) != 0:
149 if (type & ~flagutil.REVIDX_KNOWN_FLAGS) != 0:
150 raise ValueError(b'unknown revlog index flags')
150 raise ValueError(b'unknown revlog index flags')
151 return int(int(offset) << 16 | type)
151 return int(int(offset) << 16 | type)
152
152
153
153
154 def _verify_revision(rl, skipflags, state, node):
154 def _verify_revision(rl, skipflags, state, node):
155 """Verify the integrity of the given revlog ``node`` while providing a hook
155 """Verify the integrity of the given revlog ``node`` while providing a hook
156 point for extensions to influence the operation."""
156 point for extensions to influence the operation."""
157 if skipflags:
157 if skipflags:
158 state[b'skipread'].add(node)
158 state[b'skipread'].add(node)
159 else:
159 else:
160 # Side-effect: read content and verify hash.
160 # Side-effect: read content and verify hash.
161 rl.revision(node)
161 rl.revision(node)
162
162
163
163
164 # True if a fast implementation for persistent-nodemap is available
164 # True if a fast implementation for persistent-nodemap is available
165 #
165 #
166 # We also consider we have a "fast" implementation in "pure" python because
166 # We also consider we have a "fast" implementation in "pure" python because
167 # people using pure don't really have performance consideration (and a
167 # people using pure don't really have performance consideration (and a
168 # wheelbarrow of other slowness source)
168 # wheelbarrow of other slowness source)
169 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or util.safehasattr(
169 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or util.safehasattr(
170 parsers, 'BaseIndexObject'
170 parsers, 'BaseIndexObject'
171 )
171 )
172
172
173
173
174 @attr.s(slots=True, frozen=True)
174 @attr.s(slots=True, frozen=True)
175 class _revisioninfo(object):
175 class _revisioninfo(object):
176 """Information about a revision that allows building its fulltext
176 """Information about a revision that allows building its fulltext
177 node: expected hash of the revision
177 node: expected hash of the revision
178 p1, p2: parent revs of the revision
178 p1, p2: parent revs of the revision
179 btext: built text cache consisting of a one-element list
179 btext: built text cache consisting of a one-element list
180 cachedelta: (baserev, uncompressed_delta) or None
180 cachedelta: (baserev, uncompressed_delta) or None
181 flags: flags associated to the revision storage
181 flags: flags associated to the revision storage
182
182
183 One of btext[0] or cachedelta must be set.
183 One of btext[0] or cachedelta must be set.
184 """
184 """
185
185
186 node = attr.ib()
186 node = attr.ib()
187 p1 = attr.ib()
187 p1 = attr.ib()
188 p2 = attr.ib()
188 p2 = attr.ib()
189 btext = attr.ib()
189 btext = attr.ib()
190 textlen = attr.ib()
190 textlen = attr.ib()
191 cachedelta = attr.ib()
191 cachedelta = attr.ib()
192 flags = attr.ib()
192 flags = attr.ib()
193
193
194
194
195 @interfaceutil.implementer(repository.irevisiondelta)
195 @interfaceutil.implementer(repository.irevisiondelta)
196 @attr.s(slots=True)
196 @attr.s(slots=True)
197 class revlogrevisiondelta(object):
197 class revlogrevisiondelta(object):
198 node = attr.ib()
198 node = attr.ib()
199 p1node = attr.ib()
199 p1node = attr.ib()
200 p2node = attr.ib()
200 p2node = attr.ib()
201 basenode = attr.ib()
201 basenode = attr.ib()
202 flags = attr.ib()
202 flags = attr.ib()
203 baserevisionsize = attr.ib()
203 baserevisionsize = attr.ib()
204 revision = attr.ib()
204 revision = attr.ib()
205 delta = attr.ib()
205 delta = attr.ib()
206 linknode = attr.ib(default=None)
206 linknode = attr.ib(default=None)
207
207
208
208
209 @interfaceutil.implementer(repository.iverifyproblem)
209 @interfaceutil.implementer(repository.iverifyproblem)
210 @attr.s(frozen=True)
210 @attr.s(frozen=True)
211 class revlogproblem(object):
211 class revlogproblem(object):
212 warning = attr.ib(default=None)
212 warning = attr.ib(default=None)
213 error = attr.ib(default=None)
213 error = attr.ib(default=None)
214 node = attr.ib(default=None)
214 node = attr.ib(default=None)
215
215
216
216
217 # index v0:
217 # index v0:
218 # 4 bytes: offset
218 # 4 bytes: offset
219 # 4 bytes: compressed length
219 # 4 bytes: compressed length
220 # 4 bytes: base rev
220 # 4 bytes: base rev
221 # 4 bytes: link rev
221 # 4 bytes: link rev
222 # 20 bytes: parent 1 nodeid
222 # 20 bytes: parent 1 nodeid
223 # 20 bytes: parent 2 nodeid
223 # 20 bytes: parent 2 nodeid
224 # 20 bytes: nodeid
224 # 20 bytes: nodeid
225 indexformatv0 = struct.Struct(b">4l20s20s20s")
225 indexformatv0 = struct.Struct(b">4l20s20s20s")
226 indexformatv0_pack = indexformatv0.pack
226 indexformatv0_pack = indexformatv0.pack
227 indexformatv0_unpack = indexformatv0.unpack
227 indexformatv0_unpack = indexformatv0.unpack
228
228
229
229
230 class revlogoldindex(list):
230 class revlogoldindex(list):
231 @property
231 @property
232 def nodemap(self):
232 def nodemap(self):
233 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
233 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
234 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
234 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
235 return self._nodemap
235 return self._nodemap
236
236
237 @util.propertycache
237 @util.propertycache
238 def _nodemap(self):
238 def _nodemap(self):
239 nodemap = nodemaputil.NodeMap({nullid: nullrev})
239 nodemap = nodemaputil.NodeMap({nullid: nullrev})
240 for r in range(0, len(self)):
240 for r in range(0, len(self)):
241 n = self[r][7]
241 n = self[r][7]
242 nodemap[n] = r
242 nodemap[n] = r
243 return nodemap
243 return nodemap
244
244
245 def has_node(self, node):
245 def has_node(self, node):
246 """return True if the node exist in the index"""
246 """return True if the node exist in the index"""
247 return node in self._nodemap
247 return node in self._nodemap
248
248
249 def rev(self, node):
249 def rev(self, node):
250 """return a revision for a node
250 """return a revision for a node
251
251
252 If the node is unknown, raise a RevlogError"""
252 If the node is unknown, raise a RevlogError"""
253 return self._nodemap[node]
253 return self._nodemap[node]
254
254
255 def get_rev(self, node):
255 def get_rev(self, node):
256 """return a revision for a node
256 """return a revision for a node
257
257
258 If the node is unknown, return None"""
258 If the node is unknown, return None"""
259 return self._nodemap.get(node)
259 return self._nodemap.get(node)
260
260
261 def append(self, tup):
261 def append(self, tup):
262 self._nodemap[tup[7]] = len(self)
262 self._nodemap[tup[7]] = len(self)
263 super(revlogoldindex, self).append(tup)
263 super(revlogoldindex, self).append(tup)
264
264
265 def __delitem__(self, i):
265 def __delitem__(self, i):
266 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
266 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
267 raise ValueError(b"deleting slices only supports a:-1 with step 1")
267 raise ValueError(b"deleting slices only supports a:-1 with step 1")
268 for r in pycompat.xrange(i.start, len(self)):
268 for r in pycompat.xrange(i.start, len(self)):
269 del self._nodemap[self[r][7]]
269 del self._nodemap[self[r][7]]
270 super(revlogoldindex, self).__delitem__(i)
270 super(revlogoldindex, self).__delitem__(i)
271
271
272 def clearcaches(self):
272 def clearcaches(self):
273 self.__dict__.pop('_nodemap', None)
273 self.__dict__.pop('_nodemap', None)
274
274
275 def __getitem__(self, i):
275 def __getitem__(self, i):
276 if i == -1:
276 if i == -1:
277 return (0, 0, 0, -1, -1, -1, -1, nullid)
277 return (0, 0, 0, -1, -1, -1, -1, nullid)
278 return list.__getitem__(self, i)
278 return list.__getitem__(self, i)
279
279
280
280
281 class revlogoldio(object):
281 class revlogoldio(object):
282 def __init__(self):
282 def __init__(self):
283 self.size = indexformatv0.size
283 self.size = indexformatv0.size
284
284
285 def parseindex(self, data, inline):
285 def parseindex(self, data, inline):
286 s = self.size
286 s = self.size
287 index = []
287 index = []
288 nodemap = nodemaputil.NodeMap({nullid: nullrev})
288 nodemap = nodemaputil.NodeMap({nullid: nullrev})
289 n = off = 0
289 n = off = 0
290 l = len(data)
290 l = len(data)
291 while off + s <= l:
291 while off + s <= l:
292 cur = data[off : off + s]
292 cur = data[off : off + s]
293 off += s
293 off += s
294 e = indexformatv0_unpack(cur)
294 e = indexformatv0_unpack(cur)
295 # transform to revlogv1 format
295 # transform to revlogv1 format
296 e2 = (
296 e2 = (
297 offset_type(e[0], 0),
297 offset_type(e[0], 0),
298 e[1],
298 e[1],
299 -1,
299 -1,
300 e[2],
300 e[2],
301 e[3],
301 e[3],
302 nodemap.get(e[4], nullrev),
302 nodemap.get(e[4], nullrev),
303 nodemap.get(e[5], nullrev),
303 nodemap.get(e[5], nullrev),
304 e[6],
304 e[6],
305 )
305 )
306 index.append(e2)
306 index.append(e2)
307 nodemap[e[6]] = n
307 nodemap[e[6]] = n
308 n += 1
308 n += 1
309
309
310 index = revlogoldindex(index)
310 index = revlogoldindex(index)
311 return index, None
311 return index, None
312
312
313 def packentry(self, entry, node, version, rev):
313 def packentry(self, entry, node, version, rev):
314 if gettype(entry[0]):
314 if gettype(entry[0]):
315 raise error.RevlogError(
315 raise error.RevlogError(
316 _(b'index entry flags need revlog version 1')
316 _(b'index entry flags need revlog version 1')
317 )
317 )
318 e2 = (
318 e2 = (
319 getoffset(entry[0]),
319 getoffset(entry[0]),
320 entry[1],
320 entry[1],
321 entry[3],
321 entry[3],
322 entry[4],
322 entry[4],
323 node(entry[5]),
323 node(entry[5]),
324 node(entry[6]),
324 node(entry[6]),
325 entry[7],
325 entry[7],
326 )
326 )
327 return indexformatv0_pack(*e2)
327 return indexformatv0_pack(*e2)
328
328
329
329
330 # index ng:
330 # index ng:
331 # 6 bytes: offset
331 # 6 bytes: offset
332 # 2 bytes: flags
332 # 2 bytes: flags
333 # 4 bytes: compressed length
333 # 4 bytes: compressed length
334 # 4 bytes: uncompressed length
334 # 4 bytes: uncompressed length
335 # 4 bytes: base rev
335 # 4 bytes: base rev
336 # 4 bytes: link rev
336 # 4 bytes: link rev
337 # 4 bytes: parent 1 rev
337 # 4 bytes: parent 1 rev
338 # 4 bytes: parent 2 rev
338 # 4 bytes: parent 2 rev
339 # 32 bytes: nodeid
339 # 32 bytes: nodeid
340 indexformatng = struct.Struct(b">Qiiiiii20s12x")
340 indexformatng = struct.Struct(b">Qiiiiii20s12x")
341 indexformatng_pack = indexformatng.pack
341 indexformatng_pack = indexformatng.pack
342 versionformat = struct.Struct(b">I")
342 versionformat = struct.Struct(b">I")
343 versionformat_pack = versionformat.pack
343 versionformat_pack = versionformat.pack
344 versionformat_unpack = versionformat.unpack
344 versionformat_unpack = versionformat.unpack
345
345
346 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
346 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
347 # signed integer)
347 # signed integer)
348 _maxentrysize = 0x7FFFFFFF
348 _maxentrysize = 0x7FFFFFFF
349
349
350
350
351 class revlogio(object):
351 class revlogio(object):
352 def __init__(self):
352 def __init__(self):
353 self.size = indexformatng.size
353 self.size = indexformatng.size
354
354
355 def parseindex(self, data, inline):
355 def parseindex(self, data, inline):
356 # call the C implementation to parse the index data
356 # call the C implementation to parse the index data
357 index, cache = parsers.parse_index2(data, inline)
357 index, cache = parsers.parse_index2(data, inline)
358 return index, cache
358 return index, cache
359
359
360 def packentry(self, entry, node, version, rev):
360 def packentry(self, entry, node, version, rev):
361 p = indexformatng_pack(*entry)
361 p = indexformatng_pack(*entry)
362 if rev == 0:
362 if rev == 0:
363 p = versionformat_pack(version) + p[4:]
363 p = versionformat_pack(version) + p[4:]
364 return p
364 return p
365
365
366
366
367 NodemapRevlogIO = None
367 NodemapRevlogIO = None
368
368
369 if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
369 if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
370
370
371 class NodemapRevlogIO(revlogio):
371 class NodemapRevlogIO(revlogio):
372 """A debug oriented IO class that return a PersistentNodeMapIndexObject
372 """A debug oriented IO class that return a PersistentNodeMapIndexObject
373
373
374 The PersistentNodeMapIndexObject object is meant to test the persistent nodemap feature.
374 The PersistentNodeMapIndexObject object is meant to test the persistent nodemap feature.
375 """
375 """
376
376
377 def parseindex(self, data, inline):
377 def parseindex(self, data, inline):
378 index, cache = parsers.parse_index_devel_nodemap(data, inline)
378 index, cache = parsers.parse_index_devel_nodemap(data, inline)
379 return index, cache
379 return index, cache
380
380
381
381
382 class rustrevlogio(revlogio):
382 class rustrevlogio(revlogio):
383 def parseindex(self, data, inline):
383 def parseindex(self, data, inline):
384 index, cache = super(rustrevlogio, self).parseindex(data, inline)
384 index, cache = super(rustrevlogio, self).parseindex(data, inline)
385 return rustrevlog.MixedIndex(index), cache
385 return rustrevlog.MixedIndex(index), cache
386
386
387
387
388 class revlog(object):
388 class revlog(object):
389 """
389 """
390 the underlying revision storage object
390 the underlying revision storage object
391
391
392 A revlog consists of two parts, an index and the revision data.
392 A revlog consists of two parts, an index and the revision data.
393
393
394 The index is a file with a fixed record size containing
394 The index is a file with a fixed record size containing
395 information on each revision, including its nodeid (hash), the
395 information on each revision, including its nodeid (hash), the
396 nodeids of its parents, the position and offset of its data within
396 nodeids of its parents, the position and offset of its data within
397 the data file, and the revision it's based on. Finally, each entry
397 the data file, and the revision it's based on. Finally, each entry
398 contains a linkrev entry that can serve as a pointer to external
398 contains a linkrev entry that can serve as a pointer to external
399 data.
399 data.
400
400
401 The revision data itself is a linear collection of data chunks.
401 The revision data itself is a linear collection of data chunks.
402 Each chunk represents a revision and is usually represented as a
402 Each chunk represents a revision and is usually represented as a
403 delta against the previous chunk. To bound lookup time, runs of
403 delta against the previous chunk. To bound lookup time, runs of
404 deltas are limited to about 2 times the length of the original
404 deltas are limited to about 2 times the length of the original
405 version data. This makes retrieval of a version proportional to
405 version data. This makes retrieval of a version proportional to
406 its size, or O(1) relative to the number of revisions.
406 its size, or O(1) relative to the number of revisions.
407
407
408 Both pieces of the revlog are written to in an append-only
408 Both pieces of the revlog are written to in an append-only
409 fashion, which means we never need to rewrite a file to insert or
409 fashion, which means we never need to rewrite a file to insert or
410 remove data, and can use some simple techniques to avoid the need
410 remove data, and can use some simple techniques to avoid the need
411 for locking while reading.
411 for locking while reading.
412
412
413 If checkambig, indexfile is opened with checkambig=True at
413 If checkambig, indexfile is opened with checkambig=True at
414 writing, to avoid file stat ambiguity.
414 writing, to avoid file stat ambiguity.
415
415
416 If mmaplargeindex is True, and an mmapindexthreshold is set, the
416 If mmaplargeindex is True, and an mmapindexthreshold is set, the
417 index will be mmapped rather than read if it is larger than the
417 index will be mmapped rather than read if it is larger than the
418 configured threshold.
418 configured threshold.
419
419
420 If censorable is True, the revlog can have censored revisions.
420 If censorable is True, the revlog can have censored revisions.
421
421
422 If `upperboundcomp` is not None, this is the expected maximal gain from
422 If `upperboundcomp` is not None, this is the expected maximal gain from
423 compression for the data content.
423 compression for the data content.
424 """
424 """
425
425
426 _flagserrorclass = error.RevlogError
426 _flagserrorclass = error.RevlogError
427
427
428 def __init__(
428 def __init__(
429 self,
429 self,
430 opener,
430 opener,
431 indexfile,
431 indexfile,
432 datafile=None,
432 datafile=None,
433 checkambig=False,
433 checkambig=False,
434 mmaplargeindex=False,
434 mmaplargeindex=False,
435 censorable=False,
435 censorable=False,
436 upperboundcomp=None,
436 upperboundcomp=None,
437 persistentnodemap=False,
437 persistentnodemap=False,
438 ):
438 ):
439 """
439 """
440 create a revlog object
440 create a revlog object
441
441
442 opener is a function that abstracts the file opening operation
442 opener is a function that abstracts the file opening operation
443 and can be used to implement COW semantics or the like.
443 and can be used to implement COW semantics or the like.
444
444
445 """
445 """
446 self.upperboundcomp = upperboundcomp
446 self.upperboundcomp = upperboundcomp
447 self.indexfile = indexfile
447 self.indexfile = indexfile
448 self.datafile = datafile or (indexfile[:-2] + b".d")
448 self.datafile = datafile or (indexfile[:-2] + b".d")
449 self.nodemap_file = None
449 self.nodemap_file = None
450 if persistentnodemap:
450 if persistentnodemap:
451 self.nodemap_file = nodemaputil.get_nodemap_file(
451 self.nodemap_file = nodemaputil.get_nodemap_file(
452 opener, self.indexfile
452 opener, self.indexfile
453 )
453 )
454
454
455 self.opener = opener
455 self.opener = opener
456 # When True, indexfile is opened with checkambig=True at writing, to
456 # When True, indexfile is opened with checkambig=True at writing, to
457 # avoid file stat ambiguity.
457 # avoid file stat ambiguity.
458 self._checkambig = checkambig
458 self._checkambig = checkambig
459 self._mmaplargeindex = mmaplargeindex
459 self._mmaplargeindex = mmaplargeindex
460 self._censorable = censorable
460 self._censorable = censorable
461 # 3-tuple of (node, rev, text) for a raw revision.
461 # 3-tuple of (node, rev, text) for a raw revision.
462 self._revisioncache = None
462 self._revisioncache = None
463 # Maps rev to chain base rev.
463 # Maps rev to chain base rev.
464 self._chainbasecache = util.lrucachedict(100)
464 self._chainbasecache = util.lrucachedict(100)
465 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
465 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
466 self._chunkcache = (0, b'')
466 self._chunkcache = (0, b'')
467 # How much data to read and cache into the raw revlog data cache.
467 # How much data to read and cache into the raw revlog data cache.
468 self._chunkcachesize = 65536
468 self._chunkcachesize = 65536
469 self._maxchainlen = None
469 self._maxchainlen = None
470 self._deltabothparents = True
470 self._deltabothparents = True
471 self.index = None
471 self.index = None
472 self._nodemap_docket = None
472 self._nodemap_docket = None
473 # Mapping of partial identifiers to full nodes.
473 # Mapping of partial identifiers to full nodes.
474 self._pcache = {}
474 self._pcache = {}
475 # Mapping of revision integer to full node.
475 # Mapping of revision integer to full node.
476 self._compengine = b'zlib'
476 self._compengine = b'zlib'
477 self._compengineopts = {}
477 self._compengineopts = {}
478 self._maxdeltachainspan = -1
478 self._maxdeltachainspan = -1
479 self._withsparseread = False
479 self._withsparseread = False
480 self._sparserevlog = False
480 self._sparserevlog = False
481 self._srdensitythreshold = 0.50
481 self._srdensitythreshold = 0.50
482 self._srmingapsize = 262144
482 self._srmingapsize = 262144
483
483
484 # Make copy of flag processors so each revlog instance can support
484 # Make copy of flag processors so each revlog instance can support
485 # custom flags.
485 # custom flags.
486 self._flagprocessors = dict(flagutil.flagprocessors)
486 self._flagprocessors = dict(flagutil.flagprocessors)
487
487
488 # 2-tuple of file handles being used for active writing.
488 # 2-tuple of file handles being used for active writing.
489 self._writinghandles = None
489 self._writinghandles = None
490
490
491 self._loadindex()
491 self._loadindex()
492
492
493 def _loadindex(self):
493 def _loadindex(self):
494 mmapindexthreshold = None
494 mmapindexthreshold = None
495 opts = self.opener.options
495 opts = self.opener.options
496
496
497 if b'revlogv2' in opts:
497 if b'revlogv2' in opts:
498 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
498 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
499 elif b'revlogv1' in opts:
499 elif b'revlogv1' in opts:
500 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
500 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
501 if b'generaldelta' in opts:
501 if b'generaldelta' in opts:
502 newversionflags |= FLAG_GENERALDELTA
502 newversionflags |= FLAG_GENERALDELTA
503 elif b'revlogv0' in self.opener.options:
503 elif b'revlogv0' in self.opener.options:
504 newversionflags = REVLOGV0
504 newversionflags = REVLOGV0
505 else:
505 else:
506 newversionflags = REVLOG_DEFAULT_VERSION
506 newversionflags = REVLOG_DEFAULT_VERSION
507
507
508 if b'chunkcachesize' in opts:
508 if b'chunkcachesize' in opts:
509 self._chunkcachesize = opts[b'chunkcachesize']
509 self._chunkcachesize = opts[b'chunkcachesize']
510 if b'maxchainlen' in opts:
510 if b'maxchainlen' in opts:
511 self._maxchainlen = opts[b'maxchainlen']
511 self._maxchainlen = opts[b'maxchainlen']
512 if b'deltabothparents' in opts:
512 if b'deltabothparents' in opts:
513 self._deltabothparents = opts[b'deltabothparents']
513 self._deltabothparents = opts[b'deltabothparents']
514 self._lazydelta = bool(opts.get(b'lazydelta', True))
514 self._lazydelta = bool(opts.get(b'lazydelta', True))
515 self._lazydeltabase = False
515 self._lazydeltabase = False
516 if self._lazydelta:
516 if self._lazydelta:
517 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
517 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
518 if b'compengine' in opts:
518 if b'compengine' in opts:
519 self._compengine = opts[b'compengine']
519 self._compengine = opts[b'compengine']
520 if b'zlib.level' in opts:
520 if b'zlib.level' in opts:
521 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
521 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
522 if b'zstd.level' in opts:
522 if b'zstd.level' in opts:
523 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
523 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
524 if b'maxdeltachainspan' in opts:
524 if b'maxdeltachainspan' in opts:
525 self._maxdeltachainspan = opts[b'maxdeltachainspan']
525 self._maxdeltachainspan = opts[b'maxdeltachainspan']
526 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
526 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
527 mmapindexthreshold = opts[b'mmapindexthreshold']
527 mmapindexthreshold = opts[b'mmapindexthreshold']
528 self.hassidedata = bool(opts.get(b'side-data', False))
528 self.hassidedata = bool(opts.get(b'side-data', False))
529 if self.hassidedata:
529 if self.hassidedata:
530 self._flagprocessors[REVIDX_SIDEDATA] = sidedatautil.processors
530 self._flagprocessors[REVIDX_SIDEDATA] = sidedatautil.processors
531 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
531 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
532 withsparseread = bool(opts.get(b'with-sparse-read', False))
532 withsparseread = bool(opts.get(b'with-sparse-read', False))
533 # sparse-revlog forces sparse-read
533 # sparse-revlog forces sparse-read
534 self._withsparseread = self._sparserevlog or withsparseread
534 self._withsparseread = self._sparserevlog or withsparseread
535 if b'sparse-read-density-threshold' in opts:
535 if b'sparse-read-density-threshold' in opts:
536 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
536 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
537 if b'sparse-read-min-gap-size' in opts:
537 if b'sparse-read-min-gap-size' in opts:
538 self._srmingapsize = opts[b'sparse-read-min-gap-size']
538 self._srmingapsize = opts[b'sparse-read-min-gap-size']
539 if opts.get(b'enableellipsis'):
539 if opts.get(b'enableellipsis'):
540 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
540 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
541
541
542 # revlog v0 doesn't have flag processors
542 # revlog v0 doesn't have flag processors
543 for flag, processor in pycompat.iteritems(
543 for flag, processor in pycompat.iteritems(
544 opts.get(b'flagprocessors', {})
544 opts.get(b'flagprocessors', {})
545 ):
545 ):
546 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
546 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
547
547
548 if self._chunkcachesize <= 0:
548 if self._chunkcachesize <= 0:
549 raise error.RevlogError(
549 raise error.RevlogError(
550 _(b'revlog chunk cache size %r is not greater than 0')
550 _(b'revlog chunk cache size %r is not greater than 0')
551 % self._chunkcachesize
551 % self._chunkcachesize
552 )
552 )
553 elif self._chunkcachesize & (self._chunkcachesize - 1):
553 elif self._chunkcachesize & (self._chunkcachesize - 1):
554 raise error.RevlogError(
554 raise error.RevlogError(
555 _(b'revlog chunk cache size %r is not a power of 2')
555 _(b'revlog chunk cache size %r is not a power of 2')
556 % self._chunkcachesize
556 % self._chunkcachesize
557 )
557 )
558
558
559 indexdata = b''
559 indexdata = b''
560 self._initempty = True
560 self._initempty = True
561 try:
561 try:
562 with self._indexfp() as f:
562 with self._indexfp() as f:
563 if (
563 if (
564 mmapindexthreshold is not None
564 mmapindexthreshold is not None
565 and self.opener.fstat(f).st_size >= mmapindexthreshold
565 and self.opener.fstat(f).st_size >= mmapindexthreshold
566 ):
566 ):
567 # TODO: should .close() to release resources without
567 # TODO: should .close() to release resources without
568 # relying on Python GC
568 # relying on Python GC
569 indexdata = util.buffer(util.mmapread(f))
569 indexdata = util.buffer(util.mmapread(f))
570 else:
570 else:
571 indexdata = f.read()
571 indexdata = f.read()
572 if len(indexdata) > 0:
572 if len(indexdata) > 0:
573 versionflags = versionformat_unpack(indexdata[:4])[0]
573 versionflags = versionformat_unpack(indexdata[:4])[0]
574 self._initempty = False
574 self._initempty = False
575 else:
575 else:
576 versionflags = newversionflags
576 versionflags = newversionflags
577 except IOError as inst:
577 except IOError as inst:
578 if inst.errno != errno.ENOENT:
578 if inst.errno != errno.ENOENT:
579 raise
579 raise
580
580
581 versionflags = newversionflags
581 versionflags = newversionflags
582
582
583 self.version = versionflags
583 self.version = versionflags
584
584
585 flags = versionflags & ~0xFFFF
585 flags = versionflags & ~0xFFFF
586 fmt = versionflags & 0xFFFF
586 fmt = versionflags & 0xFFFF
587
587
588 if fmt == REVLOGV0:
588 if fmt == REVLOGV0:
589 if flags:
589 if flags:
590 raise error.RevlogError(
590 raise error.RevlogError(
591 _(b'unknown flags (%#04x) in version %d revlog %s')
591 _(b'unknown flags (%#04x) in version %d revlog %s')
592 % (flags >> 16, fmt, self.indexfile)
592 % (flags >> 16, fmt, self.indexfile)
593 )
593 )
594
594
595 self._inline = False
595 self._inline = False
596 self._generaldelta = False
596 self._generaldelta = False
597
597
598 elif fmt == REVLOGV1:
598 elif fmt == REVLOGV1:
599 if flags & ~REVLOGV1_FLAGS:
599 if flags & ~REVLOGV1_FLAGS:
600 raise error.RevlogError(
600 raise error.RevlogError(
601 _(b'unknown flags (%#04x) in version %d revlog %s')
601 _(b'unknown flags (%#04x) in version %d revlog %s')
602 % (flags >> 16, fmt, self.indexfile)
602 % (flags >> 16, fmt, self.indexfile)
603 )
603 )
604
604
605 self._inline = versionflags & FLAG_INLINE_DATA
605 self._inline = versionflags & FLAG_INLINE_DATA
606 self._generaldelta = versionflags & FLAG_GENERALDELTA
606 self._generaldelta = versionflags & FLAG_GENERALDELTA
607
607
608 elif fmt == REVLOGV2:
608 elif fmt == REVLOGV2:
609 if flags & ~REVLOGV2_FLAGS:
609 if flags & ~REVLOGV2_FLAGS:
610 raise error.RevlogError(
610 raise error.RevlogError(
611 _(b'unknown flags (%#04x) in version %d revlog %s')
611 _(b'unknown flags (%#04x) in version %d revlog %s')
612 % (flags >> 16, fmt, self.indexfile)
612 % (flags >> 16, fmt, self.indexfile)
613 )
613 )
614
614
615 self._inline = versionflags & FLAG_INLINE_DATA
615 self._inline = versionflags & FLAG_INLINE_DATA
616 # generaldelta implied by version 2 revlogs.
616 # generaldelta implied by version 2 revlogs.
617 self._generaldelta = True
617 self._generaldelta = True
618
618
619 else:
619 else:
620 raise error.RevlogError(
620 raise error.RevlogError(
621 _(b'unknown version (%d) in revlog %s') % (fmt, self.indexfile)
621 _(b'unknown version (%d) in revlog %s') % (fmt, self.indexfile)
622 )
622 )
623 # sparse-revlog can't be on without general-delta (issue6056)
623 # sparse-revlog can't be on without general-delta (issue6056)
624 if not self._generaldelta:
624 if not self._generaldelta:
625 self._sparserevlog = False
625 self._sparserevlog = False
626
626
627 self._storedeltachains = True
627 self._storedeltachains = True
628
628
629 devel_nodemap = (
629 devel_nodemap = (
630 self.nodemap_file
630 self.nodemap_file
631 and opts.get(b'devel-force-nodemap', False)
631 and opts.get(b'devel-force-nodemap', False)
632 and NodemapRevlogIO is not None
632 and NodemapRevlogIO is not None
633 )
633 )
634
634
635 use_rust_index = False
635 use_rust_index = False
636 if rustrevlog is not None:
636 if rustrevlog is not None:
637 if self.nodemap_file is not None:
637 if self.nodemap_file is not None:
638 use_rust_index = True
638 use_rust_index = True
639 else:
639 else:
640 use_rust_index = self.opener.options.get(b'rust.index')
640 use_rust_index = self.opener.options.get(b'rust.index')
641
641
642 self._io = revlogio()
642 self._io = revlogio()
643 if self.version == REVLOGV0:
643 if self.version == REVLOGV0:
644 self._io = revlogoldio()
644 self._io = revlogoldio()
645 elif devel_nodemap:
645 elif devel_nodemap:
646 self._io = NodemapRevlogIO()
646 self._io = NodemapRevlogIO()
647 elif use_rust_index:
647 elif use_rust_index:
648 self._io = rustrevlogio()
648 self._io = rustrevlogio()
649 try:
649 try:
650 d = self._io.parseindex(indexdata, self._inline)
650 d = self._io.parseindex(indexdata, self._inline)
651 index, _chunkcache = d
651 index, _chunkcache = d
652 use_nodemap = (
652 use_nodemap = (
653 not self._inline
653 not self._inline
654 and self.nodemap_file is not None
654 and self.nodemap_file is not None
655 and util.safehasattr(index, 'update_nodemap_data')
655 and util.safehasattr(index, 'update_nodemap_data')
656 )
656 )
657 if use_nodemap:
657 if use_nodemap:
658 nodemap_data = nodemaputil.persisted_data(self)
658 nodemap_data = nodemaputil.persisted_data(self)
659 if nodemap_data is not None:
659 if nodemap_data is not None:
660 docket = nodemap_data[0]
660 docket = nodemap_data[0]
661 if (
661 if (
662 len(d[0]) > docket.tip_rev
662 len(d[0]) > docket.tip_rev
663 and d[0][docket.tip_rev][7] == docket.tip_node
663 and d[0][docket.tip_rev][7] == docket.tip_node
664 ):
664 ):
665 # no changelog tampering
665 # no changelog tampering
666 self._nodemap_docket = docket
666 self._nodemap_docket = docket
667 index.update_nodemap_data(*nodemap_data)
667 index.update_nodemap_data(*nodemap_data)
668 except (ValueError, IndexError):
668 except (ValueError, IndexError):
669 raise error.RevlogError(
669 raise error.RevlogError(
670 _(b"index %s is corrupted") % self.indexfile
670 _(b"index %s is corrupted") % self.indexfile
671 )
671 )
672 self.index, self._chunkcache = d
672 self.index, self._chunkcache = d
673 if not self._chunkcache:
673 if not self._chunkcache:
674 self._chunkclear()
674 self._chunkclear()
675 # revnum -> (chain-length, sum-delta-length)
675 # revnum -> (chain-length, sum-delta-length)
676 self._chaininfocache = util.lrucachedict(500)
676 self._chaininfocache = util.lrucachedict(500)
677 # revlog header -> revlog compressor
677 # revlog header -> revlog compressor
678 self._decompressors = {}
678 self._decompressors = {}
679
679
680 @util.propertycache
680 @util.propertycache
681 def _compressor(self):
681 def _compressor(self):
682 engine = util.compengines[self._compengine]
682 engine = util.compengines[self._compengine]
683 return engine.revlogcompressor(self._compengineopts)
683 return engine.revlogcompressor(self._compengineopts)
684
684
685 def _indexfp(self, mode=b'r'):
685 def _indexfp(self, mode=b'r'):
686 """file object for the revlog's index file"""
686 """file object for the revlog's index file"""
687 args = {'mode': mode}
687 args = {'mode': mode}
688 if mode != b'r':
688 if mode != b'r':
689 args['checkambig'] = self._checkambig
689 args['checkambig'] = self._checkambig
690 if mode == b'w':
690 if mode == b'w':
691 args['atomictemp'] = True
691 args['atomictemp'] = True
692 return self.opener(self.indexfile, **args)
692 return self.opener(self.indexfile, **args)
693
693
694 def _datafp(self, mode=b'r'):
694 def _datafp(self, mode=b'r'):
695 """file object for the revlog's data file"""
695 """file object for the revlog's data file"""
696 return self.opener(self.datafile, mode=mode)
696 return self.opener(self.datafile, mode=mode)
697
697
698 @contextlib.contextmanager
698 @contextlib.contextmanager
699 def _datareadfp(self, existingfp=None):
699 def _datareadfp(self, existingfp=None):
700 """file object suitable to read data"""
700 """file object suitable to read data"""
701 # Use explicit file handle, if given.
701 # Use explicit file handle, if given.
702 if existingfp is not None:
702 if existingfp is not None:
703 yield existingfp
703 yield existingfp
704
704
705 # Use a file handle being actively used for writes, if available.
705 # Use a file handle being actively used for writes, if available.
706 # There is some danger to doing this because reads will seek the
706 # There is some danger to doing this because reads will seek the
707 # file. However, _writeentry() performs a SEEK_END before all writes,
707 # file. However, _writeentry() performs a SEEK_END before all writes,
708 # so we should be safe.
708 # so we should be safe.
709 elif self._writinghandles:
709 elif self._writinghandles:
710 if self._inline:
710 if self._inline:
711 yield self._writinghandles[0]
711 yield self._writinghandles[0]
712 else:
712 else:
713 yield self._writinghandles[1]
713 yield self._writinghandles[1]
714
714
715 # Otherwise open a new file handle.
715 # Otherwise open a new file handle.
716 else:
716 else:
717 if self._inline:
717 if self._inline:
718 func = self._indexfp
718 func = self._indexfp
719 else:
719 else:
720 func = self._datafp
720 func = self._datafp
721 with func() as fp:
721 with func() as fp:
722 yield fp
722 yield fp
723
723
724 def tiprev(self):
724 def tiprev(self):
725 return len(self.index) - 1
725 return len(self.index) - 1
726
726
727 def tip(self):
727 def tip(self):
728 return self.node(self.tiprev())
728 return self.node(self.tiprev())
729
729
730 def __contains__(self, rev):
730 def __contains__(self, rev):
731 return 0 <= rev < len(self)
731 return 0 <= rev < len(self)
732
732
733 def __len__(self):
733 def __len__(self):
734 return len(self.index)
734 return len(self.index)
735
735
736 def __iter__(self):
736 def __iter__(self):
737 return iter(pycompat.xrange(len(self)))
737 return iter(pycompat.xrange(len(self)))
738
738
739 def revs(self, start=0, stop=None):
739 def revs(self, start=0, stop=None):
740 """iterate over all rev in this revlog (from start to stop)"""
740 """iterate over all rev in this revlog (from start to stop)"""
741 return storageutil.iterrevs(len(self), start=start, stop=stop)
741 return storageutil.iterrevs(len(self), start=start, stop=stop)
742
742
743 @property
743 @property
744 def nodemap(self):
744 def nodemap(self):
745 msg = (
745 msg = (
746 b"revlog.nodemap is deprecated, "
746 b"revlog.nodemap is deprecated, "
747 b"use revlog.index.[has_node|rev|get_rev]"
747 b"use revlog.index.[has_node|rev|get_rev]"
748 )
748 )
749 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
749 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
750 return self.index.nodemap
750 return self.index.nodemap
751
751
752 @property
752 @property
753 def _nodecache(self):
753 def _nodecache(self):
754 msg = b"revlog._nodecache is deprecated, use revlog.index.nodemap"
754 msg = b"revlog._nodecache is deprecated, use revlog.index.nodemap"
755 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
755 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
756 return self.index.nodemap
756 return self.index.nodemap
757
757
758 def hasnode(self, node):
758 def hasnode(self, node):
759 try:
759 try:
760 self.rev(node)
760 self.rev(node)
761 return True
761 return True
762 except KeyError:
762 except KeyError:
763 return False
763 return False
764
764
765 def candelta(self, baserev, rev):
765 def candelta(self, baserev, rev):
766 """whether two revisions (baserev, rev) can be delta-ed or not"""
766 """whether two revisions (baserev, rev) can be delta-ed or not"""
767 # Disable delta if either rev requires a content-changing flag
767 # Disable delta if either rev requires a content-changing flag
768 # processor (ex. LFS). This is because such flag processor can alter
768 # processor (ex. LFS). This is because such flag processor can alter
769 # the rawtext content that the delta will be based on, and two clients
769 # the rawtext content that the delta will be based on, and two clients
770 # could have a same revlog node with different flags (i.e. different
770 # could have a same revlog node with different flags (i.e. different
771 # rawtext contents) and the delta could be incompatible.
771 # rawtext contents) and the delta could be incompatible.
772 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
772 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
773 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
773 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
774 ):
774 ):
775 return False
775 return False
776 return True
776 return True
777
777
778 def update_caches(self, transaction):
778 def update_caches(self, transaction):
779 if self.nodemap_file is not None:
779 if self.nodemap_file is not None:
780 if transaction is None:
780 if transaction is None:
781 nodemaputil.update_persistent_nodemap(self)
781 nodemaputil.update_persistent_nodemap(self)
782 else:
782 else:
783 nodemaputil.setup_persistent_nodemap(transaction, self)
783 nodemaputil.setup_persistent_nodemap(transaction, self)
784
784
785 def clearcaches(self):
785 def clearcaches(self):
786 self._revisioncache = None
786 self._revisioncache = None
787 self._chainbasecache.clear()
787 self._chainbasecache.clear()
788 self._chunkcache = (0, b'')
788 self._chunkcache = (0, b'')
789 self._pcache = {}
789 self._pcache = {}
790 self._nodemap_docket = None
790 self._nodemap_docket = None
791 self.index.clearcaches()
791 self.index.clearcaches()
792 # The python code is the one responsible for validating the docket, we
792 # The python code is the one responsible for validating the docket, we
793 # end up having to refresh it here.
793 # end up having to refresh it here.
794 use_nodemap = (
794 use_nodemap = (
795 not self._inline
795 not self._inline
796 and self.nodemap_file is not None
796 and self.nodemap_file is not None
797 and util.safehasattr(self.index, 'update_nodemap_data')
797 and util.safehasattr(self.index, 'update_nodemap_data')
798 )
798 )
799 if use_nodemap:
799 if use_nodemap:
800 nodemap_data = nodemaputil.persisted_data(self)
800 nodemap_data = nodemaputil.persisted_data(self)
801 if nodemap_data is not None:
801 if nodemap_data is not None:
802 self._nodemap_docket = nodemap_data[0]
802 self._nodemap_docket = nodemap_data[0]
803 self.index.update_nodemap_data(*nodemap_data)
803 self.index.update_nodemap_data(*nodemap_data)
804
804
805 def rev(self, node):
805 def rev(self, node):
806 try:
806 try:
807 return self.index.rev(node)
807 return self.index.rev(node)
808 except TypeError:
808 except TypeError:
809 raise
809 raise
810 except error.RevlogError:
810 except error.RevlogError:
811 # parsers.c radix tree lookup failed
811 # parsers.c radix tree lookup failed
812 if node == wdirid or node in wdirfilenodeids:
812 if node == wdirid or node in wdirfilenodeids:
813 raise error.WdirUnsupported
813 raise error.WdirUnsupported
814 raise error.LookupError(node, self.indexfile, _(b'no node'))
814 raise error.LookupError(node, self.indexfile, _(b'no node'))
815
815
816 # Accessors for index entries.
816 # Accessors for index entries.
817
817
818 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
818 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
819 # are flags.
819 # are flags.
820 def start(self, rev):
820 def start(self, rev):
821 return int(self.index[rev][0] >> 16)
821 return int(self.index[rev][0] >> 16)
822
822
823 def flags(self, rev):
823 def flags(self, rev):
824 return self.index[rev][0] & 0xFFFF
824 return self.index[rev][0] & 0xFFFF
825
825
826 def length(self, rev):
826 def length(self, rev):
827 return self.index[rev][1]
827 return self.index[rev][1]
828
828
829 def rawsize(self, rev):
829 def rawsize(self, rev):
830 """return the length of the uncompressed text for a given revision"""
830 """return the length of the uncompressed text for a given revision"""
831 l = self.index[rev][2]
831 l = self.index[rev][2]
832 if l >= 0:
832 if l >= 0:
833 return l
833 return l
834
834
835 t = self.rawdata(rev)
835 t = self.rawdata(rev)
836 return len(t)
836 return len(t)
837
837
838 def size(self, rev):
838 def size(self, rev):
839 """length of non-raw text (processed by a "read" flag processor)"""
839 """length of non-raw text (processed by a "read" flag processor)"""
840 # fast path: if no "read" flag processor could change the content,
840 # fast path: if no "read" flag processor could change the content,
841 # size is rawsize. note: ELLIPSIS is known to not change the content.
841 # size is rawsize. note: ELLIPSIS is known to not change the content.
842 flags = self.flags(rev)
842 flags = self.flags(rev)
843 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
843 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
844 return self.rawsize(rev)
844 return self.rawsize(rev)
845
845
846 return len(self.revision(rev, raw=False))
846 return len(self.revision(rev, raw=False))
847
847
848 def chainbase(self, rev):
848 def chainbase(self, rev):
849 base = self._chainbasecache.get(rev)
849 base = self._chainbasecache.get(rev)
850 if base is not None:
850 if base is not None:
851 return base
851 return base
852
852
853 index = self.index
853 index = self.index
854 iterrev = rev
854 iterrev = rev
855 base = index[iterrev][3]
855 base = index[iterrev][3]
856 while base != iterrev:
856 while base != iterrev:
857 iterrev = base
857 iterrev = base
858 base = index[iterrev][3]
858 base = index[iterrev][3]
859
859
860 self._chainbasecache[rev] = base
860 self._chainbasecache[rev] = base
861 return base
861 return base
862
862
863 def linkrev(self, rev):
863 def linkrev(self, rev):
864 return self.index[rev][4]
864 return self.index[rev][4]
865
865
866 def parentrevs(self, rev):
866 def parentrevs(self, rev):
867 try:
867 try:
868 entry = self.index[rev]
868 entry = self.index[rev]
869 except IndexError:
869 except IndexError:
870 if rev == wdirrev:
870 if rev == wdirrev:
871 raise error.WdirUnsupported
871 raise error.WdirUnsupported
872 raise
872 raise
873
873
874 return entry[5], entry[6]
874 return entry[5], entry[6]
875
875
876 # fast parentrevs(rev) where rev isn't filtered
876 # fast parentrevs(rev) where rev isn't filtered
877 _uncheckedparentrevs = parentrevs
877 _uncheckedparentrevs = parentrevs
878
878
879 def node(self, rev):
879 def node(self, rev):
880 try:
880 try:
881 return self.index[rev][7]
881 return self.index[rev][7]
882 except IndexError:
882 except IndexError:
883 if rev == wdirrev:
883 if rev == wdirrev:
884 raise error.WdirUnsupported
884 raise error.WdirUnsupported
885 raise
885 raise
886
886
887 # Derived from index values.
887 # Derived from index values.
888
888
889 def end(self, rev):
889 def end(self, rev):
890 return self.start(rev) + self.length(rev)
890 return self.start(rev) + self.length(rev)
891
891
892 def parents(self, node):
892 def parents(self, node):
893 i = self.index
893 i = self.index
894 d = i[self.rev(node)]
894 d = i[self.rev(node)]
895 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
895 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
896
896
897 def chainlen(self, rev):
897 def chainlen(self, rev):
898 return self._chaininfo(rev)[0]
898 return self._chaininfo(rev)[0]
899
899
900 def _chaininfo(self, rev):
900 def _chaininfo(self, rev):
901 chaininfocache = self._chaininfocache
901 chaininfocache = self._chaininfocache
902 if rev in chaininfocache:
902 if rev in chaininfocache:
903 return chaininfocache[rev]
903 return chaininfocache[rev]
904 index = self.index
904 index = self.index
905 generaldelta = self._generaldelta
905 generaldelta = self._generaldelta
906 iterrev = rev
906 iterrev = rev
907 e = index[iterrev]
907 e = index[iterrev]
908 clen = 0
908 clen = 0
909 compresseddeltalen = 0
909 compresseddeltalen = 0
910 while iterrev != e[3]:
910 while iterrev != e[3]:
911 clen += 1
911 clen += 1
912 compresseddeltalen += e[1]
912 compresseddeltalen += e[1]
913 if generaldelta:
913 if generaldelta:
914 iterrev = e[3]
914 iterrev = e[3]
915 else:
915 else:
916 iterrev -= 1
916 iterrev -= 1
917 if iterrev in chaininfocache:
917 if iterrev in chaininfocache:
918 t = chaininfocache[iterrev]
918 t = chaininfocache[iterrev]
919 clen += t[0]
919 clen += t[0]
920 compresseddeltalen += t[1]
920 compresseddeltalen += t[1]
921 break
921 break
922 e = index[iterrev]
922 e = index[iterrev]
923 else:
923 else:
924 # Add text length of base since decompressing that also takes
924 # Add text length of base since decompressing that also takes
925 # work. For cache hits the length is already included.
925 # work. For cache hits the length is already included.
926 compresseddeltalen += e[1]
926 compresseddeltalen += e[1]
927 r = (clen, compresseddeltalen)
927 r = (clen, compresseddeltalen)
928 chaininfocache[rev] = r
928 chaininfocache[rev] = r
929 return r
929 return r
930
930
931 def _deltachain(self, rev, stoprev=None):
931 def _deltachain(self, rev, stoprev=None):
932 """Obtain the delta chain for a revision.
932 """Obtain the delta chain for a revision.
933
933
934 ``stoprev`` specifies a revision to stop at. If not specified, we
934 ``stoprev`` specifies a revision to stop at. If not specified, we
935 stop at the base of the chain.
935 stop at the base of the chain.
936
936
937 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
937 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
938 revs in ascending order and ``stopped`` is a bool indicating whether
938 revs in ascending order and ``stopped`` is a bool indicating whether
939 ``stoprev`` was hit.
939 ``stoprev`` was hit.
940 """
940 """
941 # Try C implementation.
941 # Try C implementation.
942 try:
942 try:
943 return self.index.deltachain(rev, stoprev, self._generaldelta)
943 return self.index.deltachain(rev, stoprev, self._generaldelta)
944 except AttributeError:
944 except AttributeError:
945 pass
945 pass
946
946
947 chain = []
947 chain = []
948
948
949 # Alias to prevent attribute lookup in tight loop.
949 # Alias to prevent attribute lookup in tight loop.
950 index = self.index
950 index = self.index
951 generaldelta = self._generaldelta
951 generaldelta = self._generaldelta
952
952
953 iterrev = rev
953 iterrev = rev
954 e = index[iterrev]
954 e = index[iterrev]
955 while iterrev != e[3] and iterrev != stoprev:
955 while iterrev != e[3] and iterrev != stoprev:
956 chain.append(iterrev)
956 chain.append(iterrev)
957 if generaldelta:
957 if generaldelta:
958 iterrev = e[3]
958 iterrev = e[3]
959 else:
959 else:
960 iterrev -= 1
960 iterrev -= 1
961 e = index[iterrev]
961 e = index[iterrev]
962
962
963 if iterrev == stoprev:
963 if iterrev == stoprev:
964 stopped = True
964 stopped = True
965 else:
965 else:
966 chain.append(iterrev)
966 chain.append(iterrev)
967 stopped = False
967 stopped = False
968
968
969 chain.reverse()
969 chain.reverse()
970 return chain, stopped
970 return chain, stopped
971
971
972 def ancestors(self, revs, stoprev=0, inclusive=False):
972 def ancestors(self, revs, stoprev=0, inclusive=False):
973 """Generate the ancestors of 'revs' in reverse revision order.
973 """Generate the ancestors of 'revs' in reverse revision order.
974 Does not generate revs lower than stoprev.
974 Does not generate revs lower than stoprev.
975
975
976 See the documentation for ancestor.lazyancestors for more details."""
976 See the documentation for ancestor.lazyancestors for more details."""
977
977
978 # first, make sure start revisions aren't filtered
978 # first, make sure start revisions aren't filtered
979 revs = list(revs)
979 revs = list(revs)
980 checkrev = self.node
980 checkrev = self.node
981 for r in revs:
981 for r in revs:
982 checkrev(r)
982 checkrev(r)
983 # and we're sure ancestors aren't filtered as well
983 # and we're sure ancestors aren't filtered as well
984
984
985 if rustancestor is not None:
985 if rustancestor is not None:
986 lazyancestors = rustancestor.LazyAncestors
986 lazyancestors = rustancestor.LazyAncestors
987 arg = self.index
987 arg = self.index
988 else:
988 else:
989 lazyancestors = ancestor.lazyancestors
989 lazyancestors = ancestor.lazyancestors
990 arg = self._uncheckedparentrevs
990 arg = self._uncheckedparentrevs
991 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
991 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
992
992
993 def descendants(self, revs):
993 def descendants(self, revs):
994 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
994 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
995
995
996 def findcommonmissing(self, common=None, heads=None):
996 def findcommonmissing(self, common=None, heads=None):
997 """Return a tuple of the ancestors of common and the ancestors of heads
997 """Return a tuple of the ancestors of common and the ancestors of heads
998 that are not ancestors of common. In revset terminology, we return the
998 that are not ancestors of common. In revset terminology, we return the
999 tuple:
999 tuple:
1000
1000
1001 ::common, (::heads) - (::common)
1001 ::common, (::heads) - (::common)
1002
1002
1003 The list is sorted by revision number, meaning it is
1003 The list is sorted by revision number, meaning it is
1004 topologically sorted.
1004 topologically sorted.
1005
1005
1006 'heads' and 'common' are both lists of node IDs. If heads is
1006 'heads' and 'common' are both lists of node IDs. If heads is
1007 not supplied, uses all of the revlog's heads. If common is not
1007 not supplied, uses all of the revlog's heads. If common is not
1008 supplied, uses nullid."""
1008 supplied, uses nullid."""
1009 if common is None:
1009 if common is None:
1010 common = [nullid]
1010 common = [nullid]
1011 if heads is None:
1011 if heads is None:
1012 heads = self.heads()
1012 heads = self.heads()
1013
1013
1014 common = [self.rev(n) for n in common]
1014 common = [self.rev(n) for n in common]
1015 heads = [self.rev(n) for n in heads]
1015 heads = [self.rev(n) for n in heads]
1016
1016
1017 # we want the ancestors, but inclusive
1017 # we want the ancestors, but inclusive
1018 class lazyset(object):
1018 class lazyset(object):
1019 def __init__(self, lazyvalues):
1019 def __init__(self, lazyvalues):
1020 self.addedvalues = set()
1020 self.addedvalues = set()
1021 self.lazyvalues = lazyvalues
1021 self.lazyvalues = lazyvalues
1022
1022
1023 def __contains__(self, value):
1023 def __contains__(self, value):
1024 return value in self.addedvalues or value in self.lazyvalues
1024 return value in self.addedvalues or value in self.lazyvalues
1025
1025
1026 def __iter__(self):
1026 def __iter__(self):
1027 added = self.addedvalues
1027 added = self.addedvalues
1028 for r in added:
1028 for r in added:
1029 yield r
1029 yield r
1030 for r in self.lazyvalues:
1030 for r in self.lazyvalues:
1031 if not r in added:
1031 if not r in added:
1032 yield r
1032 yield r
1033
1033
1034 def add(self, value):
1034 def add(self, value):
1035 self.addedvalues.add(value)
1035 self.addedvalues.add(value)
1036
1036
1037 def update(self, values):
1037 def update(self, values):
1038 self.addedvalues.update(values)
1038 self.addedvalues.update(values)
1039
1039
1040 has = lazyset(self.ancestors(common))
1040 has = lazyset(self.ancestors(common))
1041 has.add(nullrev)
1041 has.add(nullrev)
1042 has.update(common)
1042 has.update(common)
1043
1043
1044 # take all ancestors from heads that aren't in has
1044 # take all ancestors from heads that aren't in has
1045 missing = set()
1045 missing = set()
1046 visit = collections.deque(r for r in heads if r not in has)
1046 visit = collections.deque(r for r in heads if r not in has)
1047 while visit:
1047 while visit:
1048 r = visit.popleft()
1048 r = visit.popleft()
1049 if r in missing:
1049 if r in missing:
1050 continue
1050 continue
1051 else:
1051 else:
1052 missing.add(r)
1052 missing.add(r)
1053 for p in self.parentrevs(r):
1053 for p in self.parentrevs(r):
1054 if p not in has:
1054 if p not in has:
1055 visit.append(p)
1055 visit.append(p)
1056 missing = list(missing)
1056 missing = list(missing)
1057 missing.sort()
1057 missing.sort()
1058 return has, [self.node(miss) for miss in missing]
1058 return has, [self.node(miss) for miss in missing]
1059
1059
1060 def incrementalmissingrevs(self, common=None):
1060 def incrementalmissingrevs(self, common=None):
1061 """Return an object that can be used to incrementally compute the
1061 """Return an object that can be used to incrementally compute the
1062 revision numbers of the ancestors of arbitrary sets that are not
1062 revision numbers of the ancestors of arbitrary sets that are not
1063 ancestors of common. This is an ancestor.incrementalmissingancestors
1063 ancestors of common. This is an ancestor.incrementalmissingancestors
1064 object.
1064 object.
1065
1065
1066 'common' is a list of revision numbers. If common is not supplied, uses
1066 'common' is a list of revision numbers. If common is not supplied, uses
1067 nullrev.
1067 nullrev.
1068 """
1068 """
1069 if common is None:
1069 if common is None:
1070 common = [nullrev]
1070 common = [nullrev]
1071
1071
1072 if rustancestor is not None:
1072 if rustancestor is not None:
1073 return rustancestor.MissingAncestors(self.index, common)
1073 return rustancestor.MissingAncestors(self.index, common)
1074 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1074 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1075
1075
1076 def findmissingrevs(self, common=None, heads=None):
1076 def findmissingrevs(self, common=None, heads=None):
1077 """Return the revision numbers of the ancestors of heads that
1077 """Return the revision numbers of the ancestors of heads that
1078 are not ancestors of common.
1078 are not ancestors of common.
1079
1079
1080 More specifically, return a list of revision numbers corresponding to
1080 More specifically, return a list of revision numbers corresponding to
1081 nodes N such that every N satisfies the following constraints:
1081 nodes N such that every N satisfies the following constraints:
1082
1082
1083 1. N is an ancestor of some node in 'heads'
1083 1. N is an ancestor of some node in 'heads'
1084 2. N is not an ancestor of any node in 'common'
1084 2. N is not an ancestor of any node in 'common'
1085
1085
1086 The list is sorted by revision number, meaning it is
1086 The list is sorted by revision number, meaning it is
1087 topologically sorted.
1087 topologically sorted.
1088
1088
1089 'heads' and 'common' are both lists of revision numbers. If heads is
1089 'heads' and 'common' are both lists of revision numbers. If heads is
1090 not supplied, uses all of the revlog's heads. If common is not
1090 not supplied, uses all of the revlog's heads. If common is not
1091 supplied, uses nullid."""
1091 supplied, uses nullid."""
1092 if common is None:
1092 if common is None:
1093 common = [nullrev]
1093 common = [nullrev]
1094 if heads is None:
1094 if heads is None:
1095 heads = self.headrevs()
1095 heads = self.headrevs()
1096
1096
1097 inc = self.incrementalmissingrevs(common=common)
1097 inc = self.incrementalmissingrevs(common=common)
1098 return inc.missingancestors(heads)
1098 return inc.missingancestors(heads)
1099
1099
1100 def findmissing(self, common=None, heads=None):
1100 def findmissing(self, common=None, heads=None):
1101 """Return the ancestors of heads that are not ancestors of common.
1101 """Return the ancestors of heads that are not ancestors of common.
1102
1102
1103 More specifically, return a list of nodes N such that every N
1103 More specifically, return a list of nodes N such that every N
1104 satisfies the following constraints:
1104 satisfies the following constraints:
1105
1105
1106 1. N is an ancestor of some node in 'heads'
1106 1. N is an ancestor of some node in 'heads'
1107 2. N is not an ancestor of any node in 'common'
1107 2. N is not an ancestor of any node in 'common'
1108
1108
1109 The list is sorted by revision number, meaning it is
1109 The list is sorted by revision number, meaning it is
1110 topologically sorted.
1110 topologically sorted.
1111
1111
1112 'heads' and 'common' are both lists of node IDs. If heads is
1112 'heads' and 'common' are both lists of node IDs. If heads is
1113 not supplied, uses all of the revlog's heads. If common is not
1113 not supplied, uses all of the revlog's heads. If common is not
1114 supplied, uses nullid."""
1114 supplied, uses nullid."""
1115 if common is None:
1115 if common is None:
1116 common = [nullid]
1116 common = [nullid]
1117 if heads is None:
1117 if heads is None:
1118 heads = self.heads()
1118 heads = self.heads()
1119
1119
1120 common = [self.rev(n) for n in common]
1120 common = [self.rev(n) for n in common]
1121 heads = [self.rev(n) for n in heads]
1121 heads = [self.rev(n) for n in heads]
1122
1122
1123 inc = self.incrementalmissingrevs(common=common)
1123 inc = self.incrementalmissingrevs(common=common)
1124 return [self.node(r) for r in inc.missingancestors(heads)]
1124 return [self.node(r) for r in inc.missingancestors(heads)]
1125
1125
1126 def nodesbetween(self, roots=None, heads=None):
1126 def nodesbetween(self, roots=None, heads=None):
1127 """Return a topological path from 'roots' to 'heads'.
1127 """Return a topological path from 'roots' to 'heads'.
1128
1128
1129 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1129 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1130 topologically sorted list of all nodes N that satisfy both of
1130 topologically sorted list of all nodes N that satisfy both of
1131 these constraints:
1131 these constraints:
1132
1132
1133 1. N is a descendant of some node in 'roots'
1133 1. N is a descendant of some node in 'roots'
1134 2. N is an ancestor of some node in 'heads'
1134 2. N is an ancestor of some node in 'heads'
1135
1135
1136 Every node is considered to be both a descendant and an ancestor
1136 Every node is considered to be both a descendant and an ancestor
1137 of itself, so every reachable node in 'roots' and 'heads' will be
1137 of itself, so every reachable node in 'roots' and 'heads' will be
1138 included in 'nodes'.
1138 included in 'nodes'.
1139
1139
1140 'outroots' is the list of reachable nodes in 'roots', i.e., the
1140 'outroots' is the list of reachable nodes in 'roots', i.e., the
1141 subset of 'roots' that is returned in 'nodes'. Likewise,
1141 subset of 'roots' that is returned in 'nodes'. Likewise,
1142 'outheads' is the subset of 'heads' that is also in 'nodes'.
1142 'outheads' is the subset of 'heads' that is also in 'nodes'.
1143
1143
1144 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1144 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1145 unspecified, uses nullid as the only root. If 'heads' is
1145 unspecified, uses nullid as the only root. If 'heads' is
1146 unspecified, uses list of all of the revlog's heads."""
1146 unspecified, uses list of all of the revlog's heads."""
1147 nonodes = ([], [], [])
1147 nonodes = ([], [], [])
1148 if roots is not None:
1148 if roots is not None:
1149 roots = list(roots)
1149 roots = list(roots)
1150 if not roots:
1150 if not roots:
1151 return nonodes
1151 return nonodes
1152 lowestrev = min([self.rev(n) for n in roots])
1152 lowestrev = min([self.rev(n) for n in roots])
1153 else:
1153 else:
1154 roots = [nullid] # Everybody's a descendant of nullid
1154 roots = [nullid] # Everybody's a descendant of nullid
1155 lowestrev = nullrev
1155 lowestrev = nullrev
1156 if (lowestrev == nullrev) and (heads is None):
1156 if (lowestrev == nullrev) and (heads is None):
1157 # We want _all_ the nodes!
1157 # We want _all_ the nodes!
1158 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1158 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1159 if heads is None:
1159 if heads is None:
1160 # All nodes are ancestors, so the latest ancestor is the last
1160 # All nodes are ancestors, so the latest ancestor is the last
1161 # node.
1161 # node.
1162 highestrev = len(self) - 1
1162 highestrev = len(self) - 1
1163 # Set ancestors to None to signal that every node is an ancestor.
1163 # Set ancestors to None to signal that every node is an ancestor.
1164 ancestors = None
1164 ancestors = None
1165 # Set heads to an empty dictionary for later discovery of heads
1165 # Set heads to an empty dictionary for later discovery of heads
1166 heads = {}
1166 heads = {}
1167 else:
1167 else:
1168 heads = list(heads)
1168 heads = list(heads)
1169 if not heads:
1169 if not heads:
1170 return nonodes
1170 return nonodes
1171 ancestors = set()
1171 ancestors = set()
1172 # Turn heads into a dictionary so we can remove 'fake' heads.
1172 # Turn heads into a dictionary so we can remove 'fake' heads.
1173 # Also, later we will be using it to filter out the heads we can't
1173 # Also, later we will be using it to filter out the heads we can't
1174 # find from roots.
1174 # find from roots.
1175 heads = dict.fromkeys(heads, False)
1175 heads = dict.fromkeys(heads, False)
1176 # Start at the top and keep marking parents until we're done.
1176 # Start at the top and keep marking parents until we're done.
1177 nodestotag = set(heads)
1177 nodestotag = set(heads)
1178 # Remember where the top was so we can use it as a limit later.
1178 # Remember where the top was so we can use it as a limit later.
1179 highestrev = max([self.rev(n) for n in nodestotag])
1179 highestrev = max([self.rev(n) for n in nodestotag])
1180 while nodestotag:
1180 while nodestotag:
1181 # grab a node to tag
1181 # grab a node to tag
1182 n = nodestotag.pop()
1182 n = nodestotag.pop()
1183 # Never tag nullid
1183 # Never tag nullid
1184 if n == nullid:
1184 if n == nullid:
1185 continue
1185 continue
1186 # A node's revision number represents its place in a
1186 # A node's revision number represents its place in a
1187 # topologically sorted list of nodes.
1187 # topologically sorted list of nodes.
1188 r = self.rev(n)
1188 r = self.rev(n)
1189 if r >= lowestrev:
1189 if r >= lowestrev:
1190 if n not in ancestors:
1190 if n not in ancestors:
1191 # If we are possibly a descendant of one of the roots
1191 # If we are possibly a descendant of one of the roots
1192 # and we haven't already been marked as an ancestor
1192 # and we haven't already been marked as an ancestor
1193 ancestors.add(n) # Mark as ancestor
1193 ancestors.add(n) # Mark as ancestor
1194 # Add non-nullid parents to list of nodes to tag.
1194 # Add non-nullid parents to list of nodes to tag.
1195 nodestotag.update(
1195 nodestotag.update(
1196 [p for p in self.parents(n) if p != nullid]
1196 [p for p in self.parents(n) if p != nullid]
1197 )
1197 )
1198 elif n in heads: # We've seen it before, is it a fake head?
1198 elif n in heads: # We've seen it before, is it a fake head?
1199 # So it is, real heads should not be the ancestors of
1199 # So it is, real heads should not be the ancestors of
1200 # any other heads.
1200 # any other heads.
1201 heads.pop(n)
1201 heads.pop(n)
1202 if not ancestors:
1202 if not ancestors:
1203 return nonodes
1203 return nonodes
1204 # Now that we have our set of ancestors, we want to remove any
1204 # Now that we have our set of ancestors, we want to remove any
1205 # roots that are not ancestors.
1205 # roots that are not ancestors.
1206
1206
1207 # If one of the roots was nullid, everything is included anyway.
1207 # If one of the roots was nullid, everything is included anyway.
1208 if lowestrev > nullrev:
1208 if lowestrev > nullrev:
1209 # But, since we weren't, let's recompute the lowest rev to not
1209 # But, since we weren't, let's recompute the lowest rev to not
1210 # include roots that aren't ancestors.
1210 # include roots that aren't ancestors.
1211
1211
1212 # Filter out roots that aren't ancestors of heads
1212 # Filter out roots that aren't ancestors of heads
1213 roots = [root for root in roots if root in ancestors]
1213 roots = [root for root in roots if root in ancestors]
1214 # Recompute the lowest revision
1214 # Recompute the lowest revision
1215 if roots:
1215 if roots:
1216 lowestrev = min([self.rev(root) for root in roots])
1216 lowestrev = min([self.rev(root) for root in roots])
1217 else:
1217 else:
1218 # No more roots? Return empty list
1218 # No more roots? Return empty list
1219 return nonodes
1219 return nonodes
1220 else:
1220 else:
1221 # We are descending from nullid, and don't need to care about
1221 # We are descending from nullid, and don't need to care about
1222 # any other roots.
1222 # any other roots.
1223 lowestrev = nullrev
1223 lowestrev = nullrev
1224 roots = [nullid]
1224 roots = [nullid]
1225 # Transform our roots list into a set.
1225 # Transform our roots list into a set.
1226 descendants = set(roots)
1226 descendants = set(roots)
1227 # Also, keep the original roots so we can filter out roots that aren't
1227 # Also, keep the original roots so we can filter out roots that aren't
1228 # 'real' roots (i.e. are descended from other roots).
1228 # 'real' roots (i.e. are descended from other roots).
1229 roots = descendants.copy()
1229 roots = descendants.copy()
1230 # Our topologically sorted list of output nodes.
1230 # Our topologically sorted list of output nodes.
1231 orderedout = []
1231 orderedout = []
1232 # Don't start at nullid since we don't want nullid in our output list,
1232 # Don't start at nullid since we don't want nullid in our output list,
1233 # and if nullid shows up in descendants, empty parents will look like
1233 # and if nullid shows up in descendants, empty parents will look like
1234 # they're descendants.
1234 # they're descendants.
1235 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1235 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1236 n = self.node(r)
1236 n = self.node(r)
1237 isdescendant = False
1237 isdescendant = False
1238 if lowestrev == nullrev: # Everybody is a descendant of nullid
1238 if lowestrev == nullrev: # Everybody is a descendant of nullid
1239 isdescendant = True
1239 isdescendant = True
1240 elif n in descendants:
1240 elif n in descendants:
1241 # n is already a descendant
1241 # n is already a descendant
1242 isdescendant = True
1242 isdescendant = True
1243 # This check only needs to be done here because all the roots
1243 # This check only needs to be done here because all the roots
1244 # will start being marked is descendants before the loop.
1244 # will start being marked is descendants before the loop.
1245 if n in roots:
1245 if n in roots:
1246 # If n was a root, check if it's a 'real' root.
1246 # If n was a root, check if it's a 'real' root.
1247 p = tuple(self.parents(n))
1247 p = tuple(self.parents(n))
1248 # If any of its parents are descendants, it's not a root.
1248 # If any of its parents are descendants, it's not a root.
1249 if (p[0] in descendants) or (p[1] in descendants):
1249 if (p[0] in descendants) or (p[1] in descendants):
1250 roots.remove(n)
1250 roots.remove(n)
1251 else:
1251 else:
1252 p = tuple(self.parents(n))
1252 p = tuple(self.parents(n))
1253 # A node is a descendant if either of its parents are
1253 # A node is a descendant if either of its parents are
1254 # descendants. (We seeded the dependents list with the roots
1254 # descendants. (We seeded the dependents list with the roots
1255 # up there, remember?)
1255 # up there, remember?)
1256 if (p[0] in descendants) or (p[1] in descendants):
1256 if (p[0] in descendants) or (p[1] in descendants):
1257 descendants.add(n)
1257 descendants.add(n)
1258 isdescendant = True
1258 isdescendant = True
1259 if isdescendant and ((ancestors is None) or (n in ancestors)):
1259 if isdescendant and ((ancestors is None) or (n in ancestors)):
1260 # Only include nodes that are both descendants and ancestors.
1260 # Only include nodes that are both descendants and ancestors.
1261 orderedout.append(n)
1261 orderedout.append(n)
1262 if (ancestors is not None) and (n in heads):
1262 if (ancestors is not None) and (n in heads):
1263 # We're trying to figure out which heads are reachable
1263 # We're trying to figure out which heads are reachable
1264 # from roots.
1264 # from roots.
1265 # Mark this head as having been reached
1265 # Mark this head as having been reached
1266 heads[n] = True
1266 heads[n] = True
1267 elif ancestors is None:
1267 elif ancestors is None:
1268 # Otherwise, we're trying to discover the heads.
1268 # Otherwise, we're trying to discover the heads.
1269 # Assume this is a head because if it isn't, the next step
1269 # Assume this is a head because if it isn't, the next step
1270 # will eventually remove it.
1270 # will eventually remove it.
1271 heads[n] = True
1271 heads[n] = True
1272 # But, obviously its parents aren't.
1272 # But, obviously its parents aren't.
1273 for p in self.parents(n):
1273 for p in self.parents(n):
1274 heads.pop(p, None)
1274 heads.pop(p, None)
1275 heads = [head for head, flag in pycompat.iteritems(heads) if flag]
1275 heads = [head for head, flag in pycompat.iteritems(heads) if flag]
1276 roots = list(roots)
1276 roots = list(roots)
1277 assert orderedout
1277 assert orderedout
1278 assert roots
1278 assert roots
1279 assert heads
1279 assert heads
1280 return (orderedout, roots, heads)
1280 return (orderedout, roots, heads)
1281
1281
1282 def headrevs(self, revs=None):
1282 def headrevs(self, revs=None):
1283 if revs is None:
1283 if revs is None:
1284 try:
1284 try:
1285 return self.index.headrevs()
1285 return self.index.headrevs()
1286 except AttributeError:
1286 except AttributeError:
1287 return self._headrevs()
1287 return self._headrevs()
1288 if rustdagop is not None:
1288 if rustdagop is not None:
1289 return rustdagop.headrevs(self.index, revs)
1289 return rustdagop.headrevs(self.index, revs)
1290 return dagop.headrevs(revs, self._uncheckedparentrevs)
1290 return dagop.headrevs(revs, self._uncheckedparentrevs)
1291
1291
1292 def computephases(self, roots):
1292 def computephases(self, roots):
1293 return self.index.computephasesmapsets(roots)
1293 return self.index.computephasesmapsets(roots)
1294
1294
1295 def _headrevs(self):
1295 def _headrevs(self):
1296 count = len(self)
1296 count = len(self)
1297 if not count:
1297 if not count:
1298 return [nullrev]
1298 return [nullrev]
1299 # we won't iter over filtered rev so nobody is a head at start
1299 # we won't iter over filtered rev so nobody is a head at start
1300 ishead = [0] * (count + 1)
1300 ishead = [0] * (count + 1)
1301 index = self.index
1301 index = self.index
1302 for r in self:
1302 for r in self:
1303 ishead[r] = 1 # I may be an head
1303 ishead[r] = 1 # I may be an head
1304 e = index[r]
1304 e = index[r]
1305 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1305 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1306 return [r for r, val in enumerate(ishead) if val]
1306 return [r for r, val in enumerate(ishead) if val]
1307
1307
1308 def heads(self, start=None, stop=None):
1308 def heads(self, start=None, stop=None):
1309 """return the list of all nodes that have no children
1309 """return the list of all nodes that have no children
1310
1310
1311 if start is specified, only heads that are descendants of
1311 if start is specified, only heads that are descendants of
1312 start will be returned
1312 start will be returned
1313 if stop is specified, it will consider all the revs from stop
1313 if stop is specified, it will consider all the revs from stop
1314 as if they had no children
1314 as if they had no children
1315 """
1315 """
1316 if start is None and stop is None:
1316 if start is None and stop is None:
1317 if not len(self):
1317 if not len(self):
1318 return [nullid]
1318 return [nullid]
1319 return [self.node(r) for r in self.headrevs()]
1319 return [self.node(r) for r in self.headrevs()]
1320
1320
1321 if start is None:
1321 if start is None:
1322 start = nullrev
1322 start = nullrev
1323 else:
1323 else:
1324 start = self.rev(start)
1324 start = self.rev(start)
1325
1325
1326 stoprevs = {self.rev(n) for n in stop or []}
1326 stoprevs = {self.rev(n) for n in stop or []}
1327
1327
1328 revs = dagop.headrevssubset(
1328 revs = dagop.headrevssubset(
1329 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1329 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1330 )
1330 )
1331
1331
1332 return [self.node(rev) for rev in revs]
1332 return [self.node(rev) for rev in revs]
1333
1333
1334 def children(self, node):
1334 def children(self, node):
1335 """find the children of a given node"""
1335 """find the children of a given node"""
1336 c = []
1336 c = []
1337 p = self.rev(node)
1337 p = self.rev(node)
1338 for r in self.revs(start=p + 1):
1338 for r in self.revs(start=p + 1):
1339 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1339 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1340 if prevs:
1340 if prevs:
1341 for pr in prevs:
1341 for pr in prevs:
1342 if pr == p:
1342 if pr == p:
1343 c.append(self.node(r))
1343 c.append(self.node(r))
1344 elif p == nullrev:
1344 elif p == nullrev:
1345 c.append(self.node(r))
1345 c.append(self.node(r))
1346 return c
1346 return c
1347
1347
1348 def commonancestorsheads(self, a, b):
1348 def commonancestorsheads(self, a, b):
1349 """calculate all the heads of the common ancestors of nodes a and b"""
1349 """calculate all the heads of the common ancestors of nodes a and b"""
1350 a, b = self.rev(a), self.rev(b)
1350 a, b = self.rev(a), self.rev(b)
1351 ancs = self._commonancestorsheads(a, b)
1351 ancs = self._commonancestorsheads(a, b)
1352 return pycompat.maplist(self.node, ancs)
1352 return pycompat.maplist(self.node, ancs)
1353
1353
1354 def _commonancestorsheads(self, *revs):
1354 def _commonancestorsheads(self, *revs):
1355 """calculate all the heads of the common ancestors of revs"""
1355 """calculate all the heads of the common ancestors of revs"""
1356 try:
1356 try:
1357 ancs = self.index.commonancestorsheads(*revs)
1357 ancs = self.index.commonancestorsheads(*revs)
1358 except (AttributeError, OverflowError): # C implementation failed
1358 except (AttributeError, OverflowError): # C implementation failed
1359 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1359 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1360 return ancs
1360 return ancs
1361
1361
1362 def isancestor(self, a, b):
1362 def isancestor(self, a, b):
1363 """return True if node a is an ancestor of node b
1363 """return True if node a is an ancestor of node b
1364
1364
1365 A revision is considered an ancestor of itself."""
1365 A revision is considered an ancestor of itself."""
1366 a, b = self.rev(a), self.rev(b)
1366 a, b = self.rev(a), self.rev(b)
1367 return self.isancestorrev(a, b)
1367 return self.isancestorrev(a, b)
1368
1368
1369 def isancestorrev(self, a, b):
1369 def isancestorrev(self, a, b):
1370 """return True if revision a is an ancestor of revision b
1370 """return True if revision a is an ancestor of revision b
1371
1371
1372 A revision is considered an ancestor of itself.
1372 A revision is considered an ancestor of itself.
1373
1373
1374 The implementation of this is trivial but the use of
1374 The implementation of this is trivial but the use of
1375 reachableroots is not."""
1375 reachableroots is not."""
1376 if a == nullrev:
1376 if a == nullrev:
1377 return True
1377 return True
1378 elif a == b:
1378 elif a == b:
1379 return True
1379 return True
1380 elif a > b:
1380 elif a > b:
1381 return False
1381 return False
1382 return bool(self.reachableroots(a, [b], [a], includepath=False))
1382 return bool(self.reachableroots(a, [b], [a], includepath=False))
1383
1383
1384 def reachableroots(self, minroot, heads, roots, includepath=False):
1384 def reachableroots(self, minroot, heads, roots, includepath=False):
1385 """return (heads(::(<roots> and <roots>::<heads>)))
1385 """return (heads(::(<roots> and <roots>::<heads>)))
1386
1386
1387 If includepath is True, return (<roots>::<heads>)."""
1387 If includepath is True, return (<roots>::<heads>)."""
1388 try:
1388 try:
1389 return self.index.reachableroots2(
1389 return self.index.reachableroots2(
1390 minroot, heads, roots, includepath
1390 minroot, heads, roots, includepath
1391 )
1391 )
1392 except AttributeError:
1392 except AttributeError:
1393 return dagop._reachablerootspure(
1393 return dagop._reachablerootspure(
1394 self.parentrevs, minroot, roots, heads, includepath
1394 self.parentrevs, minroot, roots, heads, includepath
1395 )
1395 )
1396
1396
1397 def ancestor(self, a, b):
1397 def ancestor(self, a, b):
1398 """calculate the "best" common ancestor of nodes a and b"""
1398 """calculate the "best" common ancestor of nodes a and b"""
1399
1399
1400 a, b = self.rev(a), self.rev(b)
1400 a, b = self.rev(a), self.rev(b)
1401 try:
1401 try:
1402 ancs = self.index.ancestors(a, b)
1402 ancs = self.index.ancestors(a, b)
1403 except (AttributeError, OverflowError):
1403 except (AttributeError, OverflowError):
1404 ancs = ancestor.ancestors(self.parentrevs, a, b)
1404 ancs = ancestor.ancestors(self.parentrevs, a, b)
1405 if ancs:
1405 if ancs:
1406 # choose a consistent winner when there's a tie
1406 # choose a consistent winner when there's a tie
1407 return min(map(self.node, ancs))
1407 return min(map(self.node, ancs))
1408 return nullid
1408 return nullid
1409
1409
1410 def _match(self, id):
1410 def _match(self, id):
1411 if isinstance(id, int):
1411 if isinstance(id, int):
1412 # rev
1412 # rev
1413 return self.node(id)
1413 return self.node(id)
1414 if len(id) == 20:
1414 if len(id) == 20:
1415 # possibly a binary node
1415 # possibly a binary node
1416 # odds of a binary node being all hex in ASCII are 1 in 10**25
1416 # odds of a binary node being all hex in ASCII are 1 in 10**25
1417 try:
1417 try:
1418 node = id
1418 node = id
1419 self.rev(node) # quick search the index
1419 self.rev(node) # quick search the index
1420 return node
1420 return node
1421 except error.LookupError:
1421 except error.LookupError:
1422 pass # may be partial hex id
1422 pass # may be partial hex id
1423 try:
1423 try:
1424 # str(rev)
1424 # str(rev)
1425 rev = int(id)
1425 rev = int(id)
1426 if b"%d" % rev != id:
1426 if b"%d" % rev != id:
1427 raise ValueError
1427 raise ValueError
1428 if rev < 0:
1428 if rev < 0:
1429 rev = len(self) + rev
1429 rev = len(self) + rev
1430 if rev < 0 or rev >= len(self):
1430 if rev < 0 or rev >= len(self):
1431 raise ValueError
1431 raise ValueError
1432 return self.node(rev)
1432 return self.node(rev)
1433 except (ValueError, OverflowError):
1433 except (ValueError, OverflowError):
1434 pass
1434 pass
1435 if len(id) == 40:
1435 if len(id) == 40:
1436 try:
1436 try:
1437 # a full hex nodeid?
1437 # a full hex nodeid?
1438 node = bin(id)
1438 node = bin(id)
1439 self.rev(node)
1439 self.rev(node)
1440 return node
1440 return node
1441 except (TypeError, error.LookupError):
1441 except (TypeError, error.LookupError):
1442 pass
1442 pass
1443
1443
1444 def _partialmatch(self, id):
1444 def _partialmatch(self, id):
1445 # we don't care wdirfilenodeids as they should be always full hash
1445 # we don't care wdirfilenodeids as they should be always full hash
1446 maybewdir = wdirhex.startswith(id)
1446 maybewdir = wdirhex.startswith(id)
1447 try:
1447 try:
1448 partial = self.index.partialmatch(id)
1448 partial = self.index.partialmatch(id)
1449 if partial and self.hasnode(partial):
1449 if partial and self.hasnode(partial):
1450 if maybewdir:
1450 if maybewdir:
1451 # single 'ff...' match in radix tree, ambiguous with wdir
1451 # single 'ff...' match in radix tree, ambiguous with wdir
1452 raise error.RevlogError
1452 raise error.RevlogError
1453 return partial
1453 return partial
1454 if maybewdir:
1454 if maybewdir:
1455 # no 'ff...' match in radix tree, wdir identified
1455 # no 'ff...' match in radix tree, wdir identified
1456 raise error.WdirUnsupported
1456 raise error.WdirUnsupported
1457 return None
1457 return None
1458 except error.RevlogError:
1458 except error.RevlogError:
1459 # parsers.c radix tree lookup gave multiple matches
1459 # parsers.c radix tree lookup gave multiple matches
1460 # fast path: for unfiltered changelog, radix tree is accurate
1460 # fast path: for unfiltered changelog, radix tree is accurate
1461 if not getattr(self, 'filteredrevs', None):
1461 if not getattr(self, 'filteredrevs', None):
1462 raise error.AmbiguousPrefixLookupError(
1462 raise error.AmbiguousPrefixLookupError(
1463 id, self.indexfile, _(b'ambiguous identifier')
1463 id, self.indexfile, _(b'ambiguous identifier')
1464 )
1464 )
1465 # fall through to slow path that filters hidden revisions
1465 # fall through to slow path that filters hidden revisions
1466 except (AttributeError, ValueError):
1466 except (AttributeError, ValueError):
1467 # we are pure python, or key was too short to search radix tree
1467 # we are pure python, or key was too short to search radix tree
1468 pass
1468 pass
1469
1469
1470 if id in self._pcache:
1470 if id in self._pcache:
1471 return self._pcache[id]
1471 return self._pcache[id]
1472
1472
1473 if len(id) <= 40:
1473 if len(id) <= 40:
1474 try:
1474 try:
1475 # hex(node)[:...]
1475 # hex(node)[:...]
1476 l = len(id) // 2 # grab an even number of digits
1476 l = len(id) // 2 # grab an even number of digits
1477 prefix = bin(id[: l * 2])
1477 prefix = bin(id[: l * 2])
1478 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1478 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1479 nl = [
1479 nl = [
1480 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1480 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1481 ]
1481 ]
1482 if nullhex.startswith(id):
1482 if nullhex.startswith(id):
1483 nl.append(nullid)
1483 nl.append(nullid)
1484 if len(nl) > 0:
1484 if len(nl) > 0:
1485 if len(nl) == 1 and not maybewdir:
1485 if len(nl) == 1 and not maybewdir:
1486 self._pcache[id] = nl[0]
1486 self._pcache[id] = nl[0]
1487 return nl[0]
1487 return nl[0]
1488 raise error.AmbiguousPrefixLookupError(
1488 raise error.AmbiguousPrefixLookupError(
1489 id, self.indexfile, _(b'ambiguous identifier')
1489 id, self.indexfile, _(b'ambiguous identifier')
1490 )
1490 )
1491 if maybewdir:
1491 if maybewdir:
1492 raise error.WdirUnsupported
1492 raise error.WdirUnsupported
1493 return None
1493 return None
1494 except TypeError:
1494 except TypeError:
1495 pass
1495 pass
1496
1496
1497 def lookup(self, id):
1497 def lookup(self, id):
1498 """locate a node based on:
1498 """locate a node based on:
1499 - revision number or str(revision number)
1499 - revision number or str(revision number)
1500 - nodeid or subset of hex nodeid
1500 - nodeid or subset of hex nodeid
1501 """
1501 """
1502 n = self._match(id)
1502 n = self._match(id)
1503 if n is not None:
1503 if n is not None:
1504 return n
1504 return n
1505 n = self._partialmatch(id)
1505 n = self._partialmatch(id)
1506 if n:
1506 if n:
1507 return n
1507 return n
1508
1508
1509 raise error.LookupError(id, self.indexfile, _(b'no match found'))
1509 raise error.LookupError(id, self.indexfile, _(b'no match found'))
1510
1510
1511 def shortest(self, node, minlength=1):
1511 def shortest(self, node, minlength=1):
1512 """Find the shortest unambiguous prefix that matches node."""
1512 """Find the shortest unambiguous prefix that matches node."""
1513
1513
1514 def isvalid(prefix):
1514 def isvalid(prefix):
1515 try:
1515 try:
1516 matchednode = self._partialmatch(prefix)
1516 matchednode = self._partialmatch(prefix)
1517 except error.AmbiguousPrefixLookupError:
1517 except error.AmbiguousPrefixLookupError:
1518 return False
1518 return False
1519 except error.WdirUnsupported:
1519 except error.WdirUnsupported:
1520 # single 'ff...' match
1520 # single 'ff...' match
1521 return True
1521 return True
1522 if matchednode is None:
1522 if matchednode is None:
1523 raise error.LookupError(node, self.indexfile, _(b'no node'))
1523 raise error.LookupError(node, self.indexfile, _(b'no node'))
1524 return True
1524 return True
1525
1525
1526 def maybewdir(prefix):
1526 def maybewdir(prefix):
1527 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1527 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1528
1528
1529 hexnode = hex(node)
1529 hexnode = hex(node)
1530
1530
1531 def disambiguate(hexnode, minlength):
1531 def disambiguate(hexnode, minlength):
1532 """Disambiguate against wdirid."""
1532 """Disambiguate against wdirid."""
1533 for length in range(minlength, len(hexnode) + 1):
1533 for length in range(minlength, len(hexnode) + 1):
1534 prefix = hexnode[:length]
1534 prefix = hexnode[:length]
1535 if not maybewdir(prefix):
1535 if not maybewdir(prefix):
1536 return prefix
1536 return prefix
1537
1537
1538 if not getattr(self, 'filteredrevs', None):
1538 if not getattr(self, 'filteredrevs', None):
1539 try:
1539 try:
1540 length = max(self.index.shortest(node), minlength)
1540 length = max(self.index.shortest(node), minlength)
1541 return disambiguate(hexnode, length)
1541 return disambiguate(hexnode, length)
1542 except error.RevlogError:
1542 except error.RevlogError:
1543 if node != wdirid:
1543 if node != wdirid:
1544 raise error.LookupError(node, self.indexfile, _(b'no node'))
1544 raise error.LookupError(node, self.indexfile, _(b'no node'))
1545 except AttributeError:
1545 except AttributeError:
1546 # Fall through to pure code
1546 # Fall through to pure code
1547 pass
1547 pass
1548
1548
1549 if node == wdirid:
1549 if node == wdirid:
1550 for length in range(minlength, len(hexnode) + 1):
1550 for length in range(minlength, len(hexnode) + 1):
1551 prefix = hexnode[:length]
1551 prefix = hexnode[:length]
1552 if isvalid(prefix):
1552 if isvalid(prefix):
1553 return prefix
1553 return prefix
1554
1554
1555 for length in range(minlength, len(hexnode) + 1):
1555 for length in range(minlength, len(hexnode) + 1):
1556 prefix = hexnode[:length]
1556 prefix = hexnode[:length]
1557 if isvalid(prefix):
1557 if isvalid(prefix):
1558 return disambiguate(hexnode, length)
1558 return disambiguate(hexnode, length)
1559
1559
1560 def cmp(self, node, text):
1560 def cmp(self, node, text):
1561 """compare text with a given file revision
1561 """compare text with a given file revision
1562
1562
1563 returns True if text is different than what is stored.
1563 returns True if text is different than what is stored.
1564 """
1564 """
1565 p1, p2 = self.parents(node)
1565 p1, p2 = self.parents(node)
1566 return storageutil.hashrevisionsha1(text, p1, p2) != node
1566 return storageutil.hashrevisionsha1(text, p1, p2) != node
1567
1567
1568 def _cachesegment(self, offset, data):
1568 def _cachesegment(self, offset, data):
1569 """Add a segment to the revlog cache.
1569 """Add a segment to the revlog cache.
1570
1570
1571 Accepts an absolute offset and the data that is at that location.
1571 Accepts an absolute offset and the data that is at that location.
1572 """
1572 """
1573 o, d = self._chunkcache
1573 o, d = self._chunkcache
1574 # try to add to existing cache
1574 # try to add to existing cache
1575 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1575 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1576 self._chunkcache = o, d + data
1576 self._chunkcache = o, d + data
1577 else:
1577 else:
1578 self._chunkcache = offset, data
1578 self._chunkcache = offset, data
1579
1579
1580 def _readsegment(self, offset, length, df=None):
1580 def _readsegment(self, offset, length, df=None):
1581 """Load a segment of raw data from the revlog.
1581 """Load a segment of raw data from the revlog.
1582
1582
1583 Accepts an absolute offset, length to read, and an optional existing
1583 Accepts an absolute offset, length to read, and an optional existing
1584 file handle to read from.
1584 file handle to read from.
1585
1585
1586 If an existing file handle is passed, it will be seeked and the
1586 If an existing file handle is passed, it will be seeked and the
1587 original seek position will NOT be restored.
1587 original seek position will NOT be restored.
1588
1588
1589 Returns a str or buffer of raw byte data.
1589 Returns a str or buffer of raw byte data.
1590
1590
1591 Raises if the requested number of bytes could not be read.
1591 Raises if the requested number of bytes could not be read.
1592 """
1592 """
1593 # Cache data both forward and backward around the requested
1593 # Cache data both forward and backward around the requested
1594 # data, in a fixed size window. This helps speed up operations
1594 # data, in a fixed size window. This helps speed up operations
1595 # involving reading the revlog backwards.
1595 # involving reading the revlog backwards.
1596 cachesize = self._chunkcachesize
1596 cachesize = self._chunkcachesize
1597 realoffset = offset & ~(cachesize - 1)
1597 realoffset = offset & ~(cachesize - 1)
1598 reallength = (
1598 reallength = (
1599 (offset + length + cachesize) & ~(cachesize - 1)
1599 (offset + length + cachesize) & ~(cachesize - 1)
1600 ) - realoffset
1600 ) - realoffset
1601 with self._datareadfp(df) as df:
1601 with self._datareadfp(df) as df:
1602 df.seek(realoffset)
1602 df.seek(realoffset)
1603 d = df.read(reallength)
1603 d = df.read(reallength)
1604
1604
1605 self._cachesegment(realoffset, d)
1605 self._cachesegment(realoffset, d)
1606 if offset != realoffset or reallength != length:
1606 if offset != realoffset or reallength != length:
1607 startoffset = offset - realoffset
1607 startoffset = offset - realoffset
1608 if len(d) - startoffset < length:
1608 if len(d) - startoffset < length:
1609 raise error.RevlogError(
1609 raise error.RevlogError(
1610 _(
1610 _(
1611 b'partial read of revlog %s; expected %d bytes from '
1611 b'partial read of revlog %s; expected %d bytes from '
1612 b'offset %d, got %d'
1612 b'offset %d, got %d'
1613 )
1613 )
1614 % (
1614 % (
1615 self.indexfile if self._inline else self.datafile,
1615 self.indexfile if self._inline else self.datafile,
1616 length,
1616 length,
1617 realoffset,
1617 realoffset,
1618 len(d) - startoffset,
1618 len(d) - startoffset,
1619 )
1619 )
1620 )
1620 )
1621
1621
1622 return util.buffer(d, startoffset, length)
1622 return util.buffer(d, startoffset, length)
1623
1623
1624 if len(d) < length:
1624 if len(d) < length:
1625 raise error.RevlogError(
1625 raise error.RevlogError(
1626 _(
1626 _(
1627 b'partial read of revlog %s; expected %d bytes from offset '
1627 b'partial read of revlog %s; expected %d bytes from offset '
1628 b'%d, got %d'
1628 b'%d, got %d'
1629 )
1629 )
1630 % (
1630 % (
1631 self.indexfile if self._inline else self.datafile,
1631 self.indexfile if self._inline else self.datafile,
1632 length,
1632 length,
1633 offset,
1633 offset,
1634 len(d),
1634 len(d),
1635 )
1635 )
1636 )
1636 )
1637
1637
1638 return d
1638 return d
1639
1639
1640 def _getsegment(self, offset, length, df=None):
1640 def _getsegment(self, offset, length, df=None):
1641 """Obtain a segment of raw data from the revlog.
1641 """Obtain a segment of raw data from the revlog.
1642
1642
1643 Accepts an absolute offset, length of bytes to obtain, and an
1643 Accepts an absolute offset, length of bytes to obtain, and an
1644 optional file handle to the already-opened revlog. If the file
1644 optional file handle to the already-opened revlog. If the file
1645 handle is used, it's original seek position will not be preserved.
1645 handle is used, it's original seek position will not be preserved.
1646
1646
1647 Requests for data may be returned from a cache.
1647 Requests for data may be returned from a cache.
1648
1648
1649 Returns a str or a buffer instance of raw byte data.
1649 Returns a str or a buffer instance of raw byte data.
1650 """
1650 """
1651 o, d = self._chunkcache
1651 o, d = self._chunkcache
1652 l = len(d)
1652 l = len(d)
1653
1653
1654 # is it in the cache?
1654 # is it in the cache?
1655 cachestart = offset - o
1655 cachestart = offset - o
1656 cacheend = cachestart + length
1656 cacheend = cachestart + length
1657 if cachestart >= 0 and cacheend <= l:
1657 if cachestart >= 0 and cacheend <= l:
1658 if cachestart == 0 and cacheend == l:
1658 if cachestart == 0 and cacheend == l:
1659 return d # avoid a copy
1659 return d # avoid a copy
1660 return util.buffer(d, cachestart, cacheend - cachestart)
1660 return util.buffer(d, cachestart, cacheend - cachestart)
1661
1661
1662 return self._readsegment(offset, length, df=df)
1662 return self._readsegment(offset, length, df=df)
1663
1663
1664 def _getsegmentforrevs(self, startrev, endrev, df=None):
1664 def _getsegmentforrevs(self, startrev, endrev, df=None):
1665 """Obtain a segment of raw data corresponding to a range of revisions.
1665 """Obtain a segment of raw data corresponding to a range of revisions.
1666
1666
1667 Accepts the start and end revisions and an optional already-open
1667 Accepts the start and end revisions and an optional already-open
1668 file handle to be used for reading. If the file handle is read, its
1668 file handle to be used for reading. If the file handle is read, its
1669 seek position will not be preserved.
1669 seek position will not be preserved.
1670
1670
1671 Requests for data may be satisfied by a cache.
1671 Requests for data may be satisfied by a cache.
1672
1672
1673 Returns a 2-tuple of (offset, data) for the requested range of
1673 Returns a 2-tuple of (offset, data) for the requested range of
1674 revisions. Offset is the integer offset from the beginning of the
1674 revisions. Offset is the integer offset from the beginning of the
1675 revlog and data is a str or buffer of the raw byte data.
1675 revlog and data is a str or buffer of the raw byte data.
1676
1676
1677 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1677 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1678 to determine where each revision's data begins and ends.
1678 to determine where each revision's data begins and ends.
1679 """
1679 """
1680 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1680 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1681 # (functions are expensive).
1681 # (functions are expensive).
1682 index = self.index
1682 index = self.index
1683 istart = index[startrev]
1683 istart = index[startrev]
1684 start = int(istart[0] >> 16)
1684 start = int(istart[0] >> 16)
1685 if startrev == endrev:
1685 if startrev == endrev:
1686 end = start + istart[1]
1686 end = start + istart[1]
1687 else:
1687 else:
1688 iend = index[endrev]
1688 iend = index[endrev]
1689 end = int(iend[0] >> 16) + iend[1]
1689 end = int(iend[0] >> 16) + iend[1]
1690
1690
1691 if self._inline:
1691 if self._inline:
1692 start += (startrev + 1) * self._io.size
1692 start += (startrev + 1) * self._io.size
1693 end += (endrev + 1) * self._io.size
1693 end += (endrev + 1) * self._io.size
1694 length = end - start
1694 length = end - start
1695
1695
1696 return start, self._getsegment(start, length, df=df)
1696 return start, self._getsegment(start, length, df=df)
1697
1697
1698 def _chunk(self, rev, df=None):
1698 def _chunk(self, rev, df=None):
1699 """Obtain a single decompressed chunk for a revision.
1699 """Obtain a single decompressed chunk for a revision.
1700
1700
1701 Accepts an integer revision and an optional already-open file handle
1701 Accepts an integer revision and an optional already-open file handle
1702 to be used for reading. If used, the seek position of the file will not
1702 to be used for reading. If used, the seek position of the file will not
1703 be preserved.
1703 be preserved.
1704
1704
1705 Returns a str holding uncompressed data for the requested revision.
1705 Returns a str holding uncompressed data for the requested revision.
1706 """
1706 """
1707 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1707 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1708
1708
1709 def _chunks(self, revs, df=None, targetsize=None):
1709 def _chunks(self, revs, df=None, targetsize=None):
1710 """Obtain decompressed chunks for the specified revisions.
1710 """Obtain decompressed chunks for the specified revisions.
1711
1711
1712 Accepts an iterable of numeric revisions that are assumed to be in
1712 Accepts an iterable of numeric revisions that are assumed to be in
1713 ascending order. Also accepts an optional already-open file handle
1713 ascending order. Also accepts an optional already-open file handle
1714 to be used for reading. If used, the seek position of the file will
1714 to be used for reading. If used, the seek position of the file will
1715 not be preserved.
1715 not be preserved.
1716
1716
1717 This function is similar to calling ``self._chunk()`` multiple times,
1717 This function is similar to calling ``self._chunk()`` multiple times,
1718 but is faster.
1718 but is faster.
1719
1719
1720 Returns a list with decompressed data for each requested revision.
1720 Returns a list with decompressed data for each requested revision.
1721 """
1721 """
1722 if not revs:
1722 if not revs:
1723 return []
1723 return []
1724 start = self.start
1724 start = self.start
1725 length = self.length
1725 length = self.length
1726 inline = self._inline
1726 inline = self._inline
1727 iosize = self._io.size
1727 iosize = self._io.size
1728 buffer = util.buffer
1728 buffer = util.buffer
1729
1729
1730 l = []
1730 l = []
1731 ladd = l.append
1731 ladd = l.append
1732
1732
1733 if not self._withsparseread:
1733 if not self._withsparseread:
1734 slicedchunks = (revs,)
1734 slicedchunks = (revs,)
1735 else:
1735 else:
1736 slicedchunks = deltautil.slicechunk(
1736 slicedchunks = deltautil.slicechunk(
1737 self, revs, targetsize=targetsize
1737 self, revs, targetsize=targetsize
1738 )
1738 )
1739
1739
1740 for revschunk in slicedchunks:
1740 for revschunk in slicedchunks:
1741 firstrev = revschunk[0]
1741 firstrev = revschunk[0]
1742 # Skip trailing revisions with empty diff
1742 # Skip trailing revisions with empty diff
1743 for lastrev in revschunk[::-1]:
1743 for lastrev in revschunk[::-1]:
1744 if length(lastrev) != 0:
1744 if length(lastrev) != 0:
1745 break
1745 break
1746
1746
1747 try:
1747 try:
1748 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1748 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1749 except OverflowError:
1749 except OverflowError:
1750 # issue4215 - we can't cache a run of chunks greater than
1750 # issue4215 - we can't cache a run of chunks greater than
1751 # 2G on Windows
1751 # 2G on Windows
1752 return [self._chunk(rev, df=df) for rev in revschunk]
1752 return [self._chunk(rev, df=df) for rev in revschunk]
1753
1753
1754 decomp = self.decompress
1754 decomp = self.decompress
1755 for rev in revschunk:
1755 for rev in revschunk:
1756 chunkstart = start(rev)
1756 chunkstart = start(rev)
1757 if inline:
1757 if inline:
1758 chunkstart += (rev + 1) * iosize
1758 chunkstart += (rev + 1) * iosize
1759 chunklength = length(rev)
1759 chunklength = length(rev)
1760 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1760 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1761
1761
1762 return l
1762 return l
1763
1763
1764 def _chunkclear(self):
1764 def _chunkclear(self):
1765 """Clear the raw chunk cache."""
1765 """Clear the raw chunk cache."""
1766 self._chunkcache = (0, b'')
1766 self._chunkcache = (0, b'')
1767
1767
1768 def deltaparent(self, rev):
1768 def deltaparent(self, rev):
1769 """return deltaparent of the given revision"""
1769 """return deltaparent of the given revision"""
1770 base = self.index[rev][3]
1770 base = self.index[rev][3]
1771 if base == rev:
1771 if base == rev:
1772 return nullrev
1772 return nullrev
1773 elif self._generaldelta:
1773 elif self._generaldelta:
1774 return base
1774 return base
1775 else:
1775 else:
1776 return rev - 1
1776 return rev - 1
1777
1777
1778 def issnapshot(self, rev):
1778 def issnapshot(self, rev):
1779 """tells whether rev is a snapshot"""
1779 """tells whether rev is a snapshot"""
1780 if not self._sparserevlog:
1780 if not self._sparserevlog:
1781 return self.deltaparent(rev) == nullrev
1781 return self.deltaparent(rev) == nullrev
1782 elif util.safehasattr(self.index, b'issnapshot'):
1782 elif util.safehasattr(self.index, b'issnapshot'):
1783 # directly assign the method to cache the testing and access
1783 # directly assign the method to cache the testing and access
1784 self.issnapshot = self.index.issnapshot
1784 self.issnapshot = self.index.issnapshot
1785 return self.issnapshot(rev)
1785 return self.issnapshot(rev)
1786 if rev == nullrev:
1786 if rev == nullrev:
1787 return True
1787 return True
1788 entry = self.index[rev]
1788 entry = self.index[rev]
1789 base = entry[3]
1789 base = entry[3]
1790 if base == rev:
1790 if base == rev:
1791 return True
1791 return True
1792 if base == nullrev:
1792 if base == nullrev:
1793 return True
1793 return True
1794 p1 = entry[5]
1794 p1 = entry[5]
1795 p2 = entry[6]
1795 p2 = entry[6]
1796 if base == p1 or base == p2:
1796 if base == p1 or base == p2:
1797 return False
1797 return False
1798 return self.issnapshot(base)
1798 return self.issnapshot(base)
1799
1799
1800 def snapshotdepth(self, rev):
1800 def snapshotdepth(self, rev):
1801 """number of snapshot in the chain before this one"""
1801 """number of snapshot in the chain before this one"""
1802 if not self.issnapshot(rev):
1802 if not self.issnapshot(rev):
1803 raise error.ProgrammingError(b'revision %d not a snapshot')
1803 raise error.ProgrammingError(b'revision %d not a snapshot')
1804 return len(self._deltachain(rev)[0]) - 1
1804 return len(self._deltachain(rev)[0]) - 1
1805
1805
1806 def revdiff(self, rev1, rev2):
1806 def revdiff(self, rev1, rev2):
1807 """return or calculate a delta between two revisions
1807 """return or calculate a delta between two revisions
1808
1808
1809 The delta calculated is in binary form and is intended to be written to
1809 The delta calculated is in binary form and is intended to be written to
1810 revlog data directly. So this function needs raw revision data.
1810 revlog data directly. So this function needs raw revision data.
1811 """
1811 """
1812 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1812 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1813 return bytes(self._chunk(rev2))
1813 return bytes(self._chunk(rev2))
1814
1814
1815 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1815 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1816
1816
1817 def _processflags(self, text, flags, operation, raw=False):
1817 def _processflags(self, text, flags, operation, raw=False):
1818 """deprecated entry point to access flag processors"""
1818 """deprecated entry point to access flag processors"""
1819 msg = b'_processflag(...) use the specialized variant'
1819 msg = b'_processflag(...) use the specialized variant'
1820 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1820 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1821 if raw:
1821 if raw:
1822 return text, flagutil.processflagsraw(self, text, flags)
1822 return text, flagutil.processflagsraw(self, text, flags)
1823 elif operation == b'read':
1823 elif operation == b'read':
1824 return flagutil.processflagsread(self, text, flags)
1824 return flagutil.processflagsread(self, text, flags)
1825 else: # write operation
1825 else: # write operation
1826 return flagutil.processflagswrite(self, text, flags, None)
1826 return flagutil.processflagswrite(self, text, flags, None)
1827
1827
1828 def revision(self, nodeorrev, _df=None, raw=False):
1828 def revision(self, nodeorrev, _df=None, raw=False):
1829 """return an uncompressed revision of a given node or revision
1829 """return an uncompressed revision of a given node or revision
1830 number.
1830 number.
1831
1831
1832 _df - an existing file handle to read from. (internal-only)
1832 _df - an existing file handle to read from. (internal-only)
1833 raw - an optional argument specifying if the revision data is to be
1833 raw - an optional argument specifying if the revision data is to be
1834 treated as raw data when applying flag transforms. 'raw' should be set
1834 treated as raw data when applying flag transforms. 'raw' should be set
1835 to True when generating changegroups or in debug commands.
1835 to True when generating changegroups or in debug commands.
1836 """
1836 """
1837 if raw:
1837 if raw:
1838 msg = (
1838 msg = (
1839 b'revlog.revision(..., raw=True) is deprecated, '
1839 b'revlog.revision(..., raw=True) is deprecated, '
1840 b'use revlog.rawdata(...)'
1840 b'use revlog.rawdata(...)'
1841 )
1841 )
1842 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1842 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1843 return self._revisiondata(nodeorrev, _df, raw=raw)[0]
1843 return self._revisiondata(nodeorrev, _df, raw=raw)[0]
1844
1844
1845 def sidedata(self, nodeorrev, _df=None):
1845 def sidedata(self, nodeorrev, _df=None):
1846 """a map of extra data related to the changeset but not part of the hash
1846 """a map of extra data related to the changeset but not part of the hash
1847
1847
1848 This function currently return a dictionary. However, more advanced
1848 This function currently return a dictionary. However, more advanced
1849 mapping object will likely be used in the future for a more
1849 mapping object will likely be used in the future for a more
1850 efficient/lazy code.
1850 efficient/lazy code.
1851 """
1851 """
1852 return self._revisiondata(nodeorrev, _df)[1]
1852 return self._revisiondata(nodeorrev, _df)[1]
1853
1853
1854 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1854 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1855 # deal with <nodeorrev> argument type
1855 # deal with <nodeorrev> argument type
1856 if isinstance(nodeorrev, int):
1856 if isinstance(nodeorrev, int):
1857 rev = nodeorrev
1857 rev = nodeorrev
1858 node = self.node(rev)
1858 node = self.node(rev)
1859 else:
1859 else:
1860 node = nodeorrev
1860 node = nodeorrev
1861 rev = None
1861 rev = None
1862
1862
1863 # fast path the special `nullid` rev
1863 # fast path the special `nullid` rev
1864 if node == nullid:
1864 if node == nullid:
1865 return b"", {}
1865 return b"", {}
1866
1866
1867 # ``rawtext`` is the text as stored inside the revlog. Might be the
1867 # ``rawtext`` is the text as stored inside the revlog. Might be the
1868 # revision or might need to be processed to retrieve the revision.
1868 # revision or might need to be processed to retrieve the revision.
1869 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1869 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1870
1870
1871 if raw and validated:
1871 if raw and validated:
1872 # if we don't want to process the raw text and that raw
1872 # if we don't want to process the raw text and that raw
1873 # text is cached, we can exit early.
1873 # text is cached, we can exit early.
1874 return rawtext, {}
1874 return rawtext, {}
1875 if rev is None:
1875 if rev is None:
1876 rev = self.rev(node)
1876 rev = self.rev(node)
1877 # the revlog's flag for this revision
1877 # the revlog's flag for this revision
1878 # (usually alter its state or content)
1878 # (usually alter its state or content)
1879 flags = self.flags(rev)
1879 flags = self.flags(rev)
1880
1880
1881 if validated and flags == REVIDX_DEFAULT_FLAGS:
1881 if validated and flags == REVIDX_DEFAULT_FLAGS:
1882 # no extra flags set, no flag processor runs, text = rawtext
1882 # no extra flags set, no flag processor runs, text = rawtext
1883 return rawtext, {}
1883 return rawtext, {}
1884
1884
1885 sidedata = {}
1885 sidedata = {}
1886 if raw:
1886 if raw:
1887 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1887 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1888 text = rawtext
1888 text = rawtext
1889 else:
1889 else:
1890 try:
1890 try:
1891 r = flagutil.processflagsread(self, rawtext, flags)
1891 r = flagutil.processflagsread(self, rawtext, flags)
1892 except error.SidedataHashError as exc:
1892 except error.SidedataHashError as exc:
1893 msg = _(b"integrity check failed on %s:%s sidedata key %d")
1893 msg = _(b"integrity check failed on %s:%s sidedata key %d")
1894 msg %= (self.indexfile, pycompat.bytestr(rev), exc.sidedatakey)
1894 msg %= (self.indexfile, pycompat.bytestr(rev), exc.sidedatakey)
1895 raise error.RevlogError(msg)
1895 raise error.RevlogError(msg)
1896 text, validatehash, sidedata = r
1896 text, validatehash, sidedata = r
1897 if validatehash:
1897 if validatehash:
1898 self.checkhash(text, node, rev=rev)
1898 self.checkhash(text, node, rev=rev)
1899 if not validated:
1899 if not validated:
1900 self._revisioncache = (node, rev, rawtext)
1900 self._revisioncache = (node, rev, rawtext)
1901
1901
1902 return text, sidedata
1902 return text, sidedata
1903
1903
1904 def _rawtext(self, node, rev, _df=None):
1904 def _rawtext(self, node, rev, _df=None):
1905 """return the possibly unvalidated rawtext for a revision
1905 """return the possibly unvalidated rawtext for a revision
1906
1906
1907 returns (rev, rawtext, validated)
1907 returns (rev, rawtext, validated)
1908 """
1908 """
1909
1909
1910 # revision in the cache (could be useful to apply delta)
1910 # revision in the cache (could be useful to apply delta)
1911 cachedrev = None
1911 cachedrev = None
1912 # An intermediate text to apply deltas to
1912 # An intermediate text to apply deltas to
1913 basetext = None
1913 basetext = None
1914
1914
1915 # Check if we have the entry in cache
1915 # Check if we have the entry in cache
1916 # The cache entry looks like (node, rev, rawtext)
1916 # The cache entry looks like (node, rev, rawtext)
1917 if self._revisioncache:
1917 if self._revisioncache:
1918 if self._revisioncache[0] == node:
1918 if self._revisioncache[0] == node:
1919 return (rev, self._revisioncache[2], True)
1919 return (rev, self._revisioncache[2], True)
1920 cachedrev = self._revisioncache[1]
1920 cachedrev = self._revisioncache[1]
1921
1921
1922 if rev is None:
1922 if rev is None:
1923 rev = self.rev(node)
1923 rev = self.rev(node)
1924
1924
1925 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1925 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1926 if stopped:
1926 if stopped:
1927 basetext = self._revisioncache[2]
1927 basetext = self._revisioncache[2]
1928
1928
1929 # drop cache to save memory, the caller is expected to
1929 # drop cache to save memory, the caller is expected to
1930 # update self._revisioncache after validating the text
1930 # update self._revisioncache after validating the text
1931 self._revisioncache = None
1931 self._revisioncache = None
1932
1932
1933 targetsize = None
1933 targetsize = None
1934 rawsize = self.index[rev][2]
1934 rawsize = self.index[rev][2]
1935 if 0 <= rawsize:
1935 if 0 <= rawsize:
1936 targetsize = 4 * rawsize
1936 targetsize = 4 * rawsize
1937
1937
1938 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1938 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1939 if basetext is None:
1939 if basetext is None:
1940 basetext = bytes(bins[0])
1940 basetext = bytes(bins[0])
1941 bins = bins[1:]
1941 bins = bins[1:]
1942
1942
1943 rawtext = mdiff.patches(basetext, bins)
1943 rawtext = mdiff.patches(basetext, bins)
1944 del basetext # let us have a chance to free memory early
1944 del basetext # let us have a chance to free memory early
1945 return (rev, rawtext, False)
1945 return (rev, rawtext, False)
1946
1946
1947 def rawdata(self, nodeorrev, _df=None):
1947 def rawdata(self, nodeorrev, _df=None):
1948 """return an uncompressed raw data of a given node or revision number.
1948 """return an uncompressed raw data of a given node or revision number.
1949
1949
1950 _df - an existing file handle to read from. (internal-only)
1950 _df - an existing file handle to read from. (internal-only)
1951 """
1951 """
1952 return self._revisiondata(nodeorrev, _df, raw=True)[0]
1952 return self._revisiondata(nodeorrev, _df, raw=True)[0]
1953
1953
1954 def hash(self, text, p1, p2):
1954 def hash(self, text, p1, p2):
1955 """Compute a node hash.
1955 """Compute a node hash.
1956
1956
1957 Available as a function so that subclasses can replace the hash
1957 Available as a function so that subclasses can replace the hash
1958 as needed.
1958 as needed.
1959 """
1959 """
1960 return storageutil.hashrevisionsha1(text, p1, p2)
1960 return storageutil.hashrevisionsha1(text, p1, p2)
1961
1961
1962 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1962 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1963 """Check node hash integrity.
1963 """Check node hash integrity.
1964
1964
1965 Available as a function so that subclasses can extend hash mismatch
1965 Available as a function so that subclasses can extend hash mismatch
1966 behaviors as needed.
1966 behaviors as needed.
1967 """
1967 """
1968 try:
1968 try:
1969 if p1 is None and p2 is None:
1969 if p1 is None and p2 is None:
1970 p1, p2 = self.parents(node)
1970 p1, p2 = self.parents(node)
1971 if node != self.hash(text, p1, p2):
1971 if node != self.hash(text, p1, p2):
1972 # Clear the revision cache on hash failure. The revision cache
1972 # Clear the revision cache on hash failure. The revision cache
1973 # only stores the raw revision and clearing the cache does have
1973 # only stores the raw revision and clearing the cache does have
1974 # the side-effect that we won't have a cache hit when the raw
1974 # the side-effect that we won't have a cache hit when the raw
1975 # revision data is accessed. But this case should be rare and
1975 # revision data is accessed. But this case should be rare and
1976 # it is extra work to teach the cache about the hash
1976 # it is extra work to teach the cache about the hash
1977 # verification state.
1977 # verification state.
1978 if self._revisioncache and self._revisioncache[0] == node:
1978 if self._revisioncache and self._revisioncache[0] == node:
1979 self._revisioncache = None
1979 self._revisioncache = None
1980
1980
1981 revornode = rev
1981 revornode = rev
1982 if revornode is None:
1982 if revornode is None:
1983 revornode = templatefilters.short(hex(node))
1983 revornode = templatefilters.short(hex(node))
1984 raise error.RevlogError(
1984 raise error.RevlogError(
1985 _(b"integrity check failed on %s:%s")
1985 _(b"integrity check failed on %s:%s")
1986 % (self.indexfile, pycompat.bytestr(revornode))
1986 % (self.indexfile, pycompat.bytestr(revornode))
1987 )
1987 )
1988 except error.RevlogError:
1988 except error.RevlogError:
1989 if self._censorable and storageutil.iscensoredtext(text):
1989 if self._censorable and storageutil.iscensoredtext(text):
1990 raise error.CensoredNodeError(self.indexfile, node, text)
1990 raise error.CensoredNodeError(self.indexfile, node, text)
1991 raise
1991 raise
1992
1992
1993 def _enforceinlinesize(self, tr, fp=None):
1993 def _enforceinlinesize(self, tr, fp=None):
1994 """Check if the revlog is too big for inline and convert if so.
1994 """Check if the revlog is too big for inline and convert if so.
1995
1995
1996 This should be called after revisions are added to the revlog. If the
1996 This should be called after revisions are added to the revlog. If the
1997 revlog has grown too large to be an inline revlog, it will convert it
1997 revlog has grown too large to be an inline revlog, it will convert it
1998 to use multiple index and data files.
1998 to use multiple index and data files.
1999 """
1999 """
2000 tiprev = len(self) - 1
2000 tiprev = len(self) - 1
2001 if (
2001 if (
2002 not self._inline
2002 not self._inline
2003 or (self.start(tiprev) + self.length(tiprev)) < _maxinline
2003 or (self.start(tiprev) + self.length(tiprev)) < _maxinline
2004 ):
2004 ):
2005 return
2005 return
2006
2006
2007 troffset = tr.findoffset(self.indexfile)
2007 troffset = tr.findoffset(self.indexfile)
2008 if troffset is None:
2008 if troffset is None:
2009 raise error.RevlogError(
2009 raise error.RevlogError(
2010 _(b"%s not found in the transaction") % self.indexfile
2010 _(b"%s not found in the transaction") % self.indexfile
2011 )
2011 )
2012 trindex = 0
2012 trindex = 0
2013 tr.add(self.datafile, 0)
2013 tr.add(self.datafile, 0)
2014
2014
2015 if fp:
2015 if fp:
2016 fp.flush()
2016 fp.flush()
2017 fp.close()
2017 fp.close()
2018 # We can't use the cached file handle after close(). So prevent
2018 # We can't use the cached file handle after close(). So prevent
2019 # its usage.
2019 # its usage.
2020 self._writinghandles = None
2020 self._writinghandles = None
2021
2021
2022 with self._indexfp(b'r') as ifh, self._datafp(b'w') as dfh:
2022 with self._indexfp(b'r') as ifh, self._datafp(b'w') as dfh:
2023 for r in self:
2023 for r in self:
2024 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
2024 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
2025 if troffset <= self.start(r):
2025 if troffset <= self.start(r):
2026 trindex = r
2026 trindex = r
2027
2027
2028 with self._indexfp(b'w') as fp:
2028 with self._indexfp(b'w') as fp:
2029 self.version &= ~FLAG_INLINE_DATA
2029 self.version &= ~FLAG_INLINE_DATA
2030 self._inline = False
2030 self._inline = False
2031 io = self._io
2031 io = self._io
2032 for i in self:
2032 for i in self:
2033 e = io.packentry(self.index[i], self.node, self.version, i)
2033 e = io.packentry(self.index[i], self.node, self.version, i)
2034 fp.write(e)
2034 fp.write(e)
2035
2035
2036 # the temp file replace the real index when we exit the context
2036 # the temp file replace the real index when we exit the context
2037 # manager
2037 # manager
2038
2038
2039 tr.replace(self.indexfile, trindex * self._io.size)
2039 tr.replace(self.indexfile, trindex * self._io.size)
2040 nodemaputil.setup_persistent_nodemap(tr, self)
2040 nodemaputil.setup_persistent_nodemap(tr, self)
2041 self._chunkclear()
2041 self._chunkclear()
2042
2042
2043 def _nodeduplicatecallback(self, transaction, node):
2043 def _nodeduplicatecallback(self, transaction, node):
2044 """called when trying to add a node already stored."""
2044 """called when trying to add a node already stored."""
2045
2045
2046 def addrevision(
2046 def addrevision(
2047 self,
2047 self,
2048 text,
2048 text,
2049 transaction,
2049 transaction,
2050 link,
2050 link,
2051 p1,
2051 p1,
2052 p2,
2052 p2,
2053 cachedelta=None,
2053 cachedelta=None,
2054 node=None,
2054 node=None,
2055 flags=REVIDX_DEFAULT_FLAGS,
2055 flags=REVIDX_DEFAULT_FLAGS,
2056 deltacomputer=None,
2056 deltacomputer=None,
2057 sidedata=None,
2057 sidedata=None,
2058 ):
2058 ):
2059 """add a revision to the log
2059 """add a revision to the log
2060
2060
2061 text - the revision data to add
2061 text - the revision data to add
2062 transaction - the transaction object used for rollback
2062 transaction - the transaction object used for rollback
2063 link - the linkrev data to add
2063 link - the linkrev data to add
2064 p1, p2 - the parent nodeids of the revision
2064 p1, p2 - the parent nodeids of the revision
2065 cachedelta - an optional precomputed delta
2065 cachedelta - an optional precomputed delta
2066 node - nodeid of revision; typically node is not specified, and it is
2066 node - nodeid of revision; typically node is not specified, and it is
2067 computed by default as hash(text, p1, p2), however subclasses might
2067 computed by default as hash(text, p1, p2), however subclasses might
2068 use different hashing method (and override checkhash() in such case)
2068 use different hashing method (and override checkhash() in such case)
2069 flags - the known flags to set on the revision
2069 flags - the known flags to set on the revision
2070 deltacomputer - an optional deltacomputer instance shared between
2070 deltacomputer - an optional deltacomputer instance shared between
2071 multiple calls
2071 multiple calls
2072 """
2072 """
2073 if link == nullrev:
2073 if link == nullrev:
2074 raise error.RevlogError(
2074 raise error.RevlogError(
2075 _(b"attempted to add linkrev -1 to %s") % self.indexfile
2075 _(b"attempted to add linkrev -1 to %s") % self.indexfile
2076 )
2076 )
2077
2077
2078 if sidedata is None:
2078 if sidedata is None:
2079 sidedata = {}
2079 sidedata = {}
2080 flags = flags & ~REVIDX_SIDEDATA
2080 flags = flags & ~REVIDX_SIDEDATA
2081 elif not self.hassidedata:
2081 elif not self.hassidedata:
2082 raise error.ProgrammingError(
2082 raise error.ProgrammingError(
2083 _(b"trying to add sidedata to a revlog who don't support them")
2083 _(b"trying to add sidedata to a revlog who don't support them")
2084 )
2084 )
2085 else:
2085 else:
2086 flags |= REVIDX_SIDEDATA
2086 flags |= REVIDX_SIDEDATA
2087
2087
2088 if flags:
2088 if flags:
2089 node = node or self.hash(text, p1, p2)
2089 node = node or self.hash(text, p1, p2)
2090
2090
2091 rawtext, validatehash = flagutil.processflagswrite(
2091 rawtext, validatehash = flagutil.processflagswrite(
2092 self, text, flags, sidedata=sidedata
2092 self, text, flags, sidedata=sidedata
2093 )
2093 )
2094
2094
2095 # If the flag processor modifies the revision data, ignore any provided
2095 # If the flag processor modifies the revision data, ignore any provided
2096 # cachedelta.
2096 # cachedelta.
2097 if rawtext != text:
2097 if rawtext != text:
2098 cachedelta = None
2098 cachedelta = None
2099
2099
2100 if len(rawtext) > _maxentrysize:
2100 if len(rawtext) > _maxentrysize:
2101 raise error.RevlogError(
2101 raise error.RevlogError(
2102 _(
2102 _(
2103 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2103 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2104 )
2104 )
2105 % (self.indexfile, len(rawtext))
2105 % (self.indexfile, len(rawtext))
2106 )
2106 )
2107
2107
2108 node = node or self.hash(rawtext, p1, p2)
2108 node = node or self.hash(rawtext, p1, p2)
2109 rev = self.index.get_rev(node)
2109 rev = self.index.get_rev(node)
2110 if rev is not None:
2110 if rev is not None:
2111 return rev
2111 return rev
2112
2112
2113 if validatehash:
2113 if validatehash:
2114 self.checkhash(rawtext, node, p1=p1, p2=p2)
2114 self.checkhash(rawtext, node, p1=p1, p2=p2)
2115
2115
2116 return self.addrawrevision(
2116 return self.addrawrevision(
2117 rawtext,
2117 rawtext,
2118 transaction,
2118 transaction,
2119 link,
2119 link,
2120 p1,
2120 p1,
2121 p2,
2121 p2,
2122 node,
2122 node,
2123 flags,
2123 flags,
2124 cachedelta=cachedelta,
2124 cachedelta=cachedelta,
2125 deltacomputer=deltacomputer,
2125 deltacomputer=deltacomputer,
2126 )
2126 )
2127
2127
2128 def addrawrevision(
2128 def addrawrevision(
2129 self,
2129 self,
2130 rawtext,
2130 rawtext,
2131 transaction,
2131 transaction,
2132 link,
2132 link,
2133 p1,
2133 p1,
2134 p2,
2134 p2,
2135 node,
2135 node,
2136 flags,
2136 flags,
2137 cachedelta=None,
2137 cachedelta=None,
2138 deltacomputer=None,
2138 deltacomputer=None,
2139 ):
2139 ):
2140 """add a raw revision with known flags, node and parents
2140 """add a raw revision with known flags, node and parents
2141 useful when reusing a revision not stored in this revlog (ex: received
2141 useful when reusing a revision not stored in this revlog (ex: received
2142 over wire, or read from an external bundle).
2142 over wire, or read from an external bundle).
2143 """
2143 """
2144 dfh = None
2144 dfh = None
2145 if not self._inline:
2145 if not self._inline:
2146 dfh = self._datafp(b"a+")
2146 dfh = self._datafp(b"a+")
2147 ifh = self._indexfp(b"a+")
2147 ifh = self._indexfp(b"a+")
2148 try:
2148 try:
2149 return self._addrevision(
2149 return self._addrevision(
2150 node,
2150 node,
2151 rawtext,
2151 rawtext,
2152 transaction,
2152 transaction,
2153 link,
2153 link,
2154 p1,
2154 p1,
2155 p2,
2155 p2,
2156 flags,
2156 flags,
2157 cachedelta,
2157 cachedelta,
2158 ifh,
2158 ifh,
2159 dfh,
2159 dfh,
2160 deltacomputer=deltacomputer,
2160 deltacomputer=deltacomputer,
2161 )
2161 )
2162 finally:
2162 finally:
2163 if dfh:
2163 if dfh:
2164 dfh.close()
2164 dfh.close()
2165 ifh.close()
2165 ifh.close()
2166
2166
2167 def compress(self, data):
2167 def compress(self, data):
2168 """Generate a possibly-compressed representation of data."""
2168 """Generate a possibly-compressed representation of data."""
2169 if not data:
2169 if not data:
2170 return b'', data
2170 return b'', data
2171
2171
2172 compressed = self._compressor.compress(data)
2172 compressed = self._compressor.compress(data)
2173
2173
2174 if compressed:
2174 if compressed:
2175 # The revlog compressor added the header in the returned data.
2175 # The revlog compressor added the header in the returned data.
2176 return b'', compressed
2176 return b'', compressed
2177
2177
2178 if data[0:1] == b'\0':
2178 if data[0:1] == b'\0':
2179 return b'', data
2179 return b'', data
2180 return b'u', data
2180 return b'u', data
2181
2181
2182 def decompress(self, data):
2182 def decompress(self, data):
2183 """Decompress a revlog chunk.
2183 """Decompress a revlog chunk.
2184
2184
2185 The chunk is expected to begin with a header identifying the
2185 The chunk is expected to begin with a header identifying the
2186 format type so it can be routed to an appropriate decompressor.
2186 format type so it can be routed to an appropriate decompressor.
2187 """
2187 """
2188 if not data:
2188 if not data:
2189 return data
2189 return data
2190
2190
2191 # Revlogs are read much more frequently than they are written and many
2191 # Revlogs are read much more frequently than they are written and many
2192 # chunks only take microseconds to decompress, so performance is
2192 # chunks only take microseconds to decompress, so performance is
2193 # important here.
2193 # important here.
2194 #
2194 #
2195 # We can make a few assumptions about revlogs:
2195 # We can make a few assumptions about revlogs:
2196 #
2196 #
2197 # 1) the majority of chunks will be compressed (as opposed to inline
2197 # 1) the majority of chunks will be compressed (as opposed to inline
2198 # raw data).
2198 # raw data).
2199 # 2) decompressing *any* data will likely by at least 10x slower than
2199 # 2) decompressing *any* data will likely by at least 10x slower than
2200 # returning raw inline data.
2200 # returning raw inline data.
2201 # 3) we want to prioritize common and officially supported compression
2201 # 3) we want to prioritize common and officially supported compression
2202 # engines
2202 # engines
2203 #
2203 #
2204 # It follows that we want to optimize for "decompress compressed data
2204 # It follows that we want to optimize for "decompress compressed data
2205 # when encoded with common and officially supported compression engines"
2205 # when encoded with common and officially supported compression engines"
2206 # case over "raw data" and "data encoded by less common or non-official
2206 # case over "raw data" and "data encoded by less common or non-official
2207 # compression engines." That is why we have the inline lookup first
2207 # compression engines." That is why we have the inline lookup first
2208 # followed by the compengines lookup.
2208 # followed by the compengines lookup.
2209 #
2209 #
2210 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2210 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2211 # compressed chunks. And this matters for changelog and manifest reads.
2211 # compressed chunks. And this matters for changelog and manifest reads.
2212 t = data[0:1]
2212 t = data[0:1]
2213
2213
2214 if t == b'x':
2214 if t == b'x':
2215 try:
2215 try:
2216 return _zlibdecompress(data)
2216 return _zlibdecompress(data)
2217 except zlib.error as e:
2217 except zlib.error as e:
2218 raise error.RevlogError(
2218 raise error.RevlogError(
2219 _(b'revlog decompress error: %s')
2219 _(b'revlog decompress error: %s')
2220 % stringutil.forcebytestr(e)
2220 % stringutil.forcebytestr(e)
2221 )
2221 )
2222 # '\0' is more common than 'u' so it goes first.
2222 # '\0' is more common than 'u' so it goes first.
2223 elif t == b'\0':
2223 elif t == b'\0':
2224 return data
2224 return data
2225 elif t == b'u':
2225 elif t == b'u':
2226 return util.buffer(data, 1)
2226 return util.buffer(data, 1)
2227
2227
2228 try:
2228 try:
2229 compressor = self._decompressors[t]
2229 compressor = self._decompressors[t]
2230 except KeyError:
2230 except KeyError:
2231 try:
2231 try:
2232 engine = util.compengines.forrevlogheader(t)
2232 engine = util.compengines.forrevlogheader(t)
2233 compressor = engine.revlogcompressor(self._compengineopts)
2233 compressor = engine.revlogcompressor(self._compengineopts)
2234 self._decompressors[t] = compressor
2234 self._decompressors[t] = compressor
2235 except KeyError:
2235 except KeyError:
2236 raise error.RevlogError(_(b'unknown compression type %r') % t)
2236 raise error.RevlogError(_(b'unknown compression type %r') % t)
2237
2237
2238 return compressor.decompress(data)
2238 return compressor.decompress(data)
2239
2239
2240 def _addrevision(
2240 def _addrevision(
2241 self,
2241 self,
2242 node,
2242 node,
2243 rawtext,
2243 rawtext,
2244 transaction,
2244 transaction,
2245 link,
2245 link,
2246 p1,
2246 p1,
2247 p2,
2247 p2,
2248 flags,
2248 flags,
2249 cachedelta,
2249 cachedelta,
2250 ifh,
2250 ifh,
2251 dfh,
2251 dfh,
2252 alwayscache=False,
2252 alwayscache=False,
2253 deltacomputer=None,
2253 deltacomputer=None,
2254 ):
2254 ):
2255 """internal function to add revisions to the log
2255 """internal function to add revisions to the log
2256
2256
2257 see addrevision for argument descriptions.
2257 see addrevision for argument descriptions.
2258
2258
2259 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2259 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2260
2260
2261 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2261 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2262 be used.
2262 be used.
2263
2263
2264 invariants:
2264 invariants:
2265 - rawtext is optional (can be None); if not set, cachedelta must be set.
2265 - rawtext is optional (can be None); if not set, cachedelta must be set.
2266 if both are set, they must correspond to each other.
2266 if both are set, they must correspond to each other.
2267 """
2267 """
2268 if node == nullid:
2268 if node == nullid:
2269 raise error.RevlogError(
2269 raise error.RevlogError(
2270 _(b"%s: attempt to add null revision") % self.indexfile
2270 _(b"%s: attempt to add null revision") % self.indexfile
2271 )
2271 )
2272 if node == wdirid or node in wdirfilenodeids:
2272 if node == wdirid or node in wdirfilenodeids:
2273 raise error.RevlogError(
2273 raise error.RevlogError(
2274 _(b"%s: attempt to add wdir revision") % self.indexfile
2274 _(b"%s: attempt to add wdir revision") % self.indexfile
2275 )
2275 )
2276
2276
2277 if self._inline:
2277 if self._inline:
2278 fh = ifh
2278 fh = ifh
2279 else:
2279 else:
2280 fh = dfh
2280 fh = dfh
2281
2281
2282 btext = [rawtext]
2282 btext = [rawtext]
2283
2283
2284 curr = len(self)
2284 curr = len(self)
2285 prev = curr - 1
2285 prev = curr - 1
2286 offset = self.end(prev)
2286 offset = self.end(prev)
2287 p1r, p2r = self.rev(p1), self.rev(p2)
2287 p1r, p2r = self.rev(p1), self.rev(p2)
2288
2288
2289 # full versions are inserted when the needed deltas
2289 # full versions are inserted when the needed deltas
2290 # become comparable to the uncompressed text
2290 # become comparable to the uncompressed text
2291 if rawtext is None:
2291 if rawtext is None:
2292 # need rawtext size, before changed by flag processors, which is
2292 # need rawtext size, before changed by flag processors, which is
2293 # the non-raw size. use revlog explicitly to avoid filelog's extra
2293 # the non-raw size. use revlog explicitly to avoid filelog's extra
2294 # logic that might remove metadata size.
2294 # logic that might remove metadata size.
2295 textlen = mdiff.patchedsize(
2295 textlen = mdiff.patchedsize(
2296 revlog.size(self, cachedelta[0]), cachedelta[1]
2296 revlog.size(self, cachedelta[0]), cachedelta[1]
2297 )
2297 )
2298 else:
2298 else:
2299 textlen = len(rawtext)
2299 textlen = len(rawtext)
2300
2300
2301 if deltacomputer is None:
2301 if deltacomputer is None:
2302 deltacomputer = deltautil.deltacomputer(self)
2302 deltacomputer = deltautil.deltacomputer(self)
2303
2303
2304 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2304 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2305
2305
2306 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2306 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2307
2307
2308 e = (
2308 e = (
2309 offset_type(offset, flags),
2309 offset_type(offset, flags),
2310 deltainfo.deltalen,
2310 deltainfo.deltalen,
2311 textlen,
2311 textlen,
2312 deltainfo.base,
2312 deltainfo.base,
2313 link,
2313 link,
2314 p1r,
2314 p1r,
2315 p2r,
2315 p2r,
2316 node,
2316 node,
2317 )
2317 )
2318 self.index.append(e)
2318 self.index.append(e)
2319
2319
2320 entry = self._io.packentry(e, self.node, self.version, curr)
2320 entry = self._io.packentry(e, self.node, self.version, curr)
2321 self._writeentry(
2321 self._writeentry(
2322 transaction, ifh, dfh, entry, deltainfo.data, link, offset
2322 transaction, ifh, dfh, entry, deltainfo.data, link, offset
2323 )
2323 )
2324
2324
2325 rawtext = btext[0]
2325 rawtext = btext[0]
2326
2326
2327 if alwayscache and rawtext is None:
2327 if alwayscache and rawtext is None:
2328 rawtext = deltacomputer.buildtext(revinfo, fh)
2328 rawtext = deltacomputer.buildtext(revinfo, fh)
2329
2329
2330 if type(rawtext) == bytes: # only accept immutable objects
2330 if type(rawtext) == bytes: # only accept immutable objects
2331 self._revisioncache = (node, curr, rawtext)
2331 self._revisioncache = (node, curr, rawtext)
2332 self._chainbasecache[curr] = deltainfo.chainbase
2332 self._chainbasecache[curr] = deltainfo.chainbase
2333 return curr
2333 return curr
2334
2334
2335 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2335 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2336 # Files opened in a+ mode have inconsistent behavior on various
2336 # Files opened in a+ mode have inconsistent behavior on various
2337 # platforms. Windows requires that a file positioning call be made
2337 # platforms. Windows requires that a file positioning call be made
2338 # when the file handle transitions between reads and writes. See
2338 # when the file handle transitions between reads and writes. See
2339 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2339 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2340 # platforms, Python or the platform itself can be buggy. Some versions
2340 # platforms, Python or the platform itself can be buggy. Some versions
2341 # of Solaris have been observed to not append at the end of the file
2341 # of Solaris have been observed to not append at the end of the file
2342 # if the file was seeked to before the end. See issue4943 for more.
2342 # if the file was seeked to before the end. See issue4943 for more.
2343 #
2343 #
2344 # We work around this issue by inserting a seek() before writing.
2344 # We work around this issue by inserting a seek() before writing.
2345 # Note: This is likely not necessary on Python 3. However, because
2345 # Note: This is likely not necessary on Python 3. However, because
2346 # the file handle is reused for reads and may be seeked there, we need
2346 # the file handle is reused for reads and may be seeked there, we need
2347 # to be careful before changing this.
2347 # to be careful before changing this.
2348 ifh.seek(0, os.SEEK_END)
2348 ifh.seek(0, os.SEEK_END)
2349 if dfh:
2349 if dfh:
2350 dfh.seek(0, os.SEEK_END)
2350 dfh.seek(0, os.SEEK_END)
2351
2351
2352 curr = len(self) - 1
2352 curr = len(self) - 1
2353 if not self._inline:
2353 if not self._inline:
2354 transaction.add(self.datafile, offset)
2354 transaction.add(self.datafile, offset)
2355 transaction.add(self.indexfile, curr * len(entry))
2355 transaction.add(self.indexfile, curr * len(entry))
2356 if data[0]:
2356 if data[0]:
2357 dfh.write(data[0])
2357 dfh.write(data[0])
2358 dfh.write(data[1])
2358 dfh.write(data[1])
2359 ifh.write(entry)
2359 ifh.write(entry)
2360 else:
2360 else:
2361 offset += curr * self._io.size
2361 offset += curr * self._io.size
2362 transaction.add(self.indexfile, offset)
2362 transaction.add(self.indexfile, offset)
2363 ifh.write(entry)
2363 ifh.write(entry)
2364 ifh.write(data[0])
2364 ifh.write(data[0])
2365 ifh.write(data[1])
2365 ifh.write(data[1])
2366 self._enforceinlinesize(transaction, ifh)
2366 self._enforceinlinesize(transaction, ifh)
2367 nodemaputil.setup_persistent_nodemap(transaction, self)
2367 nodemaputil.setup_persistent_nodemap(transaction, self)
2368
2368
2369 def addgroup(
2369 def addgroup(
2370 self,
2370 self,
2371 deltas,
2371 deltas,
2372 linkmapper,
2372 linkmapper,
2373 transaction,
2373 transaction,
2374 alwayscache=False,
2374 alwayscache=False,
2375 addrevisioncb=None,
2375 addrevisioncb=None,
2376 duplicaterevisioncb=None,
2376 duplicaterevisioncb=None,
2377 ):
2377 ):
2378 """
2378 """
2379 add a delta group
2379 add a delta group
2380
2380
2381 given a set of deltas, add them to the revision log. the
2381 given a set of deltas, add them to the revision log. the
2382 first delta is against its parent, which should be in our
2382 first delta is against its parent, which should be in our
2383 log, the rest are against the previous delta.
2383 log, the rest are against the previous delta.
2384
2384
2385 If ``addrevisioncb`` is defined, it will be called with arguments of
2385 If ``addrevisioncb`` is defined, it will be called with arguments of
2386 this revlog and the node that was added.
2386 this revlog and the node that was added.
2387 """
2387 """
2388
2388
2389 if self._writinghandles:
2389 if self._writinghandles:
2390 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2390 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2391
2391
2392 r = len(self)
2392 r = len(self)
2393 end = 0
2393 end = 0
2394 if r:
2394 if r:
2395 end = self.end(r - 1)
2395 end = self.end(r - 1)
2396 ifh = self._indexfp(b"a+")
2396 ifh = self._indexfp(b"a+")
2397 isize = r * self._io.size
2397 isize = r * self._io.size
2398 if self._inline:
2398 if self._inline:
2399 transaction.add(self.indexfile, end + isize)
2399 transaction.add(self.indexfile, end + isize)
2400 dfh = None
2400 dfh = None
2401 else:
2401 else:
2402 transaction.add(self.indexfile, isize)
2402 transaction.add(self.indexfile, isize)
2403 transaction.add(self.datafile, end)
2403 transaction.add(self.datafile, end)
2404 dfh = self._datafp(b"a+")
2404 dfh = self._datafp(b"a+")
2405
2405
2406 def flush():
2406 def flush():
2407 if dfh:
2407 if dfh:
2408 dfh.flush()
2408 dfh.flush()
2409 ifh.flush()
2409 ifh.flush()
2410
2410
2411 self._writinghandles = (ifh, dfh)
2411 self._writinghandles = (ifh, dfh)
2412 empty = True
2412 empty = True
2413
2413
2414 try:
2414 try:
2415 deltacomputer = deltautil.deltacomputer(self)
2415 deltacomputer = deltautil.deltacomputer(self)
2416 # loop through our set of deltas
2416 # loop through our set of deltas
2417 for data in deltas:
2417 for data in deltas:
2418 node, p1, p2, linknode, deltabase, delta, flags = data
2418 node, p1, p2, linknode, deltabase, delta, flags = data
2419 link = linkmapper(linknode)
2419 link = linkmapper(linknode)
2420 flags = flags or REVIDX_DEFAULT_FLAGS
2420 flags = flags or REVIDX_DEFAULT_FLAGS
2421
2421
2422 if self.index.has_node(node):
2422 rev = self.index.get_rev(node)
2423 if rev is not None:
2423 # this can happen if two branches make the same change
2424 # this can happen if two branches make the same change
2424 self._nodeduplicatecallback(transaction, node)
2425 self._nodeduplicatecallback(transaction, rev)
2425 if duplicaterevisioncb:
2426 if duplicaterevisioncb:
2426 duplicaterevisioncb(self, node)
2427 duplicaterevisioncb(self, rev)
2427 empty = False
2428 empty = False
2428 continue
2429 continue
2429
2430
2430 for p in (p1, p2):
2431 for p in (p1, p2):
2431 if not self.index.has_node(p):
2432 if not self.index.has_node(p):
2432 raise error.LookupError(
2433 raise error.LookupError(
2433 p, self.indexfile, _(b'unknown parent')
2434 p, self.indexfile, _(b'unknown parent')
2434 )
2435 )
2435
2436
2436 if not self.index.has_node(deltabase):
2437 if not self.index.has_node(deltabase):
2437 raise error.LookupError(
2438 raise error.LookupError(
2438 deltabase, self.indexfile, _(b'unknown delta base')
2439 deltabase, self.indexfile, _(b'unknown delta base')
2439 )
2440 )
2440
2441
2441 baserev = self.rev(deltabase)
2442 baserev = self.rev(deltabase)
2442
2443
2443 if baserev != nullrev and self.iscensored(baserev):
2444 if baserev != nullrev and self.iscensored(baserev):
2444 # if base is censored, delta must be full replacement in a
2445 # if base is censored, delta must be full replacement in a
2445 # single patch operation
2446 # single patch operation
2446 hlen = struct.calcsize(b">lll")
2447 hlen = struct.calcsize(b">lll")
2447 oldlen = self.rawsize(baserev)
2448 oldlen = self.rawsize(baserev)
2448 newlen = len(delta) - hlen
2449 newlen = len(delta) - hlen
2449 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2450 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2450 raise error.CensoredBaseError(
2451 raise error.CensoredBaseError(
2451 self.indexfile, self.node(baserev)
2452 self.indexfile, self.node(baserev)
2452 )
2453 )
2453
2454
2454 if not flags and self._peek_iscensored(baserev, delta, flush):
2455 if not flags and self._peek_iscensored(baserev, delta, flush):
2455 flags |= REVIDX_ISCENSORED
2456 flags |= REVIDX_ISCENSORED
2456
2457
2457 # We assume consumers of addrevisioncb will want to retrieve
2458 # We assume consumers of addrevisioncb will want to retrieve
2458 # the added revision, which will require a call to
2459 # the added revision, which will require a call to
2459 # revision(). revision() will fast path if there is a cache
2460 # revision(). revision() will fast path if there is a cache
2460 # hit. So, we tell _addrevision() to always cache in this case.
2461 # hit. So, we tell _addrevision() to always cache in this case.
2461 # We're only using addgroup() in the context of changegroup
2462 # We're only using addgroup() in the context of changegroup
2462 # generation so the revision data can always be handled as raw
2463 # generation so the revision data can always be handled as raw
2463 # by the flagprocessor.
2464 # by the flagprocessor.
2464 self._addrevision(
2465 rev = self._addrevision(
2465 node,
2466 node,
2466 None,
2467 None,
2467 transaction,
2468 transaction,
2468 link,
2469 link,
2469 p1,
2470 p1,
2470 p2,
2471 p2,
2471 flags,
2472 flags,
2472 (baserev, delta),
2473 (baserev, delta),
2473 ifh,
2474 ifh,
2474 dfh,
2475 dfh,
2475 alwayscache=alwayscache,
2476 alwayscache=alwayscache,
2476 deltacomputer=deltacomputer,
2477 deltacomputer=deltacomputer,
2477 )
2478 )
2478
2479
2479 if addrevisioncb:
2480 if addrevisioncb:
2480 addrevisioncb(self, node)
2481 addrevisioncb(self, rev)
2481 empty = False
2482 empty = False
2482
2483
2483 if not dfh and not self._inline:
2484 if not dfh and not self._inline:
2484 # addrevision switched from inline to conventional
2485 # addrevision switched from inline to conventional
2485 # reopen the index
2486 # reopen the index
2486 ifh.close()
2487 ifh.close()
2487 dfh = self._datafp(b"a+")
2488 dfh = self._datafp(b"a+")
2488 ifh = self._indexfp(b"a+")
2489 ifh = self._indexfp(b"a+")
2489 self._writinghandles = (ifh, dfh)
2490 self._writinghandles = (ifh, dfh)
2490 finally:
2491 finally:
2491 self._writinghandles = None
2492 self._writinghandles = None
2492
2493
2493 if dfh:
2494 if dfh:
2494 dfh.close()
2495 dfh.close()
2495 ifh.close()
2496 ifh.close()
2496 return not empty
2497 return not empty
2497
2498
2498 def iscensored(self, rev):
2499 def iscensored(self, rev):
2499 """Check if a file revision is censored."""
2500 """Check if a file revision is censored."""
2500 if not self._censorable:
2501 if not self._censorable:
2501 return False
2502 return False
2502
2503
2503 return self.flags(rev) & REVIDX_ISCENSORED
2504 return self.flags(rev) & REVIDX_ISCENSORED
2504
2505
2505 def _peek_iscensored(self, baserev, delta, flush):
2506 def _peek_iscensored(self, baserev, delta, flush):
2506 """Quickly check if a delta produces a censored revision."""
2507 """Quickly check if a delta produces a censored revision."""
2507 if not self._censorable:
2508 if not self._censorable:
2508 return False
2509 return False
2509
2510
2510 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2511 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2511
2512
2512 def getstrippoint(self, minlink):
2513 def getstrippoint(self, minlink):
2513 """find the minimum rev that must be stripped to strip the linkrev
2514 """find the minimum rev that must be stripped to strip the linkrev
2514
2515
2515 Returns a tuple containing the minimum rev and a set of all revs that
2516 Returns a tuple containing the minimum rev and a set of all revs that
2516 have linkrevs that will be broken by this strip.
2517 have linkrevs that will be broken by this strip.
2517 """
2518 """
2518 return storageutil.resolvestripinfo(
2519 return storageutil.resolvestripinfo(
2519 minlink,
2520 minlink,
2520 len(self) - 1,
2521 len(self) - 1,
2521 self.headrevs(),
2522 self.headrevs(),
2522 self.linkrev,
2523 self.linkrev,
2523 self.parentrevs,
2524 self.parentrevs,
2524 )
2525 )
2525
2526
2526 def strip(self, minlink, transaction):
2527 def strip(self, minlink, transaction):
2527 """truncate the revlog on the first revision with a linkrev >= minlink
2528 """truncate the revlog on the first revision with a linkrev >= minlink
2528
2529
2529 This function is called when we're stripping revision minlink and
2530 This function is called when we're stripping revision minlink and
2530 its descendants from the repository.
2531 its descendants from the repository.
2531
2532
2532 We have to remove all revisions with linkrev >= minlink, because
2533 We have to remove all revisions with linkrev >= minlink, because
2533 the equivalent changelog revisions will be renumbered after the
2534 the equivalent changelog revisions will be renumbered after the
2534 strip.
2535 strip.
2535
2536
2536 So we truncate the revlog on the first of these revisions, and
2537 So we truncate the revlog on the first of these revisions, and
2537 trust that the caller has saved the revisions that shouldn't be
2538 trust that the caller has saved the revisions that shouldn't be
2538 removed and that it'll re-add them after this truncation.
2539 removed and that it'll re-add them after this truncation.
2539 """
2540 """
2540 if len(self) == 0:
2541 if len(self) == 0:
2541 return
2542 return
2542
2543
2543 rev, _ = self.getstrippoint(minlink)
2544 rev, _ = self.getstrippoint(minlink)
2544 if rev == len(self):
2545 if rev == len(self):
2545 return
2546 return
2546
2547
2547 # first truncate the files on disk
2548 # first truncate the files on disk
2548 end = self.start(rev)
2549 end = self.start(rev)
2549 if not self._inline:
2550 if not self._inline:
2550 transaction.add(self.datafile, end)
2551 transaction.add(self.datafile, end)
2551 end = rev * self._io.size
2552 end = rev * self._io.size
2552 else:
2553 else:
2553 end += rev * self._io.size
2554 end += rev * self._io.size
2554
2555
2555 transaction.add(self.indexfile, end)
2556 transaction.add(self.indexfile, end)
2556
2557
2557 # then reset internal state in memory to forget those revisions
2558 # then reset internal state in memory to forget those revisions
2558 self._revisioncache = None
2559 self._revisioncache = None
2559 self._chaininfocache = util.lrucachedict(500)
2560 self._chaininfocache = util.lrucachedict(500)
2560 self._chunkclear()
2561 self._chunkclear()
2561
2562
2562 del self.index[rev:-1]
2563 del self.index[rev:-1]
2563
2564
2564 def checksize(self):
2565 def checksize(self):
2565 """Check size of index and data files
2566 """Check size of index and data files
2566
2567
2567 return a (dd, di) tuple.
2568 return a (dd, di) tuple.
2568 - dd: extra bytes for the "data" file
2569 - dd: extra bytes for the "data" file
2569 - di: extra bytes for the "index" file
2570 - di: extra bytes for the "index" file
2570
2571
2571 A healthy revlog will return (0, 0).
2572 A healthy revlog will return (0, 0).
2572 """
2573 """
2573 expected = 0
2574 expected = 0
2574 if len(self):
2575 if len(self):
2575 expected = max(0, self.end(len(self) - 1))
2576 expected = max(0, self.end(len(self) - 1))
2576
2577
2577 try:
2578 try:
2578 with self._datafp() as f:
2579 with self._datafp() as f:
2579 f.seek(0, io.SEEK_END)
2580 f.seek(0, io.SEEK_END)
2580 actual = f.tell()
2581 actual = f.tell()
2581 dd = actual - expected
2582 dd = actual - expected
2582 except IOError as inst:
2583 except IOError as inst:
2583 if inst.errno != errno.ENOENT:
2584 if inst.errno != errno.ENOENT:
2584 raise
2585 raise
2585 dd = 0
2586 dd = 0
2586
2587
2587 try:
2588 try:
2588 f = self.opener(self.indexfile)
2589 f = self.opener(self.indexfile)
2589 f.seek(0, io.SEEK_END)
2590 f.seek(0, io.SEEK_END)
2590 actual = f.tell()
2591 actual = f.tell()
2591 f.close()
2592 f.close()
2592 s = self._io.size
2593 s = self._io.size
2593 i = max(0, actual // s)
2594 i = max(0, actual // s)
2594 di = actual - (i * s)
2595 di = actual - (i * s)
2595 if self._inline:
2596 if self._inline:
2596 databytes = 0
2597 databytes = 0
2597 for r in self:
2598 for r in self:
2598 databytes += max(0, self.length(r))
2599 databytes += max(0, self.length(r))
2599 dd = 0
2600 dd = 0
2600 di = actual - len(self) * s - databytes
2601 di = actual - len(self) * s - databytes
2601 except IOError as inst:
2602 except IOError as inst:
2602 if inst.errno != errno.ENOENT:
2603 if inst.errno != errno.ENOENT:
2603 raise
2604 raise
2604 di = 0
2605 di = 0
2605
2606
2606 return (dd, di)
2607 return (dd, di)
2607
2608
2608 def files(self):
2609 def files(self):
2609 res = [self.indexfile]
2610 res = [self.indexfile]
2610 if not self._inline:
2611 if not self._inline:
2611 res.append(self.datafile)
2612 res.append(self.datafile)
2612 return res
2613 return res
2613
2614
2614 def emitrevisions(
2615 def emitrevisions(
2615 self,
2616 self,
2616 nodes,
2617 nodes,
2617 nodesorder=None,
2618 nodesorder=None,
2618 revisiondata=False,
2619 revisiondata=False,
2619 assumehaveparentrevisions=False,
2620 assumehaveparentrevisions=False,
2620 deltamode=repository.CG_DELTAMODE_STD,
2621 deltamode=repository.CG_DELTAMODE_STD,
2621 ):
2622 ):
2622 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2623 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2623 raise error.ProgrammingError(
2624 raise error.ProgrammingError(
2624 b'unhandled value for nodesorder: %s' % nodesorder
2625 b'unhandled value for nodesorder: %s' % nodesorder
2625 )
2626 )
2626
2627
2627 if nodesorder is None and not self._generaldelta:
2628 if nodesorder is None and not self._generaldelta:
2628 nodesorder = b'storage'
2629 nodesorder = b'storage'
2629
2630
2630 if (
2631 if (
2631 not self._storedeltachains
2632 not self._storedeltachains
2632 and deltamode != repository.CG_DELTAMODE_PREV
2633 and deltamode != repository.CG_DELTAMODE_PREV
2633 ):
2634 ):
2634 deltamode = repository.CG_DELTAMODE_FULL
2635 deltamode = repository.CG_DELTAMODE_FULL
2635
2636
2636 return storageutil.emitrevisions(
2637 return storageutil.emitrevisions(
2637 self,
2638 self,
2638 nodes,
2639 nodes,
2639 nodesorder,
2640 nodesorder,
2640 revlogrevisiondelta,
2641 revlogrevisiondelta,
2641 deltaparentfn=self.deltaparent,
2642 deltaparentfn=self.deltaparent,
2642 candeltafn=self.candelta,
2643 candeltafn=self.candelta,
2643 rawsizefn=self.rawsize,
2644 rawsizefn=self.rawsize,
2644 revdifffn=self.revdiff,
2645 revdifffn=self.revdiff,
2645 flagsfn=self.flags,
2646 flagsfn=self.flags,
2646 deltamode=deltamode,
2647 deltamode=deltamode,
2647 revisiondata=revisiondata,
2648 revisiondata=revisiondata,
2648 assumehaveparentrevisions=assumehaveparentrevisions,
2649 assumehaveparentrevisions=assumehaveparentrevisions,
2649 )
2650 )
2650
2651
2651 DELTAREUSEALWAYS = b'always'
2652 DELTAREUSEALWAYS = b'always'
2652 DELTAREUSESAMEREVS = b'samerevs'
2653 DELTAREUSESAMEREVS = b'samerevs'
2653 DELTAREUSENEVER = b'never'
2654 DELTAREUSENEVER = b'never'
2654
2655
2655 DELTAREUSEFULLADD = b'fulladd'
2656 DELTAREUSEFULLADD = b'fulladd'
2656
2657
2657 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2658 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2658
2659
2659 def clone(
2660 def clone(
2660 self,
2661 self,
2661 tr,
2662 tr,
2662 destrevlog,
2663 destrevlog,
2663 addrevisioncb=None,
2664 addrevisioncb=None,
2664 deltareuse=DELTAREUSESAMEREVS,
2665 deltareuse=DELTAREUSESAMEREVS,
2665 forcedeltabothparents=None,
2666 forcedeltabothparents=None,
2666 sidedatacompanion=None,
2667 sidedatacompanion=None,
2667 ):
2668 ):
2668 """Copy this revlog to another, possibly with format changes.
2669 """Copy this revlog to another, possibly with format changes.
2669
2670
2670 The destination revlog will contain the same revisions and nodes.
2671 The destination revlog will contain the same revisions and nodes.
2671 However, it may not be bit-for-bit identical due to e.g. delta encoding
2672 However, it may not be bit-for-bit identical due to e.g. delta encoding
2672 differences.
2673 differences.
2673
2674
2674 The ``deltareuse`` argument control how deltas from the existing revlog
2675 The ``deltareuse`` argument control how deltas from the existing revlog
2675 are preserved in the destination revlog. The argument can have the
2676 are preserved in the destination revlog. The argument can have the
2676 following values:
2677 following values:
2677
2678
2678 DELTAREUSEALWAYS
2679 DELTAREUSEALWAYS
2679 Deltas will always be reused (if possible), even if the destination
2680 Deltas will always be reused (if possible), even if the destination
2680 revlog would not select the same revisions for the delta. This is the
2681 revlog would not select the same revisions for the delta. This is the
2681 fastest mode of operation.
2682 fastest mode of operation.
2682 DELTAREUSESAMEREVS
2683 DELTAREUSESAMEREVS
2683 Deltas will be reused if the destination revlog would pick the same
2684 Deltas will be reused if the destination revlog would pick the same
2684 revisions for the delta. This mode strikes a balance between speed
2685 revisions for the delta. This mode strikes a balance between speed
2685 and optimization.
2686 and optimization.
2686 DELTAREUSENEVER
2687 DELTAREUSENEVER
2687 Deltas will never be reused. This is the slowest mode of execution.
2688 Deltas will never be reused. This is the slowest mode of execution.
2688 This mode can be used to recompute deltas (e.g. if the diff/delta
2689 This mode can be used to recompute deltas (e.g. if the diff/delta
2689 algorithm changes).
2690 algorithm changes).
2690 DELTAREUSEFULLADD
2691 DELTAREUSEFULLADD
2691 Revision will be re-added as if their were new content. This is
2692 Revision will be re-added as if their were new content. This is
2692 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2693 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2693 eg: large file detection and handling.
2694 eg: large file detection and handling.
2694
2695
2695 Delta computation can be slow, so the choice of delta reuse policy can
2696 Delta computation can be slow, so the choice of delta reuse policy can
2696 significantly affect run time.
2697 significantly affect run time.
2697
2698
2698 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2699 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2699 two extremes. Deltas will be reused if they are appropriate. But if the
2700 two extremes. Deltas will be reused if they are appropriate. But if the
2700 delta could choose a better revision, it will do so. This means if you
2701 delta could choose a better revision, it will do so. This means if you
2701 are converting a non-generaldelta revlog to a generaldelta revlog,
2702 are converting a non-generaldelta revlog to a generaldelta revlog,
2702 deltas will be recomputed if the delta's parent isn't a parent of the
2703 deltas will be recomputed if the delta's parent isn't a parent of the
2703 revision.
2704 revision.
2704
2705
2705 In addition to the delta policy, the ``forcedeltabothparents``
2706 In addition to the delta policy, the ``forcedeltabothparents``
2706 argument controls whether to force compute deltas against both parents
2707 argument controls whether to force compute deltas against both parents
2707 for merges. By default, the current default is used.
2708 for merges. By default, the current default is used.
2708
2709
2709 If not None, the `sidedatacompanion` is callable that accept two
2710 If not None, the `sidedatacompanion` is callable that accept two
2710 arguments:
2711 arguments:
2711
2712
2712 (srcrevlog, rev)
2713 (srcrevlog, rev)
2713
2714
2714 and return a quintet that control changes to sidedata content from the
2715 and return a quintet that control changes to sidedata content from the
2715 old revision to the new clone result:
2716 old revision to the new clone result:
2716
2717
2717 (dropall, filterout, update, new_flags, dropped_flags)
2718 (dropall, filterout, update, new_flags, dropped_flags)
2718
2719
2719 * if `dropall` is True, all sidedata should be dropped
2720 * if `dropall` is True, all sidedata should be dropped
2720 * `filterout` is a set of sidedata keys that should be dropped
2721 * `filterout` is a set of sidedata keys that should be dropped
2721 * `update` is a mapping of additionnal/new key -> value
2722 * `update` is a mapping of additionnal/new key -> value
2722 * new_flags is a bitfields of new flags that the revision should get
2723 * new_flags is a bitfields of new flags that the revision should get
2723 * dropped_flags is a bitfields of new flags that the revision shoudl not longer have
2724 * dropped_flags is a bitfields of new flags that the revision shoudl not longer have
2724 """
2725 """
2725 if deltareuse not in self.DELTAREUSEALL:
2726 if deltareuse not in self.DELTAREUSEALL:
2726 raise ValueError(
2727 raise ValueError(
2727 _(b'value for deltareuse invalid: %s') % deltareuse
2728 _(b'value for deltareuse invalid: %s') % deltareuse
2728 )
2729 )
2729
2730
2730 if len(destrevlog):
2731 if len(destrevlog):
2731 raise ValueError(_(b'destination revlog is not empty'))
2732 raise ValueError(_(b'destination revlog is not empty'))
2732
2733
2733 if getattr(self, 'filteredrevs', None):
2734 if getattr(self, 'filteredrevs', None):
2734 raise ValueError(_(b'source revlog has filtered revisions'))
2735 raise ValueError(_(b'source revlog has filtered revisions'))
2735 if getattr(destrevlog, 'filteredrevs', None):
2736 if getattr(destrevlog, 'filteredrevs', None):
2736 raise ValueError(_(b'destination revlog has filtered revisions'))
2737 raise ValueError(_(b'destination revlog has filtered revisions'))
2737
2738
2738 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2739 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2739 # if possible.
2740 # if possible.
2740 oldlazydelta = destrevlog._lazydelta
2741 oldlazydelta = destrevlog._lazydelta
2741 oldlazydeltabase = destrevlog._lazydeltabase
2742 oldlazydeltabase = destrevlog._lazydeltabase
2742 oldamd = destrevlog._deltabothparents
2743 oldamd = destrevlog._deltabothparents
2743
2744
2744 try:
2745 try:
2745 if deltareuse == self.DELTAREUSEALWAYS:
2746 if deltareuse == self.DELTAREUSEALWAYS:
2746 destrevlog._lazydeltabase = True
2747 destrevlog._lazydeltabase = True
2747 destrevlog._lazydelta = True
2748 destrevlog._lazydelta = True
2748 elif deltareuse == self.DELTAREUSESAMEREVS:
2749 elif deltareuse == self.DELTAREUSESAMEREVS:
2749 destrevlog._lazydeltabase = False
2750 destrevlog._lazydeltabase = False
2750 destrevlog._lazydelta = True
2751 destrevlog._lazydelta = True
2751 elif deltareuse == self.DELTAREUSENEVER:
2752 elif deltareuse == self.DELTAREUSENEVER:
2752 destrevlog._lazydeltabase = False
2753 destrevlog._lazydeltabase = False
2753 destrevlog._lazydelta = False
2754 destrevlog._lazydelta = False
2754
2755
2755 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2756 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2756
2757
2757 self._clone(
2758 self._clone(
2758 tr,
2759 tr,
2759 destrevlog,
2760 destrevlog,
2760 addrevisioncb,
2761 addrevisioncb,
2761 deltareuse,
2762 deltareuse,
2762 forcedeltabothparents,
2763 forcedeltabothparents,
2763 sidedatacompanion,
2764 sidedatacompanion,
2764 )
2765 )
2765
2766
2766 finally:
2767 finally:
2767 destrevlog._lazydelta = oldlazydelta
2768 destrevlog._lazydelta = oldlazydelta
2768 destrevlog._lazydeltabase = oldlazydeltabase
2769 destrevlog._lazydeltabase = oldlazydeltabase
2769 destrevlog._deltabothparents = oldamd
2770 destrevlog._deltabothparents = oldamd
2770
2771
2771 def _clone(
2772 def _clone(
2772 self,
2773 self,
2773 tr,
2774 tr,
2774 destrevlog,
2775 destrevlog,
2775 addrevisioncb,
2776 addrevisioncb,
2776 deltareuse,
2777 deltareuse,
2777 forcedeltabothparents,
2778 forcedeltabothparents,
2778 sidedatacompanion,
2779 sidedatacompanion,
2779 ):
2780 ):
2780 """perform the core duty of `revlog.clone` after parameter processing"""
2781 """perform the core duty of `revlog.clone` after parameter processing"""
2781 deltacomputer = deltautil.deltacomputer(destrevlog)
2782 deltacomputer = deltautil.deltacomputer(destrevlog)
2782 index = self.index
2783 index = self.index
2783 for rev in self:
2784 for rev in self:
2784 entry = index[rev]
2785 entry = index[rev]
2785
2786
2786 # Some classes override linkrev to take filtered revs into
2787 # Some classes override linkrev to take filtered revs into
2787 # account. Use raw entry from index.
2788 # account. Use raw entry from index.
2788 flags = entry[0] & 0xFFFF
2789 flags = entry[0] & 0xFFFF
2789 linkrev = entry[4]
2790 linkrev = entry[4]
2790 p1 = index[entry[5]][7]
2791 p1 = index[entry[5]][7]
2791 p2 = index[entry[6]][7]
2792 p2 = index[entry[6]][7]
2792 node = entry[7]
2793 node = entry[7]
2793
2794
2794 sidedataactions = (False, [], {}, 0, 0)
2795 sidedataactions = (False, [], {}, 0, 0)
2795 if sidedatacompanion is not None:
2796 if sidedatacompanion is not None:
2796 sidedataactions = sidedatacompanion(self, rev)
2797 sidedataactions = sidedatacompanion(self, rev)
2797
2798
2798 # (Possibly) reuse the delta from the revlog if allowed and
2799 # (Possibly) reuse the delta from the revlog if allowed and
2799 # the revlog chunk is a delta.
2800 # the revlog chunk is a delta.
2800 cachedelta = None
2801 cachedelta = None
2801 rawtext = None
2802 rawtext = None
2802 if any(sidedataactions) or deltareuse == self.DELTAREUSEFULLADD:
2803 if any(sidedataactions) or deltareuse == self.DELTAREUSEFULLADD:
2803 dropall = sidedataactions[0]
2804 dropall = sidedataactions[0]
2804 filterout = sidedataactions[1]
2805 filterout = sidedataactions[1]
2805 update = sidedataactions[2]
2806 update = sidedataactions[2]
2806 new_flags = sidedataactions[3]
2807 new_flags = sidedataactions[3]
2807 dropped_flags = sidedataactions[4]
2808 dropped_flags = sidedataactions[4]
2808 text, sidedata = self._revisiondata(rev)
2809 text, sidedata = self._revisiondata(rev)
2809 if dropall:
2810 if dropall:
2810 sidedata = {}
2811 sidedata = {}
2811 for key in filterout:
2812 for key in filterout:
2812 sidedata.pop(key, None)
2813 sidedata.pop(key, None)
2813 sidedata.update(update)
2814 sidedata.update(update)
2814 if not sidedata:
2815 if not sidedata:
2815 sidedata = None
2816 sidedata = None
2816
2817
2817 flags |= new_flags
2818 flags |= new_flags
2818 flags &= ~dropped_flags
2819 flags &= ~dropped_flags
2819
2820
2820 destrevlog.addrevision(
2821 destrevlog.addrevision(
2821 text,
2822 text,
2822 tr,
2823 tr,
2823 linkrev,
2824 linkrev,
2824 p1,
2825 p1,
2825 p2,
2826 p2,
2826 cachedelta=cachedelta,
2827 cachedelta=cachedelta,
2827 node=node,
2828 node=node,
2828 flags=flags,
2829 flags=flags,
2829 deltacomputer=deltacomputer,
2830 deltacomputer=deltacomputer,
2830 sidedata=sidedata,
2831 sidedata=sidedata,
2831 )
2832 )
2832 else:
2833 else:
2833 if destrevlog._lazydelta:
2834 if destrevlog._lazydelta:
2834 dp = self.deltaparent(rev)
2835 dp = self.deltaparent(rev)
2835 if dp != nullrev:
2836 if dp != nullrev:
2836 cachedelta = (dp, bytes(self._chunk(rev)))
2837 cachedelta = (dp, bytes(self._chunk(rev)))
2837
2838
2838 if not cachedelta:
2839 if not cachedelta:
2839 rawtext = self.rawdata(rev)
2840 rawtext = self.rawdata(rev)
2840
2841
2841 ifh = destrevlog.opener(
2842 ifh = destrevlog.opener(
2842 destrevlog.indexfile, b'a+', checkambig=False
2843 destrevlog.indexfile, b'a+', checkambig=False
2843 )
2844 )
2844 dfh = None
2845 dfh = None
2845 if not destrevlog._inline:
2846 if not destrevlog._inline:
2846 dfh = destrevlog.opener(destrevlog.datafile, b'a+')
2847 dfh = destrevlog.opener(destrevlog.datafile, b'a+')
2847 try:
2848 try:
2848 destrevlog._addrevision(
2849 destrevlog._addrevision(
2849 node,
2850 node,
2850 rawtext,
2851 rawtext,
2851 tr,
2852 tr,
2852 linkrev,
2853 linkrev,
2853 p1,
2854 p1,
2854 p2,
2855 p2,
2855 flags,
2856 flags,
2856 cachedelta,
2857 cachedelta,
2857 ifh,
2858 ifh,
2858 dfh,
2859 dfh,
2859 deltacomputer=deltacomputer,
2860 deltacomputer=deltacomputer,
2860 )
2861 )
2861 finally:
2862 finally:
2862 if dfh:
2863 if dfh:
2863 dfh.close()
2864 dfh.close()
2864 ifh.close()
2865 ifh.close()
2865
2866
2866 if addrevisioncb:
2867 if addrevisioncb:
2867 addrevisioncb(self, rev, node)
2868 addrevisioncb(self, rev, node)
2868
2869
2869 def censorrevision(self, tr, censornode, tombstone=b''):
2870 def censorrevision(self, tr, censornode, tombstone=b''):
2870 if (self.version & 0xFFFF) == REVLOGV0:
2871 if (self.version & 0xFFFF) == REVLOGV0:
2871 raise error.RevlogError(
2872 raise error.RevlogError(
2872 _(b'cannot censor with version %d revlogs') % self.version
2873 _(b'cannot censor with version %d revlogs') % self.version
2873 )
2874 )
2874
2875
2875 censorrev = self.rev(censornode)
2876 censorrev = self.rev(censornode)
2876 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2877 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2877
2878
2878 if len(tombstone) > self.rawsize(censorrev):
2879 if len(tombstone) > self.rawsize(censorrev):
2879 raise error.Abort(
2880 raise error.Abort(
2880 _(b'censor tombstone must be no longer than censored data')
2881 _(b'censor tombstone must be no longer than censored data')
2881 )
2882 )
2882
2883
2883 # Rewriting the revlog in place is hard. Our strategy for censoring is
2884 # Rewriting the revlog in place is hard. Our strategy for censoring is
2884 # to create a new revlog, copy all revisions to it, then replace the
2885 # to create a new revlog, copy all revisions to it, then replace the
2885 # revlogs on transaction close.
2886 # revlogs on transaction close.
2886
2887
2887 newindexfile = self.indexfile + b'.tmpcensored'
2888 newindexfile = self.indexfile + b'.tmpcensored'
2888 newdatafile = self.datafile + b'.tmpcensored'
2889 newdatafile = self.datafile + b'.tmpcensored'
2889
2890
2890 # This is a bit dangerous. We could easily have a mismatch of state.
2891 # This is a bit dangerous. We could easily have a mismatch of state.
2891 newrl = revlog(self.opener, newindexfile, newdatafile, censorable=True)
2892 newrl = revlog(self.opener, newindexfile, newdatafile, censorable=True)
2892 newrl.version = self.version
2893 newrl.version = self.version
2893 newrl._generaldelta = self._generaldelta
2894 newrl._generaldelta = self._generaldelta
2894 newrl._io = self._io
2895 newrl._io = self._io
2895
2896
2896 for rev in self.revs():
2897 for rev in self.revs():
2897 node = self.node(rev)
2898 node = self.node(rev)
2898 p1, p2 = self.parents(node)
2899 p1, p2 = self.parents(node)
2899
2900
2900 if rev == censorrev:
2901 if rev == censorrev:
2901 newrl.addrawrevision(
2902 newrl.addrawrevision(
2902 tombstone,
2903 tombstone,
2903 tr,
2904 tr,
2904 self.linkrev(censorrev),
2905 self.linkrev(censorrev),
2905 p1,
2906 p1,
2906 p2,
2907 p2,
2907 censornode,
2908 censornode,
2908 REVIDX_ISCENSORED,
2909 REVIDX_ISCENSORED,
2909 )
2910 )
2910
2911
2911 if newrl.deltaparent(rev) != nullrev:
2912 if newrl.deltaparent(rev) != nullrev:
2912 raise error.Abort(
2913 raise error.Abort(
2913 _(
2914 _(
2914 b'censored revision stored as delta; '
2915 b'censored revision stored as delta; '
2915 b'cannot censor'
2916 b'cannot censor'
2916 ),
2917 ),
2917 hint=_(
2918 hint=_(
2918 b'censoring of revlogs is not '
2919 b'censoring of revlogs is not '
2919 b'fully implemented; please report '
2920 b'fully implemented; please report '
2920 b'this bug'
2921 b'this bug'
2921 ),
2922 ),
2922 )
2923 )
2923 continue
2924 continue
2924
2925
2925 if self.iscensored(rev):
2926 if self.iscensored(rev):
2926 if self.deltaparent(rev) != nullrev:
2927 if self.deltaparent(rev) != nullrev:
2927 raise error.Abort(
2928 raise error.Abort(
2928 _(
2929 _(
2929 b'cannot censor due to censored '
2930 b'cannot censor due to censored '
2930 b'revision having delta stored'
2931 b'revision having delta stored'
2931 )
2932 )
2932 )
2933 )
2933 rawtext = self._chunk(rev)
2934 rawtext = self._chunk(rev)
2934 else:
2935 else:
2935 rawtext = self.rawdata(rev)
2936 rawtext = self.rawdata(rev)
2936
2937
2937 newrl.addrawrevision(
2938 newrl.addrawrevision(
2938 rawtext, tr, self.linkrev(rev), p1, p2, node, self.flags(rev)
2939 rawtext, tr, self.linkrev(rev), p1, p2, node, self.flags(rev)
2939 )
2940 )
2940
2941
2941 tr.addbackup(self.indexfile, location=b'store')
2942 tr.addbackup(self.indexfile, location=b'store')
2942 if not self._inline:
2943 if not self._inline:
2943 tr.addbackup(self.datafile, location=b'store')
2944 tr.addbackup(self.datafile, location=b'store')
2944
2945
2945 self.opener.rename(newrl.indexfile, self.indexfile)
2946 self.opener.rename(newrl.indexfile, self.indexfile)
2946 if not self._inline:
2947 if not self._inline:
2947 self.opener.rename(newrl.datafile, self.datafile)
2948 self.opener.rename(newrl.datafile, self.datafile)
2948
2949
2949 self.clearcaches()
2950 self.clearcaches()
2950 self._loadindex()
2951 self._loadindex()
2951
2952
2952 def verifyintegrity(self, state):
2953 def verifyintegrity(self, state):
2953 """Verifies the integrity of the revlog.
2954 """Verifies the integrity of the revlog.
2954
2955
2955 Yields ``revlogproblem`` instances describing problems that are
2956 Yields ``revlogproblem`` instances describing problems that are
2956 found.
2957 found.
2957 """
2958 """
2958 dd, di = self.checksize()
2959 dd, di = self.checksize()
2959 if dd:
2960 if dd:
2960 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
2961 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
2961 if di:
2962 if di:
2962 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
2963 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
2963
2964
2964 version = self.version & 0xFFFF
2965 version = self.version & 0xFFFF
2965
2966
2966 # The verifier tells us what version revlog we should be.
2967 # The verifier tells us what version revlog we should be.
2967 if version != state[b'expectedversion']:
2968 if version != state[b'expectedversion']:
2968 yield revlogproblem(
2969 yield revlogproblem(
2969 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
2970 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
2970 % (self.indexfile, version, state[b'expectedversion'])
2971 % (self.indexfile, version, state[b'expectedversion'])
2971 )
2972 )
2972
2973
2973 state[b'skipread'] = set()
2974 state[b'skipread'] = set()
2974 state[b'safe_renamed'] = set()
2975 state[b'safe_renamed'] = set()
2975
2976
2976 for rev in self:
2977 for rev in self:
2977 node = self.node(rev)
2978 node = self.node(rev)
2978
2979
2979 # Verify contents. 4 cases to care about:
2980 # Verify contents. 4 cases to care about:
2980 #
2981 #
2981 # common: the most common case
2982 # common: the most common case
2982 # rename: with a rename
2983 # rename: with a rename
2983 # meta: file content starts with b'\1\n', the metadata
2984 # meta: file content starts with b'\1\n', the metadata
2984 # header defined in filelog.py, but without a rename
2985 # header defined in filelog.py, but without a rename
2985 # ext: content stored externally
2986 # ext: content stored externally
2986 #
2987 #
2987 # More formally, their differences are shown below:
2988 # More formally, their differences are shown below:
2988 #
2989 #
2989 # | common | rename | meta | ext
2990 # | common | rename | meta | ext
2990 # -------------------------------------------------------
2991 # -------------------------------------------------------
2991 # flags() | 0 | 0 | 0 | not 0
2992 # flags() | 0 | 0 | 0 | not 0
2992 # renamed() | False | True | False | ?
2993 # renamed() | False | True | False | ?
2993 # rawtext[0:2]=='\1\n'| False | True | True | ?
2994 # rawtext[0:2]=='\1\n'| False | True | True | ?
2994 #
2995 #
2995 # "rawtext" means the raw text stored in revlog data, which
2996 # "rawtext" means the raw text stored in revlog data, which
2996 # could be retrieved by "rawdata(rev)". "text"
2997 # could be retrieved by "rawdata(rev)". "text"
2997 # mentioned below is "revision(rev)".
2998 # mentioned below is "revision(rev)".
2998 #
2999 #
2999 # There are 3 different lengths stored physically:
3000 # There are 3 different lengths stored physically:
3000 # 1. L1: rawsize, stored in revlog index
3001 # 1. L1: rawsize, stored in revlog index
3001 # 2. L2: len(rawtext), stored in revlog data
3002 # 2. L2: len(rawtext), stored in revlog data
3002 # 3. L3: len(text), stored in revlog data if flags==0, or
3003 # 3. L3: len(text), stored in revlog data if flags==0, or
3003 # possibly somewhere else if flags!=0
3004 # possibly somewhere else if flags!=0
3004 #
3005 #
3005 # L1 should be equal to L2. L3 could be different from them.
3006 # L1 should be equal to L2. L3 could be different from them.
3006 # "text" may or may not affect commit hash depending on flag
3007 # "text" may or may not affect commit hash depending on flag
3007 # processors (see flagutil.addflagprocessor).
3008 # processors (see flagutil.addflagprocessor).
3008 #
3009 #
3009 # | common | rename | meta | ext
3010 # | common | rename | meta | ext
3010 # -------------------------------------------------
3011 # -------------------------------------------------
3011 # rawsize() | L1 | L1 | L1 | L1
3012 # rawsize() | L1 | L1 | L1 | L1
3012 # size() | L1 | L2-LM | L1(*) | L1 (?)
3013 # size() | L1 | L2-LM | L1(*) | L1 (?)
3013 # len(rawtext) | L2 | L2 | L2 | L2
3014 # len(rawtext) | L2 | L2 | L2 | L2
3014 # len(text) | L2 | L2 | L2 | L3
3015 # len(text) | L2 | L2 | L2 | L3
3015 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3016 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3016 #
3017 #
3017 # LM: length of metadata, depending on rawtext
3018 # LM: length of metadata, depending on rawtext
3018 # (*): not ideal, see comment in filelog.size
3019 # (*): not ideal, see comment in filelog.size
3019 # (?): could be "- len(meta)" if the resolved content has
3020 # (?): could be "- len(meta)" if the resolved content has
3020 # rename metadata
3021 # rename metadata
3021 #
3022 #
3022 # Checks needed to be done:
3023 # Checks needed to be done:
3023 # 1. length check: L1 == L2, in all cases.
3024 # 1. length check: L1 == L2, in all cases.
3024 # 2. hash check: depending on flag processor, we may need to
3025 # 2. hash check: depending on flag processor, we may need to
3025 # use either "text" (external), or "rawtext" (in revlog).
3026 # use either "text" (external), or "rawtext" (in revlog).
3026
3027
3027 try:
3028 try:
3028 skipflags = state.get(b'skipflags', 0)
3029 skipflags = state.get(b'skipflags', 0)
3029 if skipflags:
3030 if skipflags:
3030 skipflags &= self.flags(rev)
3031 skipflags &= self.flags(rev)
3031
3032
3032 _verify_revision(self, skipflags, state, node)
3033 _verify_revision(self, skipflags, state, node)
3033
3034
3034 l1 = self.rawsize(rev)
3035 l1 = self.rawsize(rev)
3035 l2 = len(self.rawdata(node))
3036 l2 = len(self.rawdata(node))
3036
3037
3037 if l1 != l2:
3038 if l1 != l2:
3038 yield revlogproblem(
3039 yield revlogproblem(
3039 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3040 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3040 node=node,
3041 node=node,
3041 )
3042 )
3042
3043
3043 except error.CensoredNodeError:
3044 except error.CensoredNodeError:
3044 if state[b'erroroncensored']:
3045 if state[b'erroroncensored']:
3045 yield revlogproblem(
3046 yield revlogproblem(
3046 error=_(b'censored file data'), node=node
3047 error=_(b'censored file data'), node=node
3047 )
3048 )
3048 state[b'skipread'].add(node)
3049 state[b'skipread'].add(node)
3049 except Exception as e:
3050 except Exception as e:
3050 yield revlogproblem(
3051 yield revlogproblem(
3051 error=_(b'unpacking %s: %s')
3052 error=_(b'unpacking %s: %s')
3052 % (short(node), stringutil.forcebytestr(e)),
3053 % (short(node), stringutil.forcebytestr(e)),
3053 node=node,
3054 node=node,
3054 )
3055 )
3055 state[b'skipread'].add(node)
3056 state[b'skipread'].add(node)
3056
3057
3057 def storageinfo(
3058 def storageinfo(
3058 self,
3059 self,
3059 exclusivefiles=False,
3060 exclusivefiles=False,
3060 sharedfiles=False,
3061 sharedfiles=False,
3061 revisionscount=False,
3062 revisionscount=False,
3062 trackedsize=False,
3063 trackedsize=False,
3063 storedsize=False,
3064 storedsize=False,
3064 ):
3065 ):
3065 d = {}
3066 d = {}
3066
3067
3067 if exclusivefiles:
3068 if exclusivefiles:
3068 d[b'exclusivefiles'] = [(self.opener, self.indexfile)]
3069 d[b'exclusivefiles'] = [(self.opener, self.indexfile)]
3069 if not self._inline:
3070 if not self._inline:
3070 d[b'exclusivefiles'].append((self.opener, self.datafile))
3071 d[b'exclusivefiles'].append((self.opener, self.datafile))
3071
3072
3072 if sharedfiles:
3073 if sharedfiles:
3073 d[b'sharedfiles'] = []
3074 d[b'sharedfiles'] = []
3074
3075
3075 if revisionscount:
3076 if revisionscount:
3076 d[b'revisionscount'] = len(self)
3077 d[b'revisionscount'] = len(self)
3077
3078
3078 if trackedsize:
3079 if trackedsize:
3079 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3080 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3080
3081
3081 if storedsize:
3082 if storedsize:
3082 d[b'storedsize'] = sum(
3083 d[b'storedsize'] = sum(
3083 self.opener.stat(path).st_size for path in self.files()
3084 self.opener.stat(path).st_size for path in self.files()
3084 )
3085 )
3085
3086
3086 return d
3087 return d
@@ -1,1448 +1,1450 b''
1 # storage.py - Testing of storage primitives.
1 # storage.py - Testing of storage primitives.
2 #
2 #
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import unittest
10 import unittest
11
11
12 from ..node import (
12 from ..node import (
13 hex,
13 hex,
14 nullid,
14 nullid,
15 nullrev,
15 nullrev,
16 )
16 )
17 from ..pycompat import getattr
17 from ..pycompat import getattr
18 from .. import (
18 from .. import (
19 error,
19 error,
20 mdiff,
20 mdiff,
21 )
21 )
22 from ..interfaces import repository
22 from ..interfaces import repository
23 from ..utils import storageutil
23 from ..utils import storageutil
24
24
25
25
26 class basetestcase(unittest.TestCase):
26 class basetestcase(unittest.TestCase):
27 if not getattr(unittest.TestCase, 'assertRaisesRegex', False):
27 if not getattr(unittest.TestCase, 'assertRaisesRegex', False):
28 assertRaisesRegex = ( # camelcase-required
28 assertRaisesRegex = ( # camelcase-required
29 unittest.TestCase.assertRaisesRegexp
29 unittest.TestCase.assertRaisesRegexp
30 )
30 )
31
31
32
32
33 class ifileindextests(basetestcase):
33 class ifileindextests(basetestcase):
34 """Generic tests for the ifileindex interface.
34 """Generic tests for the ifileindex interface.
35
35
36 All file storage backends for index data should conform to the tests in this
36 All file storage backends for index data should conform to the tests in this
37 class.
37 class.
38
38
39 Use ``makeifileindextests()`` to create an instance of this type.
39 Use ``makeifileindextests()`` to create an instance of this type.
40 """
40 """
41
41
42 def testempty(self):
42 def testempty(self):
43 f = self._makefilefn()
43 f = self._makefilefn()
44 self.assertEqual(len(f), 0, b'new file store has 0 length by default')
44 self.assertEqual(len(f), 0, b'new file store has 0 length by default')
45 self.assertEqual(list(f), [], b'iter yields nothing by default')
45 self.assertEqual(list(f), [], b'iter yields nothing by default')
46
46
47 gen = iter(f)
47 gen = iter(f)
48 with self.assertRaises(StopIteration):
48 with self.assertRaises(StopIteration):
49 next(gen)
49 next(gen)
50
50
51 self.assertFalse(f.hasnode(None))
51 self.assertFalse(f.hasnode(None))
52 self.assertFalse(f.hasnode(0))
52 self.assertFalse(f.hasnode(0))
53 self.assertFalse(f.hasnode(nullrev))
53 self.assertFalse(f.hasnode(nullrev))
54 self.assertFalse(f.hasnode(nullid))
54 self.assertFalse(f.hasnode(nullid))
55 self.assertFalse(f.hasnode(b'0'))
55 self.assertFalse(f.hasnode(b'0'))
56 self.assertFalse(f.hasnode(b'a' * 20))
56 self.assertFalse(f.hasnode(b'a' * 20))
57
57
58 # revs() should evaluate to an empty list.
58 # revs() should evaluate to an empty list.
59 self.assertEqual(list(f.revs()), [])
59 self.assertEqual(list(f.revs()), [])
60
60
61 revs = iter(f.revs())
61 revs = iter(f.revs())
62 with self.assertRaises(StopIteration):
62 with self.assertRaises(StopIteration):
63 next(revs)
63 next(revs)
64
64
65 self.assertEqual(list(f.revs(start=20)), [])
65 self.assertEqual(list(f.revs(start=20)), [])
66
66
67 # parents() and parentrevs() work with nullid/nullrev.
67 # parents() and parentrevs() work with nullid/nullrev.
68 self.assertEqual(f.parents(nullid), (nullid, nullid))
68 self.assertEqual(f.parents(nullid), (nullid, nullid))
69 self.assertEqual(f.parentrevs(nullrev), (nullrev, nullrev))
69 self.assertEqual(f.parentrevs(nullrev), (nullrev, nullrev))
70
70
71 with self.assertRaises(error.LookupError):
71 with self.assertRaises(error.LookupError):
72 f.parents(b'\x01' * 20)
72 f.parents(b'\x01' * 20)
73
73
74 for i in range(-5, 5):
74 for i in range(-5, 5):
75 if i == nullrev:
75 if i == nullrev:
76 continue
76 continue
77
77
78 with self.assertRaises(IndexError):
78 with self.assertRaises(IndexError):
79 f.parentrevs(i)
79 f.parentrevs(i)
80
80
81 # nullid/nullrev lookup always works.
81 # nullid/nullrev lookup always works.
82 self.assertEqual(f.rev(nullid), nullrev)
82 self.assertEqual(f.rev(nullid), nullrev)
83 self.assertEqual(f.node(nullrev), nullid)
83 self.assertEqual(f.node(nullrev), nullid)
84
84
85 with self.assertRaises(error.LookupError):
85 with self.assertRaises(error.LookupError):
86 f.rev(b'\x01' * 20)
86 f.rev(b'\x01' * 20)
87
87
88 for i in range(-5, 5):
88 for i in range(-5, 5):
89 if i == nullrev:
89 if i == nullrev:
90 continue
90 continue
91
91
92 with self.assertRaises(IndexError):
92 with self.assertRaises(IndexError):
93 f.node(i)
93 f.node(i)
94
94
95 self.assertEqual(f.lookup(nullid), nullid)
95 self.assertEqual(f.lookup(nullid), nullid)
96 self.assertEqual(f.lookup(nullrev), nullid)
96 self.assertEqual(f.lookup(nullrev), nullid)
97 self.assertEqual(f.lookup(hex(nullid)), nullid)
97 self.assertEqual(f.lookup(hex(nullid)), nullid)
98 self.assertEqual(f.lookup(b'%d' % nullrev), nullid)
98 self.assertEqual(f.lookup(b'%d' % nullrev), nullid)
99
99
100 with self.assertRaises(error.LookupError):
100 with self.assertRaises(error.LookupError):
101 f.lookup(b'badvalue')
101 f.lookup(b'badvalue')
102
102
103 with self.assertRaises(error.LookupError):
103 with self.assertRaises(error.LookupError):
104 f.lookup(hex(nullid)[0:12])
104 f.lookup(hex(nullid)[0:12])
105
105
106 with self.assertRaises(error.LookupError):
106 with self.assertRaises(error.LookupError):
107 f.lookup(b'-2')
107 f.lookup(b'-2')
108
108
109 with self.assertRaises(error.LookupError):
109 with self.assertRaises(error.LookupError):
110 f.lookup(b'0')
110 f.lookup(b'0')
111
111
112 with self.assertRaises(error.LookupError):
112 with self.assertRaises(error.LookupError):
113 f.lookup(b'1')
113 f.lookup(b'1')
114
114
115 with self.assertRaises(error.LookupError):
115 with self.assertRaises(error.LookupError):
116 f.lookup(b'11111111111111111111111111111111111111')
116 f.lookup(b'11111111111111111111111111111111111111')
117
117
118 for i in range(-5, 5):
118 for i in range(-5, 5):
119 if i == nullrev:
119 if i == nullrev:
120 continue
120 continue
121
121
122 with self.assertRaises(LookupError):
122 with self.assertRaises(LookupError):
123 f.lookup(i)
123 f.lookup(i)
124
124
125 self.assertEqual(f.linkrev(nullrev), nullrev)
125 self.assertEqual(f.linkrev(nullrev), nullrev)
126
126
127 for i in range(-5, 5):
127 for i in range(-5, 5):
128 if i == nullrev:
128 if i == nullrev:
129 continue
129 continue
130
130
131 with self.assertRaises(IndexError):
131 with self.assertRaises(IndexError):
132 f.linkrev(i)
132 f.linkrev(i)
133
133
134 self.assertFalse(f.iscensored(nullrev))
134 self.assertFalse(f.iscensored(nullrev))
135
135
136 for i in range(-5, 5):
136 for i in range(-5, 5):
137 if i == nullrev:
137 if i == nullrev:
138 continue
138 continue
139
139
140 with self.assertRaises(IndexError):
140 with self.assertRaises(IndexError):
141 f.iscensored(i)
141 f.iscensored(i)
142
142
143 self.assertEqual(list(f.commonancestorsheads(nullid, nullid)), [])
143 self.assertEqual(list(f.commonancestorsheads(nullid, nullid)), [])
144
144
145 with self.assertRaises(ValueError):
145 with self.assertRaises(ValueError):
146 self.assertEqual(list(f.descendants([])), [])
146 self.assertEqual(list(f.descendants([])), [])
147
147
148 self.assertEqual(list(f.descendants([nullrev])), [])
148 self.assertEqual(list(f.descendants([nullrev])), [])
149
149
150 self.assertEqual(f.heads(), [nullid])
150 self.assertEqual(f.heads(), [nullid])
151 self.assertEqual(f.heads(nullid), [nullid])
151 self.assertEqual(f.heads(nullid), [nullid])
152 self.assertEqual(f.heads(None, [nullid]), [nullid])
152 self.assertEqual(f.heads(None, [nullid]), [nullid])
153 self.assertEqual(f.heads(nullid, [nullid]), [nullid])
153 self.assertEqual(f.heads(nullid, [nullid]), [nullid])
154
154
155 self.assertEqual(f.children(nullid), [])
155 self.assertEqual(f.children(nullid), [])
156
156
157 with self.assertRaises(error.LookupError):
157 with self.assertRaises(error.LookupError):
158 f.children(b'\x01' * 20)
158 f.children(b'\x01' * 20)
159
159
160 def testsinglerevision(self):
160 def testsinglerevision(self):
161 f = self._makefilefn()
161 f = self._makefilefn()
162 with self._maketransactionfn() as tr:
162 with self._maketransactionfn() as tr:
163 node = f.add(b'initial', None, tr, 0, nullid, nullid)
163 node = f.add(b'initial', None, tr, 0, nullid, nullid)
164
164
165 self.assertEqual(len(f), 1)
165 self.assertEqual(len(f), 1)
166 self.assertEqual(list(f), [0])
166 self.assertEqual(list(f), [0])
167
167
168 gen = iter(f)
168 gen = iter(f)
169 self.assertEqual(next(gen), 0)
169 self.assertEqual(next(gen), 0)
170
170
171 with self.assertRaises(StopIteration):
171 with self.assertRaises(StopIteration):
172 next(gen)
172 next(gen)
173
173
174 self.assertTrue(f.hasnode(node))
174 self.assertTrue(f.hasnode(node))
175 self.assertFalse(f.hasnode(hex(node)))
175 self.assertFalse(f.hasnode(hex(node)))
176 self.assertFalse(f.hasnode(nullrev))
176 self.assertFalse(f.hasnode(nullrev))
177 self.assertFalse(f.hasnode(nullid))
177 self.assertFalse(f.hasnode(nullid))
178 self.assertFalse(f.hasnode(node[0:12]))
178 self.assertFalse(f.hasnode(node[0:12]))
179 self.assertFalse(f.hasnode(hex(node)[0:20]))
179 self.assertFalse(f.hasnode(hex(node)[0:20]))
180
180
181 self.assertEqual(list(f.revs()), [0])
181 self.assertEqual(list(f.revs()), [0])
182 self.assertEqual(list(f.revs(start=1)), [])
182 self.assertEqual(list(f.revs(start=1)), [])
183 self.assertEqual(list(f.revs(start=0)), [0])
183 self.assertEqual(list(f.revs(start=0)), [0])
184 self.assertEqual(list(f.revs(stop=0)), [0])
184 self.assertEqual(list(f.revs(stop=0)), [0])
185 self.assertEqual(list(f.revs(stop=1)), [0])
185 self.assertEqual(list(f.revs(stop=1)), [0])
186 self.assertEqual(list(f.revs(1, 1)), [])
186 self.assertEqual(list(f.revs(1, 1)), [])
187 # TODO buggy
187 # TODO buggy
188 self.assertEqual(list(f.revs(1, 0)), [1, 0])
188 self.assertEqual(list(f.revs(1, 0)), [1, 0])
189 self.assertEqual(list(f.revs(2, 0)), [2, 1, 0])
189 self.assertEqual(list(f.revs(2, 0)), [2, 1, 0])
190
190
191 self.assertEqual(f.parents(node), (nullid, nullid))
191 self.assertEqual(f.parents(node), (nullid, nullid))
192 self.assertEqual(f.parentrevs(0), (nullrev, nullrev))
192 self.assertEqual(f.parentrevs(0), (nullrev, nullrev))
193
193
194 with self.assertRaises(error.LookupError):
194 with self.assertRaises(error.LookupError):
195 f.parents(b'\x01' * 20)
195 f.parents(b'\x01' * 20)
196
196
197 with self.assertRaises(IndexError):
197 with self.assertRaises(IndexError):
198 f.parentrevs(1)
198 f.parentrevs(1)
199
199
200 self.assertEqual(f.rev(node), 0)
200 self.assertEqual(f.rev(node), 0)
201
201
202 with self.assertRaises(error.LookupError):
202 with self.assertRaises(error.LookupError):
203 f.rev(b'\x01' * 20)
203 f.rev(b'\x01' * 20)
204
204
205 self.assertEqual(f.node(0), node)
205 self.assertEqual(f.node(0), node)
206
206
207 with self.assertRaises(IndexError):
207 with self.assertRaises(IndexError):
208 f.node(1)
208 f.node(1)
209
209
210 self.assertEqual(f.lookup(node), node)
210 self.assertEqual(f.lookup(node), node)
211 self.assertEqual(f.lookup(0), node)
211 self.assertEqual(f.lookup(0), node)
212 self.assertEqual(f.lookup(-1), nullid)
212 self.assertEqual(f.lookup(-1), nullid)
213 self.assertEqual(f.lookup(b'0'), node)
213 self.assertEqual(f.lookup(b'0'), node)
214 self.assertEqual(f.lookup(hex(node)), node)
214 self.assertEqual(f.lookup(hex(node)), node)
215
215
216 with self.assertRaises(error.LookupError):
216 with self.assertRaises(error.LookupError):
217 f.lookup(hex(node)[0:12])
217 f.lookup(hex(node)[0:12])
218
218
219 with self.assertRaises(error.LookupError):
219 with self.assertRaises(error.LookupError):
220 f.lookup(-2)
220 f.lookup(-2)
221
221
222 with self.assertRaises(error.LookupError):
222 with self.assertRaises(error.LookupError):
223 f.lookup(b'-2')
223 f.lookup(b'-2')
224
224
225 with self.assertRaises(error.LookupError):
225 with self.assertRaises(error.LookupError):
226 f.lookup(1)
226 f.lookup(1)
227
227
228 with self.assertRaises(error.LookupError):
228 with self.assertRaises(error.LookupError):
229 f.lookup(b'1')
229 f.lookup(b'1')
230
230
231 self.assertEqual(f.linkrev(0), 0)
231 self.assertEqual(f.linkrev(0), 0)
232
232
233 with self.assertRaises(IndexError):
233 with self.assertRaises(IndexError):
234 f.linkrev(1)
234 f.linkrev(1)
235
235
236 self.assertFalse(f.iscensored(0))
236 self.assertFalse(f.iscensored(0))
237
237
238 with self.assertRaises(IndexError):
238 with self.assertRaises(IndexError):
239 f.iscensored(1)
239 f.iscensored(1)
240
240
241 self.assertEqual(list(f.descendants([0])), [])
241 self.assertEqual(list(f.descendants([0])), [])
242
242
243 self.assertEqual(f.heads(), [node])
243 self.assertEqual(f.heads(), [node])
244 self.assertEqual(f.heads(node), [node])
244 self.assertEqual(f.heads(node), [node])
245 self.assertEqual(f.heads(stop=[node]), [node])
245 self.assertEqual(f.heads(stop=[node]), [node])
246
246
247 with self.assertRaises(error.LookupError):
247 with self.assertRaises(error.LookupError):
248 f.heads(stop=[b'\x01' * 20])
248 f.heads(stop=[b'\x01' * 20])
249
249
250 self.assertEqual(f.children(node), [])
250 self.assertEqual(f.children(node), [])
251
251
252 def testmultiplerevisions(self):
252 def testmultiplerevisions(self):
253 fulltext0 = b'x' * 1024
253 fulltext0 = b'x' * 1024
254 fulltext1 = fulltext0 + b'y'
254 fulltext1 = fulltext0 + b'y'
255 fulltext2 = b'y' + fulltext0 + b'z'
255 fulltext2 = b'y' + fulltext0 + b'z'
256
256
257 f = self._makefilefn()
257 f = self._makefilefn()
258 with self._maketransactionfn() as tr:
258 with self._maketransactionfn() as tr:
259 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
259 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
260 node1 = f.add(fulltext1, None, tr, 1, node0, nullid)
260 node1 = f.add(fulltext1, None, tr, 1, node0, nullid)
261 node2 = f.add(fulltext2, None, tr, 3, node1, nullid)
261 node2 = f.add(fulltext2, None, tr, 3, node1, nullid)
262
262
263 self.assertEqual(len(f), 3)
263 self.assertEqual(len(f), 3)
264 self.assertEqual(list(f), [0, 1, 2])
264 self.assertEqual(list(f), [0, 1, 2])
265
265
266 gen = iter(f)
266 gen = iter(f)
267 self.assertEqual(next(gen), 0)
267 self.assertEqual(next(gen), 0)
268 self.assertEqual(next(gen), 1)
268 self.assertEqual(next(gen), 1)
269 self.assertEqual(next(gen), 2)
269 self.assertEqual(next(gen), 2)
270
270
271 with self.assertRaises(StopIteration):
271 with self.assertRaises(StopIteration):
272 next(gen)
272 next(gen)
273
273
274 self.assertEqual(list(f.revs()), [0, 1, 2])
274 self.assertEqual(list(f.revs()), [0, 1, 2])
275 self.assertEqual(list(f.revs(0)), [0, 1, 2])
275 self.assertEqual(list(f.revs(0)), [0, 1, 2])
276 self.assertEqual(list(f.revs(1)), [1, 2])
276 self.assertEqual(list(f.revs(1)), [1, 2])
277 self.assertEqual(list(f.revs(2)), [2])
277 self.assertEqual(list(f.revs(2)), [2])
278 self.assertEqual(list(f.revs(3)), [])
278 self.assertEqual(list(f.revs(3)), [])
279 self.assertEqual(list(f.revs(stop=1)), [0, 1])
279 self.assertEqual(list(f.revs(stop=1)), [0, 1])
280 self.assertEqual(list(f.revs(stop=2)), [0, 1, 2])
280 self.assertEqual(list(f.revs(stop=2)), [0, 1, 2])
281 self.assertEqual(list(f.revs(stop=3)), [0, 1, 2])
281 self.assertEqual(list(f.revs(stop=3)), [0, 1, 2])
282 self.assertEqual(list(f.revs(2, 0)), [2, 1, 0])
282 self.assertEqual(list(f.revs(2, 0)), [2, 1, 0])
283 self.assertEqual(list(f.revs(2, 1)), [2, 1])
283 self.assertEqual(list(f.revs(2, 1)), [2, 1])
284 # TODO this is wrong
284 # TODO this is wrong
285 self.assertEqual(list(f.revs(3, 2)), [3, 2])
285 self.assertEqual(list(f.revs(3, 2)), [3, 2])
286
286
287 self.assertEqual(f.parents(node0), (nullid, nullid))
287 self.assertEqual(f.parents(node0), (nullid, nullid))
288 self.assertEqual(f.parents(node1), (node0, nullid))
288 self.assertEqual(f.parents(node1), (node0, nullid))
289 self.assertEqual(f.parents(node2), (node1, nullid))
289 self.assertEqual(f.parents(node2), (node1, nullid))
290
290
291 self.assertEqual(f.parentrevs(0), (nullrev, nullrev))
291 self.assertEqual(f.parentrevs(0), (nullrev, nullrev))
292 self.assertEqual(f.parentrevs(1), (0, nullrev))
292 self.assertEqual(f.parentrevs(1), (0, nullrev))
293 self.assertEqual(f.parentrevs(2), (1, nullrev))
293 self.assertEqual(f.parentrevs(2), (1, nullrev))
294
294
295 self.assertEqual(f.rev(node0), 0)
295 self.assertEqual(f.rev(node0), 0)
296 self.assertEqual(f.rev(node1), 1)
296 self.assertEqual(f.rev(node1), 1)
297 self.assertEqual(f.rev(node2), 2)
297 self.assertEqual(f.rev(node2), 2)
298
298
299 with self.assertRaises(error.LookupError):
299 with self.assertRaises(error.LookupError):
300 f.rev(b'\x01' * 20)
300 f.rev(b'\x01' * 20)
301
301
302 self.assertEqual(f.node(0), node0)
302 self.assertEqual(f.node(0), node0)
303 self.assertEqual(f.node(1), node1)
303 self.assertEqual(f.node(1), node1)
304 self.assertEqual(f.node(2), node2)
304 self.assertEqual(f.node(2), node2)
305
305
306 with self.assertRaises(IndexError):
306 with self.assertRaises(IndexError):
307 f.node(3)
307 f.node(3)
308
308
309 self.assertEqual(f.lookup(node0), node0)
309 self.assertEqual(f.lookup(node0), node0)
310 self.assertEqual(f.lookup(0), node0)
310 self.assertEqual(f.lookup(0), node0)
311 self.assertEqual(f.lookup(b'0'), node0)
311 self.assertEqual(f.lookup(b'0'), node0)
312 self.assertEqual(f.lookup(hex(node0)), node0)
312 self.assertEqual(f.lookup(hex(node0)), node0)
313
313
314 self.assertEqual(f.lookup(node1), node1)
314 self.assertEqual(f.lookup(node1), node1)
315 self.assertEqual(f.lookup(1), node1)
315 self.assertEqual(f.lookup(1), node1)
316 self.assertEqual(f.lookup(b'1'), node1)
316 self.assertEqual(f.lookup(b'1'), node1)
317 self.assertEqual(f.lookup(hex(node1)), node1)
317 self.assertEqual(f.lookup(hex(node1)), node1)
318
318
319 self.assertEqual(f.linkrev(0), 0)
319 self.assertEqual(f.linkrev(0), 0)
320 self.assertEqual(f.linkrev(1), 1)
320 self.assertEqual(f.linkrev(1), 1)
321 self.assertEqual(f.linkrev(2), 3)
321 self.assertEqual(f.linkrev(2), 3)
322
322
323 with self.assertRaises(IndexError):
323 with self.assertRaises(IndexError):
324 f.linkrev(3)
324 f.linkrev(3)
325
325
326 self.assertFalse(f.iscensored(0))
326 self.assertFalse(f.iscensored(0))
327 self.assertFalse(f.iscensored(1))
327 self.assertFalse(f.iscensored(1))
328 self.assertFalse(f.iscensored(2))
328 self.assertFalse(f.iscensored(2))
329
329
330 with self.assertRaises(IndexError):
330 with self.assertRaises(IndexError):
331 f.iscensored(3)
331 f.iscensored(3)
332
332
333 self.assertEqual(f.commonancestorsheads(node1, nullid), [])
333 self.assertEqual(f.commonancestorsheads(node1, nullid), [])
334 self.assertEqual(f.commonancestorsheads(node1, node0), [node0])
334 self.assertEqual(f.commonancestorsheads(node1, node0), [node0])
335 self.assertEqual(f.commonancestorsheads(node1, node1), [node1])
335 self.assertEqual(f.commonancestorsheads(node1, node1), [node1])
336 self.assertEqual(f.commonancestorsheads(node0, node1), [node0])
336 self.assertEqual(f.commonancestorsheads(node0, node1), [node0])
337 self.assertEqual(f.commonancestorsheads(node1, node2), [node1])
337 self.assertEqual(f.commonancestorsheads(node1, node2), [node1])
338 self.assertEqual(f.commonancestorsheads(node2, node1), [node1])
338 self.assertEqual(f.commonancestorsheads(node2, node1), [node1])
339
339
340 self.assertEqual(list(f.descendants([0])), [1, 2])
340 self.assertEqual(list(f.descendants([0])), [1, 2])
341 self.assertEqual(list(f.descendants([1])), [2])
341 self.assertEqual(list(f.descendants([1])), [2])
342 self.assertEqual(list(f.descendants([0, 1])), [1, 2])
342 self.assertEqual(list(f.descendants([0, 1])), [1, 2])
343
343
344 self.assertEqual(f.heads(), [node2])
344 self.assertEqual(f.heads(), [node2])
345 self.assertEqual(f.heads(node0), [node2])
345 self.assertEqual(f.heads(node0), [node2])
346 self.assertEqual(f.heads(node1), [node2])
346 self.assertEqual(f.heads(node1), [node2])
347 self.assertEqual(f.heads(node2), [node2])
347 self.assertEqual(f.heads(node2), [node2])
348
348
349 # TODO this behavior seems wonky. Is it correct? If so, the
349 # TODO this behavior seems wonky. Is it correct? If so, the
350 # docstring for heads() should be updated to reflect desired
350 # docstring for heads() should be updated to reflect desired
351 # behavior.
351 # behavior.
352 self.assertEqual(f.heads(stop=[node1]), [node1, node2])
352 self.assertEqual(f.heads(stop=[node1]), [node1, node2])
353 self.assertEqual(f.heads(stop=[node0]), [node0, node2])
353 self.assertEqual(f.heads(stop=[node0]), [node0, node2])
354 self.assertEqual(f.heads(stop=[node1, node2]), [node1, node2])
354 self.assertEqual(f.heads(stop=[node1, node2]), [node1, node2])
355
355
356 with self.assertRaises(error.LookupError):
356 with self.assertRaises(error.LookupError):
357 f.heads(stop=[b'\x01' * 20])
357 f.heads(stop=[b'\x01' * 20])
358
358
359 self.assertEqual(f.children(node0), [node1])
359 self.assertEqual(f.children(node0), [node1])
360 self.assertEqual(f.children(node1), [node2])
360 self.assertEqual(f.children(node1), [node2])
361 self.assertEqual(f.children(node2), [])
361 self.assertEqual(f.children(node2), [])
362
362
363 def testmultipleheads(self):
363 def testmultipleheads(self):
364 f = self._makefilefn()
364 f = self._makefilefn()
365
365
366 with self._maketransactionfn() as tr:
366 with self._maketransactionfn() as tr:
367 node0 = f.add(b'0', None, tr, 0, nullid, nullid)
367 node0 = f.add(b'0', None, tr, 0, nullid, nullid)
368 node1 = f.add(b'1', None, tr, 1, node0, nullid)
368 node1 = f.add(b'1', None, tr, 1, node0, nullid)
369 node2 = f.add(b'2', None, tr, 2, node1, nullid)
369 node2 = f.add(b'2', None, tr, 2, node1, nullid)
370 node3 = f.add(b'3', None, tr, 3, node0, nullid)
370 node3 = f.add(b'3', None, tr, 3, node0, nullid)
371 node4 = f.add(b'4', None, tr, 4, node3, nullid)
371 node4 = f.add(b'4', None, tr, 4, node3, nullid)
372 node5 = f.add(b'5', None, tr, 5, node0, nullid)
372 node5 = f.add(b'5', None, tr, 5, node0, nullid)
373
373
374 self.assertEqual(len(f), 6)
374 self.assertEqual(len(f), 6)
375
375
376 self.assertEqual(list(f.descendants([0])), [1, 2, 3, 4, 5])
376 self.assertEqual(list(f.descendants([0])), [1, 2, 3, 4, 5])
377 self.assertEqual(list(f.descendants([1])), [2])
377 self.assertEqual(list(f.descendants([1])), [2])
378 self.assertEqual(list(f.descendants([2])), [])
378 self.assertEqual(list(f.descendants([2])), [])
379 self.assertEqual(list(f.descendants([3])), [4])
379 self.assertEqual(list(f.descendants([3])), [4])
380 self.assertEqual(list(f.descendants([0, 1])), [1, 2, 3, 4, 5])
380 self.assertEqual(list(f.descendants([0, 1])), [1, 2, 3, 4, 5])
381 self.assertEqual(list(f.descendants([1, 3])), [2, 4])
381 self.assertEqual(list(f.descendants([1, 3])), [2, 4])
382
382
383 self.assertEqual(f.heads(), [node2, node4, node5])
383 self.assertEqual(f.heads(), [node2, node4, node5])
384 self.assertEqual(f.heads(node0), [node2, node4, node5])
384 self.assertEqual(f.heads(node0), [node2, node4, node5])
385 self.assertEqual(f.heads(node1), [node2])
385 self.assertEqual(f.heads(node1), [node2])
386 self.assertEqual(f.heads(node2), [node2])
386 self.assertEqual(f.heads(node2), [node2])
387 self.assertEqual(f.heads(node3), [node4])
387 self.assertEqual(f.heads(node3), [node4])
388 self.assertEqual(f.heads(node4), [node4])
388 self.assertEqual(f.heads(node4), [node4])
389 self.assertEqual(f.heads(node5), [node5])
389 self.assertEqual(f.heads(node5), [node5])
390
390
391 # TODO this seems wrong.
391 # TODO this seems wrong.
392 self.assertEqual(f.heads(stop=[node0]), [node0, node2, node4, node5])
392 self.assertEqual(f.heads(stop=[node0]), [node0, node2, node4, node5])
393 self.assertEqual(f.heads(stop=[node1]), [node1, node2, node4, node5])
393 self.assertEqual(f.heads(stop=[node1]), [node1, node2, node4, node5])
394
394
395 self.assertEqual(f.children(node0), [node1, node3, node5])
395 self.assertEqual(f.children(node0), [node1, node3, node5])
396 self.assertEqual(f.children(node1), [node2])
396 self.assertEqual(f.children(node1), [node2])
397 self.assertEqual(f.children(node2), [])
397 self.assertEqual(f.children(node2), [])
398 self.assertEqual(f.children(node3), [node4])
398 self.assertEqual(f.children(node3), [node4])
399 self.assertEqual(f.children(node4), [])
399 self.assertEqual(f.children(node4), [])
400 self.assertEqual(f.children(node5), [])
400 self.assertEqual(f.children(node5), [])
401
401
402
402
403 class ifiledatatests(basetestcase):
403 class ifiledatatests(basetestcase):
404 """Generic tests for the ifiledata interface.
404 """Generic tests for the ifiledata interface.
405
405
406 All file storage backends for data should conform to the tests in this
406 All file storage backends for data should conform to the tests in this
407 class.
407 class.
408
408
409 Use ``makeifiledatatests()`` to create an instance of this type.
409 Use ``makeifiledatatests()`` to create an instance of this type.
410 """
410 """
411
411
412 def testempty(self):
412 def testempty(self):
413 f = self._makefilefn()
413 f = self._makefilefn()
414
414
415 self.assertEqual(f.storageinfo(), {})
415 self.assertEqual(f.storageinfo(), {})
416 self.assertEqual(
416 self.assertEqual(
417 f.storageinfo(revisionscount=True, trackedsize=True),
417 f.storageinfo(revisionscount=True, trackedsize=True),
418 {b'revisionscount': 0, b'trackedsize': 0},
418 {b'revisionscount': 0, b'trackedsize': 0},
419 )
419 )
420
420
421 self.assertEqual(f.size(nullrev), 0)
421 self.assertEqual(f.size(nullrev), 0)
422
422
423 for i in range(-5, 5):
423 for i in range(-5, 5):
424 if i == nullrev:
424 if i == nullrev:
425 continue
425 continue
426
426
427 with self.assertRaises(IndexError):
427 with self.assertRaises(IndexError):
428 f.size(i)
428 f.size(i)
429
429
430 self.assertEqual(f.revision(nullid), b'')
430 self.assertEqual(f.revision(nullid), b'')
431 self.assertEqual(f.rawdata(nullid), b'')
431 self.assertEqual(f.rawdata(nullid), b'')
432
432
433 with self.assertRaises(error.LookupError):
433 with self.assertRaises(error.LookupError):
434 f.revision(b'\x01' * 20)
434 f.revision(b'\x01' * 20)
435
435
436 self.assertEqual(f.read(nullid), b'')
436 self.assertEqual(f.read(nullid), b'')
437
437
438 with self.assertRaises(error.LookupError):
438 with self.assertRaises(error.LookupError):
439 f.read(b'\x01' * 20)
439 f.read(b'\x01' * 20)
440
440
441 self.assertFalse(f.renamed(nullid))
441 self.assertFalse(f.renamed(nullid))
442
442
443 with self.assertRaises(error.LookupError):
443 with self.assertRaises(error.LookupError):
444 f.read(b'\x01' * 20)
444 f.read(b'\x01' * 20)
445
445
446 self.assertTrue(f.cmp(nullid, b''))
446 self.assertTrue(f.cmp(nullid, b''))
447 self.assertTrue(f.cmp(nullid, b'foo'))
447 self.assertTrue(f.cmp(nullid, b'foo'))
448
448
449 with self.assertRaises(error.LookupError):
449 with self.assertRaises(error.LookupError):
450 f.cmp(b'\x01' * 20, b'irrelevant')
450 f.cmp(b'\x01' * 20, b'irrelevant')
451
451
452 # Emitting empty list is an empty generator.
452 # Emitting empty list is an empty generator.
453 gen = f.emitrevisions([])
453 gen = f.emitrevisions([])
454 with self.assertRaises(StopIteration):
454 with self.assertRaises(StopIteration):
455 next(gen)
455 next(gen)
456
456
457 # Emitting null node yields nothing.
457 # Emitting null node yields nothing.
458 gen = f.emitrevisions([nullid])
458 gen = f.emitrevisions([nullid])
459 with self.assertRaises(StopIteration):
459 with self.assertRaises(StopIteration):
460 next(gen)
460 next(gen)
461
461
462 # Requesting unknown node fails.
462 # Requesting unknown node fails.
463 with self.assertRaises(error.LookupError):
463 with self.assertRaises(error.LookupError):
464 list(f.emitrevisions([b'\x01' * 20]))
464 list(f.emitrevisions([b'\x01' * 20]))
465
465
466 def testsinglerevision(self):
466 def testsinglerevision(self):
467 fulltext = b'initial'
467 fulltext = b'initial'
468
468
469 f = self._makefilefn()
469 f = self._makefilefn()
470 with self._maketransactionfn() as tr:
470 with self._maketransactionfn() as tr:
471 node = f.add(fulltext, None, tr, 0, nullid, nullid)
471 node = f.add(fulltext, None, tr, 0, nullid, nullid)
472
472
473 self.assertEqual(f.storageinfo(), {})
473 self.assertEqual(f.storageinfo(), {})
474 self.assertEqual(
474 self.assertEqual(
475 f.storageinfo(revisionscount=True, trackedsize=True),
475 f.storageinfo(revisionscount=True, trackedsize=True),
476 {b'revisionscount': 1, b'trackedsize': len(fulltext)},
476 {b'revisionscount': 1, b'trackedsize': len(fulltext)},
477 )
477 )
478
478
479 self.assertEqual(f.size(0), len(fulltext))
479 self.assertEqual(f.size(0), len(fulltext))
480
480
481 with self.assertRaises(IndexError):
481 with self.assertRaises(IndexError):
482 f.size(1)
482 f.size(1)
483
483
484 self.assertEqual(f.revision(node), fulltext)
484 self.assertEqual(f.revision(node), fulltext)
485 self.assertEqual(f.rawdata(node), fulltext)
485 self.assertEqual(f.rawdata(node), fulltext)
486
486
487 self.assertEqual(f.read(node), fulltext)
487 self.assertEqual(f.read(node), fulltext)
488
488
489 self.assertFalse(f.renamed(node))
489 self.assertFalse(f.renamed(node))
490
490
491 self.assertFalse(f.cmp(node, fulltext))
491 self.assertFalse(f.cmp(node, fulltext))
492 self.assertTrue(f.cmp(node, fulltext + b'extra'))
492 self.assertTrue(f.cmp(node, fulltext + b'extra'))
493
493
494 # Emitting a single revision works.
494 # Emitting a single revision works.
495 gen = f.emitrevisions([node])
495 gen = f.emitrevisions([node])
496 rev = next(gen)
496 rev = next(gen)
497
497
498 self.assertEqual(rev.node, node)
498 self.assertEqual(rev.node, node)
499 self.assertEqual(rev.p1node, nullid)
499 self.assertEqual(rev.p1node, nullid)
500 self.assertEqual(rev.p2node, nullid)
500 self.assertEqual(rev.p2node, nullid)
501 self.assertIsNone(rev.linknode)
501 self.assertIsNone(rev.linknode)
502 self.assertEqual(rev.basenode, nullid)
502 self.assertEqual(rev.basenode, nullid)
503 self.assertIsNone(rev.baserevisionsize)
503 self.assertIsNone(rev.baserevisionsize)
504 self.assertIsNone(rev.revision)
504 self.assertIsNone(rev.revision)
505 self.assertIsNone(rev.delta)
505 self.assertIsNone(rev.delta)
506
506
507 with self.assertRaises(StopIteration):
507 with self.assertRaises(StopIteration):
508 next(gen)
508 next(gen)
509
509
510 # Requesting revision data works.
510 # Requesting revision data works.
511 gen = f.emitrevisions([node], revisiondata=True)
511 gen = f.emitrevisions([node], revisiondata=True)
512 rev = next(gen)
512 rev = next(gen)
513
513
514 self.assertEqual(rev.node, node)
514 self.assertEqual(rev.node, node)
515 self.assertEqual(rev.p1node, nullid)
515 self.assertEqual(rev.p1node, nullid)
516 self.assertEqual(rev.p2node, nullid)
516 self.assertEqual(rev.p2node, nullid)
517 self.assertIsNone(rev.linknode)
517 self.assertIsNone(rev.linknode)
518 self.assertEqual(rev.basenode, nullid)
518 self.assertEqual(rev.basenode, nullid)
519 self.assertIsNone(rev.baserevisionsize)
519 self.assertIsNone(rev.baserevisionsize)
520 self.assertEqual(rev.revision, fulltext)
520 self.assertEqual(rev.revision, fulltext)
521 self.assertIsNone(rev.delta)
521 self.assertIsNone(rev.delta)
522
522
523 with self.assertRaises(StopIteration):
523 with self.assertRaises(StopIteration):
524 next(gen)
524 next(gen)
525
525
526 # Emitting an unknown node after a known revision results in error.
526 # Emitting an unknown node after a known revision results in error.
527 with self.assertRaises(error.LookupError):
527 with self.assertRaises(error.LookupError):
528 list(f.emitrevisions([node, b'\x01' * 20]))
528 list(f.emitrevisions([node, b'\x01' * 20]))
529
529
530 def testmultiplerevisions(self):
530 def testmultiplerevisions(self):
531 fulltext0 = b'x' * 1024
531 fulltext0 = b'x' * 1024
532 fulltext1 = fulltext0 + b'y'
532 fulltext1 = fulltext0 + b'y'
533 fulltext2 = b'y' + fulltext0 + b'z'
533 fulltext2 = b'y' + fulltext0 + b'z'
534
534
535 f = self._makefilefn()
535 f = self._makefilefn()
536 with self._maketransactionfn() as tr:
536 with self._maketransactionfn() as tr:
537 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
537 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
538 node1 = f.add(fulltext1, None, tr, 1, node0, nullid)
538 node1 = f.add(fulltext1, None, tr, 1, node0, nullid)
539 node2 = f.add(fulltext2, None, tr, 3, node1, nullid)
539 node2 = f.add(fulltext2, None, tr, 3, node1, nullid)
540
540
541 self.assertEqual(f.storageinfo(), {})
541 self.assertEqual(f.storageinfo(), {})
542 self.assertEqual(
542 self.assertEqual(
543 f.storageinfo(revisionscount=True, trackedsize=True),
543 f.storageinfo(revisionscount=True, trackedsize=True),
544 {
544 {
545 b'revisionscount': 3,
545 b'revisionscount': 3,
546 b'trackedsize': len(fulltext0)
546 b'trackedsize': len(fulltext0)
547 + len(fulltext1)
547 + len(fulltext1)
548 + len(fulltext2),
548 + len(fulltext2),
549 },
549 },
550 )
550 )
551
551
552 self.assertEqual(f.size(0), len(fulltext0))
552 self.assertEqual(f.size(0), len(fulltext0))
553 self.assertEqual(f.size(1), len(fulltext1))
553 self.assertEqual(f.size(1), len(fulltext1))
554 self.assertEqual(f.size(2), len(fulltext2))
554 self.assertEqual(f.size(2), len(fulltext2))
555
555
556 with self.assertRaises(IndexError):
556 with self.assertRaises(IndexError):
557 f.size(3)
557 f.size(3)
558
558
559 self.assertEqual(f.revision(node0), fulltext0)
559 self.assertEqual(f.revision(node0), fulltext0)
560 self.assertEqual(f.rawdata(node0), fulltext0)
560 self.assertEqual(f.rawdata(node0), fulltext0)
561 self.assertEqual(f.revision(node1), fulltext1)
561 self.assertEqual(f.revision(node1), fulltext1)
562 self.assertEqual(f.rawdata(node1), fulltext1)
562 self.assertEqual(f.rawdata(node1), fulltext1)
563 self.assertEqual(f.revision(node2), fulltext2)
563 self.assertEqual(f.revision(node2), fulltext2)
564 self.assertEqual(f.rawdata(node2), fulltext2)
564 self.assertEqual(f.rawdata(node2), fulltext2)
565
565
566 with self.assertRaises(error.LookupError):
566 with self.assertRaises(error.LookupError):
567 f.revision(b'\x01' * 20)
567 f.revision(b'\x01' * 20)
568
568
569 self.assertEqual(f.read(node0), fulltext0)
569 self.assertEqual(f.read(node0), fulltext0)
570 self.assertEqual(f.read(node1), fulltext1)
570 self.assertEqual(f.read(node1), fulltext1)
571 self.assertEqual(f.read(node2), fulltext2)
571 self.assertEqual(f.read(node2), fulltext2)
572
572
573 with self.assertRaises(error.LookupError):
573 with self.assertRaises(error.LookupError):
574 f.read(b'\x01' * 20)
574 f.read(b'\x01' * 20)
575
575
576 self.assertFalse(f.renamed(node0))
576 self.assertFalse(f.renamed(node0))
577 self.assertFalse(f.renamed(node1))
577 self.assertFalse(f.renamed(node1))
578 self.assertFalse(f.renamed(node2))
578 self.assertFalse(f.renamed(node2))
579
579
580 with self.assertRaises(error.LookupError):
580 with self.assertRaises(error.LookupError):
581 f.renamed(b'\x01' * 20)
581 f.renamed(b'\x01' * 20)
582
582
583 self.assertFalse(f.cmp(node0, fulltext0))
583 self.assertFalse(f.cmp(node0, fulltext0))
584 self.assertFalse(f.cmp(node1, fulltext1))
584 self.assertFalse(f.cmp(node1, fulltext1))
585 self.assertFalse(f.cmp(node2, fulltext2))
585 self.assertFalse(f.cmp(node2, fulltext2))
586
586
587 self.assertTrue(f.cmp(node1, fulltext0))
587 self.assertTrue(f.cmp(node1, fulltext0))
588 self.assertTrue(f.cmp(node2, fulltext1))
588 self.assertTrue(f.cmp(node2, fulltext1))
589
589
590 with self.assertRaises(error.LookupError):
590 with self.assertRaises(error.LookupError):
591 f.cmp(b'\x01' * 20, b'irrelevant')
591 f.cmp(b'\x01' * 20, b'irrelevant')
592
592
593 # Nodes should be emitted in order.
593 # Nodes should be emitted in order.
594 gen = f.emitrevisions([node0, node1, node2], revisiondata=True)
594 gen = f.emitrevisions([node0, node1, node2], revisiondata=True)
595
595
596 rev = next(gen)
596 rev = next(gen)
597
597
598 self.assertEqual(rev.node, node0)
598 self.assertEqual(rev.node, node0)
599 self.assertEqual(rev.p1node, nullid)
599 self.assertEqual(rev.p1node, nullid)
600 self.assertEqual(rev.p2node, nullid)
600 self.assertEqual(rev.p2node, nullid)
601 self.assertIsNone(rev.linknode)
601 self.assertIsNone(rev.linknode)
602 self.assertEqual(rev.basenode, nullid)
602 self.assertEqual(rev.basenode, nullid)
603 self.assertIsNone(rev.baserevisionsize)
603 self.assertIsNone(rev.baserevisionsize)
604 self.assertEqual(rev.revision, fulltext0)
604 self.assertEqual(rev.revision, fulltext0)
605 self.assertIsNone(rev.delta)
605 self.assertIsNone(rev.delta)
606
606
607 rev = next(gen)
607 rev = next(gen)
608
608
609 self.assertEqual(rev.node, node1)
609 self.assertEqual(rev.node, node1)
610 self.assertEqual(rev.p1node, node0)
610 self.assertEqual(rev.p1node, node0)
611 self.assertEqual(rev.p2node, nullid)
611 self.assertEqual(rev.p2node, nullid)
612 self.assertIsNone(rev.linknode)
612 self.assertIsNone(rev.linknode)
613 self.assertEqual(rev.basenode, node0)
613 self.assertEqual(rev.basenode, node0)
614 self.assertIsNone(rev.baserevisionsize)
614 self.assertIsNone(rev.baserevisionsize)
615 self.assertIsNone(rev.revision)
615 self.assertIsNone(rev.revision)
616 self.assertEqual(
616 self.assertEqual(
617 rev.delta,
617 rev.delta,
618 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' + fulltext1,
618 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' + fulltext1,
619 )
619 )
620
620
621 rev = next(gen)
621 rev = next(gen)
622
622
623 self.assertEqual(rev.node, node2)
623 self.assertEqual(rev.node, node2)
624 self.assertEqual(rev.p1node, node1)
624 self.assertEqual(rev.p1node, node1)
625 self.assertEqual(rev.p2node, nullid)
625 self.assertEqual(rev.p2node, nullid)
626 self.assertIsNone(rev.linknode)
626 self.assertIsNone(rev.linknode)
627 self.assertEqual(rev.basenode, node1)
627 self.assertEqual(rev.basenode, node1)
628 self.assertIsNone(rev.baserevisionsize)
628 self.assertIsNone(rev.baserevisionsize)
629 self.assertIsNone(rev.revision)
629 self.assertIsNone(rev.revision)
630 self.assertEqual(
630 self.assertEqual(
631 rev.delta,
631 rev.delta,
632 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' + fulltext2,
632 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' + fulltext2,
633 )
633 )
634
634
635 with self.assertRaises(StopIteration):
635 with self.assertRaises(StopIteration):
636 next(gen)
636 next(gen)
637
637
638 # Request not in DAG order is reordered to be in DAG order.
638 # Request not in DAG order is reordered to be in DAG order.
639 gen = f.emitrevisions([node2, node1, node0], revisiondata=True)
639 gen = f.emitrevisions([node2, node1, node0], revisiondata=True)
640
640
641 rev = next(gen)
641 rev = next(gen)
642
642
643 self.assertEqual(rev.node, node0)
643 self.assertEqual(rev.node, node0)
644 self.assertEqual(rev.p1node, nullid)
644 self.assertEqual(rev.p1node, nullid)
645 self.assertEqual(rev.p2node, nullid)
645 self.assertEqual(rev.p2node, nullid)
646 self.assertIsNone(rev.linknode)
646 self.assertIsNone(rev.linknode)
647 self.assertEqual(rev.basenode, nullid)
647 self.assertEqual(rev.basenode, nullid)
648 self.assertIsNone(rev.baserevisionsize)
648 self.assertIsNone(rev.baserevisionsize)
649 self.assertEqual(rev.revision, fulltext0)
649 self.assertEqual(rev.revision, fulltext0)
650 self.assertIsNone(rev.delta)
650 self.assertIsNone(rev.delta)
651
651
652 rev = next(gen)
652 rev = next(gen)
653
653
654 self.assertEqual(rev.node, node1)
654 self.assertEqual(rev.node, node1)
655 self.assertEqual(rev.p1node, node0)
655 self.assertEqual(rev.p1node, node0)
656 self.assertEqual(rev.p2node, nullid)
656 self.assertEqual(rev.p2node, nullid)
657 self.assertIsNone(rev.linknode)
657 self.assertIsNone(rev.linknode)
658 self.assertEqual(rev.basenode, node0)
658 self.assertEqual(rev.basenode, node0)
659 self.assertIsNone(rev.baserevisionsize)
659 self.assertIsNone(rev.baserevisionsize)
660 self.assertIsNone(rev.revision)
660 self.assertIsNone(rev.revision)
661 self.assertEqual(
661 self.assertEqual(
662 rev.delta,
662 rev.delta,
663 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' + fulltext1,
663 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' + fulltext1,
664 )
664 )
665
665
666 rev = next(gen)
666 rev = next(gen)
667
667
668 self.assertEqual(rev.node, node2)
668 self.assertEqual(rev.node, node2)
669 self.assertEqual(rev.p1node, node1)
669 self.assertEqual(rev.p1node, node1)
670 self.assertEqual(rev.p2node, nullid)
670 self.assertEqual(rev.p2node, nullid)
671 self.assertIsNone(rev.linknode)
671 self.assertIsNone(rev.linknode)
672 self.assertEqual(rev.basenode, node1)
672 self.assertEqual(rev.basenode, node1)
673 self.assertIsNone(rev.baserevisionsize)
673 self.assertIsNone(rev.baserevisionsize)
674 self.assertIsNone(rev.revision)
674 self.assertIsNone(rev.revision)
675 self.assertEqual(
675 self.assertEqual(
676 rev.delta,
676 rev.delta,
677 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' + fulltext2,
677 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' + fulltext2,
678 )
678 )
679
679
680 with self.assertRaises(StopIteration):
680 with self.assertRaises(StopIteration):
681 next(gen)
681 next(gen)
682
682
683 # Unrecognized nodesorder value raises ProgrammingError.
683 # Unrecognized nodesorder value raises ProgrammingError.
684 with self.assertRaises(error.ProgrammingError):
684 with self.assertRaises(error.ProgrammingError):
685 list(f.emitrevisions([], nodesorder=b'bad'))
685 list(f.emitrevisions([], nodesorder=b'bad'))
686
686
687 # nodesorder=storage is recognized. But we can't test it thoroughly
687 # nodesorder=storage is recognized. But we can't test it thoroughly
688 # because behavior is storage-dependent.
688 # because behavior is storage-dependent.
689 res = list(
689 res = list(
690 f.emitrevisions([node2, node1, node0], nodesorder=b'storage')
690 f.emitrevisions([node2, node1, node0], nodesorder=b'storage')
691 )
691 )
692 self.assertEqual(len(res), 3)
692 self.assertEqual(len(res), 3)
693 self.assertEqual({o.node for o in res}, {node0, node1, node2})
693 self.assertEqual({o.node for o in res}, {node0, node1, node2})
694
694
695 # nodesorder=nodes forces the order.
695 # nodesorder=nodes forces the order.
696 gen = f.emitrevisions(
696 gen = f.emitrevisions(
697 [node2, node0], nodesorder=b'nodes', revisiondata=True
697 [node2, node0], nodesorder=b'nodes', revisiondata=True
698 )
698 )
699
699
700 rev = next(gen)
700 rev = next(gen)
701 self.assertEqual(rev.node, node2)
701 self.assertEqual(rev.node, node2)
702 self.assertEqual(rev.p1node, node1)
702 self.assertEqual(rev.p1node, node1)
703 self.assertEqual(rev.p2node, nullid)
703 self.assertEqual(rev.p2node, nullid)
704 self.assertEqual(rev.basenode, nullid)
704 self.assertEqual(rev.basenode, nullid)
705 self.assertIsNone(rev.baserevisionsize)
705 self.assertIsNone(rev.baserevisionsize)
706 self.assertEqual(rev.revision, fulltext2)
706 self.assertEqual(rev.revision, fulltext2)
707 self.assertIsNone(rev.delta)
707 self.assertIsNone(rev.delta)
708
708
709 rev = next(gen)
709 rev = next(gen)
710 self.assertEqual(rev.node, node0)
710 self.assertEqual(rev.node, node0)
711 self.assertEqual(rev.p1node, nullid)
711 self.assertEqual(rev.p1node, nullid)
712 self.assertEqual(rev.p2node, nullid)
712 self.assertEqual(rev.p2node, nullid)
713 # Delta behavior is storage dependent, so we can't easily test it.
713 # Delta behavior is storage dependent, so we can't easily test it.
714
714
715 with self.assertRaises(StopIteration):
715 with self.assertRaises(StopIteration):
716 next(gen)
716 next(gen)
717
717
718 # assumehaveparentrevisions=False (the default) won't send a delta for
718 # assumehaveparentrevisions=False (the default) won't send a delta for
719 # the first revision.
719 # the first revision.
720 gen = f.emitrevisions({node2, node1}, revisiondata=True)
720 gen = f.emitrevisions({node2, node1}, revisiondata=True)
721
721
722 rev = next(gen)
722 rev = next(gen)
723 self.assertEqual(rev.node, node1)
723 self.assertEqual(rev.node, node1)
724 self.assertEqual(rev.p1node, node0)
724 self.assertEqual(rev.p1node, node0)
725 self.assertEqual(rev.p2node, nullid)
725 self.assertEqual(rev.p2node, nullid)
726 self.assertEqual(rev.basenode, nullid)
726 self.assertEqual(rev.basenode, nullid)
727 self.assertIsNone(rev.baserevisionsize)
727 self.assertIsNone(rev.baserevisionsize)
728 self.assertEqual(rev.revision, fulltext1)
728 self.assertEqual(rev.revision, fulltext1)
729 self.assertIsNone(rev.delta)
729 self.assertIsNone(rev.delta)
730
730
731 rev = next(gen)
731 rev = next(gen)
732 self.assertEqual(rev.node, node2)
732 self.assertEqual(rev.node, node2)
733 self.assertEqual(rev.p1node, node1)
733 self.assertEqual(rev.p1node, node1)
734 self.assertEqual(rev.p2node, nullid)
734 self.assertEqual(rev.p2node, nullid)
735 self.assertEqual(rev.basenode, node1)
735 self.assertEqual(rev.basenode, node1)
736 self.assertIsNone(rev.baserevisionsize)
736 self.assertIsNone(rev.baserevisionsize)
737 self.assertIsNone(rev.revision)
737 self.assertIsNone(rev.revision)
738 self.assertEqual(
738 self.assertEqual(
739 rev.delta,
739 rev.delta,
740 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' + fulltext2,
740 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' + fulltext2,
741 )
741 )
742
742
743 with self.assertRaises(StopIteration):
743 with self.assertRaises(StopIteration):
744 next(gen)
744 next(gen)
745
745
746 # assumehaveparentrevisions=True allows delta against initial revision.
746 # assumehaveparentrevisions=True allows delta against initial revision.
747 gen = f.emitrevisions(
747 gen = f.emitrevisions(
748 [node2, node1], revisiondata=True, assumehaveparentrevisions=True
748 [node2, node1], revisiondata=True, assumehaveparentrevisions=True
749 )
749 )
750
750
751 rev = next(gen)
751 rev = next(gen)
752 self.assertEqual(rev.node, node1)
752 self.assertEqual(rev.node, node1)
753 self.assertEqual(rev.p1node, node0)
753 self.assertEqual(rev.p1node, node0)
754 self.assertEqual(rev.p2node, nullid)
754 self.assertEqual(rev.p2node, nullid)
755 self.assertEqual(rev.basenode, node0)
755 self.assertEqual(rev.basenode, node0)
756 self.assertIsNone(rev.baserevisionsize)
756 self.assertIsNone(rev.baserevisionsize)
757 self.assertIsNone(rev.revision)
757 self.assertIsNone(rev.revision)
758 self.assertEqual(
758 self.assertEqual(
759 rev.delta,
759 rev.delta,
760 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' + fulltext1,
760 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' + fulltext1,
761 )
761 )
762
762
763 # forceprevious=True forces a delta against the previous revision.
763 # forceprevious=True forces a delta against the previous revision.
764 # Special case for initial revision.
764 # Special case for initial revision.
765 gen = f.emitrevisions(
765 gen = f.emitrevisions(
766 [node0], revisiondata=True, deltamode=repository.CG_DELTAMODE_PREV
766 [node0], revisiondata=True, deltamode=repository.CG_DELTAMODE_PREV
767 )
767 )
768
768
769 rev = next(gen)
769 rev = next(gen)
770 self.assertEqual(rev.node, node0)
770 self.assertEqual(rev.node, node0)
771 self.assertEqual(rev.p1node, nullid)
771 self.assertEqual(rev.p1node, nullid)
772 self.assertEqual(rev.p2node, nullid)
772 self.assertEqual(rev.p2node, nullid)
773 self.assertEqual(rev.basenode, nullid)
773 self.assertEqual(rev.basenode, nullid)
774 self.assertIsNone(rev.baserevisionsize)
774 self.assertIsNone(rev.baserevisionsize)
775 self.assertIsNone(rev.revision)
775 self.assertIsNone(rev.revision)
776 self.assertEqual(
776 self.assertEqual(
777 rev.delta,
777 rev.delta,
778 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00' + fulltext0,
778 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00' + fulltext0,
779 )
779 )
780
780
781 with self.assertRaises(StopIteration):
781 with self.assertRaises(StopIteration):
782 next(gen)
782 next(gen)
783
783
784 gen = f.emitrevisions(
784 gen = f.emitrevisions(
785 [node0, node2],
785 [node0, node2],
786 revisiondata=True,
786 revisiondata=True,
787 deltamode=repository.CG_DELTAMODE_PREV,
787 deltamode=repository.CG_DELTAMODE_PREV,
788 )
788 )
789
789
790 rev = next(gen)
790 rev = next(gen)
791 self.assertEqual(rev.node, node0)
791 self.assertEqual(rev.node, node0)
792 self.assertEqual(rev.p1node, nullid)
792 self.assertEqual(rev.p1node, nullid)
793 self.assertEqual(rev.p2node, nullid)
793 self.assertEqual(rev.p2node, nullid)
794 self.assertEqual(rev.basenode, nullid)
794 self.assertEqual(rev.basenode, nullid)
795 self.assertIsNone(rev.baserevisionsize)
795 self.assertIsNone(rev.baserevisionsize)
796 self.assertIsNone(rev.revision)
796 self.assertIsNone(rev.revision)
797 self.assertEqual(
797 self.assertEqual(
798 rev.delta,
798 rev.delta,
799 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00' + fulltext0,
799 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00' + fulltext0,
800 )
800 )
801
801
802 rev = next(gen)
802 rev = next(gen)
803 self.assertEqual(rev.node, node2)
803 self.assertEqual(rev.node, node2)
804 self.assertEqual(rev.p1node, node1)
804 self.assertEqual(rev.p1node, node1)
805 self.assertEqual(rev.p2node, nullid)
805 self.assertEqual(rev.p2node, nullid)
806 self.assertEqual(rev.basenode, node0)
806 self.assertEqual(rev.basenode, node0)
807
807
808 with self.assertRaises(StopIteration):
808 with self.assertRaises(StopIteration):
809 next(gen)
809 next(gen)
810
810
811 def testrenamed(self):
811 def testrenamed(self):
812 fulltext0 = b'foo'
812 fulltext0 = b'foo'
813 fulltext1 = b'bar'
813 fulltext1 = b'bar'
814 fulltext2 = b'baz'
814 fulltext2 = b'baz'
815
815
816 meta1 = {
816 meta1 = {
817 b'copy': b'source0',
817 b'copy': b'source0',
818 b'copyrev': b'a' * 40,
818 b'copyrev': b'a' * 40,
819 }
819 }
820
820
821 meta2 = {
821 meta2 = {
822 b'copy': b'source1',
822 b'copy': b'source1',
823 b'copyrev': b'b' * 40,
823 b'copyrev': b'b' * 40,
824 }
824 }
825
825
826 stored1 = b''.join(
826 stored1 = b''.join(
827 [
827 [
828 b'\x01\ncopy: source0\n',
828 b'\x01\ncopy: source0\n',
829 b'copyrev: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n\x01\n',
829 b'copyrev: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n\x01\n',
830 fulltext1,
830 fulltext1,
831 ]
831 ]
832 )
832 )
833
833
834 stored2 = b''.join(
834 stored2 = b''.join(
835 [
835 [
836 b'\x01\ncopy: source1\n',
836 b'\x01\ncopy: source1\n',
837 b'copyrev: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n\x01\n',
837 b'copyrev: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n\x01\n',
838 fulltext2,
838 fulltext2,
839 ]
839 ]
840 )
840 )
841
841
842 f = self._makefilefn()
842 f = self._makefilefn()
843 with self._maketransactionfn() as tr:
843 with self._maketransactionfn() as tr:
844 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
844 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
845 node1 = f.add(fulltext1, meta1, tr, 1, node0, nullid)
845 node1 = f.add(fulltext1, meta1, tr, 1, node0, nullid)
846 node2 = f.add(fulltext2, meta2, tr, 2, nullid, nullid)
846 node2 = f.add(fulltext2, meta2, tr, 2, nullid, nullid)
847
847
848 # Metadata header isn't recognized when parent isn't nullid.
848 # Metadata header isn't recognized when parent isn't nullid.
849 self.assertEqual(f.size(1), len(stored1))
849 self.assertEqual(f.size(1), len(stored1))
850 self.assertEqual(f.size(2), len(fulltext2))
850 self.assertEqual(f.size(2), len(fulltext2))
851
851
852 self.assertEqual(f.revision(node1), stored1)
852 self.assertEqual(f.revision(node1), stored1)
853 self.assertEqual(f.rawdata(node1), stored1)
853 self.assertEqual(f.rawdata(node1), stored1)
854 self.assertEqual(f.revision(node2), stored2)
854 self.assertEqual(f.revision(node2), stored2)
855 self.assertEqual(f.rawdata(node2), stored2)
855 self.assertEqual(f.rawdata(node2), stored2)
856
856
857 self.assertEqual(f.read(node1), fulltext1)
857 self.assertEqual(f.read(node1), fulltext1)
858 self.assertEqual(f.read(node2), fulltext2)
858 self.assertEqual(f.read(node2), fulltext2)
859
859
860 # Returns False when first parent is set.
860 # Returns False when first parent is set.
861 self.assertFalse(f.renamed(node1))
861 self.assertFalse(f.renamed(node1))
862 self.assertEqual(f.renamed(node2), (b'source1', b'\xbb' * 20))
862 self.assertEqual(f.renamed(node2), (b'source1', b'\xbb' * 20))
863
863
864 self.assertTrue(f.cmp(node1, fulltext1))
864 self.assertTrue(f.cmp(node1, fulltext1))
865 self.assertTrue(f.cmp(node1, stored1))
865 self.assertTrue(f.cmp(node1, stored1))
866 self.assertFalse(f.cmp(node2, fulltext2))
866 self.assertFalse(f.cmp(node2, fulltext2))
867 self.assertTrue(f.cmp(node2, stored2))
867 self.assertTrue(f.cmp(node2, stored2))
868
868
869 def testmetadataprefix(self):
869 def testmetadataprefix(self):
870 # Content with metadata prefix has extra prefix inserted in storage.
870 # Content with metadata prefix has extra prefix inserted in storage.
871 fulltext0 = b'\x01\nfoo'
871 fulltext0 = b'\x01\nfoo'
872 stored0 = b'\x01\n\x01\n\x01\nfoo'
872 stored0 = b'\x01\n\x01\n\x01\nfoo'
873
873
874 fulltext1 = b'\x01\nbar'
874 fulltext1 = b'\x01\nbar'
875 meta1 = {
875 meta1 = {
876 b'copy': b'source0',
876 b'copy': b'source0',
877 b'copyrev': b'b' * 40,
877 b'copyrev': b'b' * 40,
878 }
878 }
879 stored1 = b''.join(
879 stored1 = b''.join(
880 [
880 [
881 b'\x01\ncopy: source0\n',
881 b'\x01\ncopy: source0\n',
882 b'copyrev: %s\n' % (b'b' * 40),
882 b'copyrev: %s\n' % (b'b' * 40),
883 b'\x01\n\x01\nbar',
883 b'\x01\n\x01\nbar',
884 ]
884 ]
885 )
885 )
886
886
887 f = self._makefilefn()
887 f = self._makefilefn()
888 with self._maketransactionfn() as tr:
888 with self._maketransactionfn() as tr:
889 node0 = f.add(fulltext0, {}, tr, 0, nullid, nullid)
889 node0 = f.add(fulltext0, {}, tr, 0, nullid, nullid)
890 node1 = f.add(fulltext1, meta1, tr, 1, nullid, nullid)
890 node1 = f.add(fulltext1, meta1, tr, 1, nullid, nullid)
891
891
892 # TODO this is buggy.
892 # TODO this is buggy.
893 self.assertEqual(f.size(0), len(fulltext0) + 4)
893 self.assertEqual(f.size(0), len(fulltext0) + 4)
894
894
895 self.assertEqual(f.size(1), len(fulltext1))
895 self.assertEqual(f.size(1), len(fulltext1))
896
896
897 self.assertEqual(f.revision(node0), stored0)
897 self.assertEqual(f.revision(node0), stored0)
898 self.assertEqual(f.rawdata(node0), stored0)
898 self.assertEqual(f.rawdata(node0), stored0)
899
899
900 self.assertEqual(f.revision(node1), stored1)
900 self.assertEqual(f.revision(node1), stored1)
901 self.assertEqual(f.rawdata(node1), stored1)
901 self.assertEqual(f.rawdata(node1), stored1)
902
902
903 self.assertEqual(f.read(node0), fulltext0)
903 self.assertEqual(f.read(node0), fulltext0)
904 self.assertEqual(f.read(node1), fulltext1)
904 self.assertEqual(f.read(node1), fulltext1)
905
905
906 self.assertFalse(f.cmp(node0, fulltext0))
906 self.assertFalse(f.cmp(node0, fulltext0))
907 self.assertTrue(f.cmp(node0, stored0))
907 self.assertTrue(f.cmp(node0, stored0))
908
908
909 self.assertFalse(f.cmp(node1, fulltext1))
909 self.assertFalse(f.cmp(node1, fulltext1))
910 self.assertTrue(f.cmp(node1, stored0))
910 self.assertTrue(f.cmp(node1, stored0))
911
911
912 def testbadnoderead(self):
912 def testbadnoderead(self):
913 f = self._makefilefn()
913 f = self._makefilefn()
914
914
915 fulltext0 = b'foo\n' * 30
915 fulltext0 = b'foo\n' * 30
916 fulltext1 = fulltext0 + b'bar\n'
916 fulltext1 = fulltext0 + b'bar\n'
917
917
918 with self._maketransactionfn() as tr:
918 with self._maketransactionfn() as tr:
919 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
919 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
920 node1 = b'\xaa' * 20
920 node1 = b'\xaa' * 20
921
921
922 self._addrawrevisionfn(
922 self._addrawrevisionfn(
923 f, tr, node1, node0, nullid, 1, rawtext=fulltext1
923 f, tr, node1, node0, nullid, 1, rawtext=fulltext1
924 )
924 )
925
925
926 self.assertEqual(len(f), 2)
926 self.assertEqual(len(f), 2)
927 self.assertEqual(f.parents(node1), (node0, nullid))
927 self.assertEqual(f.parents(node1), (node0, nullid))
928
928
929 # revision() raises since it performs hash verification.
929 # revision() raises since it performs hash verification.
930 with self.assertRaises(error.StorageError):
930 with self.assertRaises(error.StorageError):
931 f.revision(node1)
931 f.revision(node1)
932
932
933 # rawdata() still verifies because there are no special storage
933 # rawdata() still verifies because there are no special storage
934 # settings.
934 # settings.
935 with self.assertRaises(error.StorageError):
935 with self.assertRaises(error.StorageError):
936 f.rawdata(node1)
936 f.rawdata(node1)
937
937
938 # read() behaves like revision().
938 # read() behaves like revision().
939 with self.assertRaises(error.StorageError):
939 with self.assertRaises(error.StorageError):
940 f.read(node1)
940 f.read(node1)
941
941
942 # We can't test renamed() here because some backends may not require
942 # We can't test renamed() here because some backends may not require
943 # reading/validating the fulltext to return rename metadata.
943 # reading/validating the fulltext to return rename metadata.
944
944
945 def testbadnoderevisionraw(self):
945 def testbadnoderevisionraw(self):
946 # Like above except we test rawdata() first to isolate
946 # Like above except we test rawdata() first to isolate
947 # revision caching behavior.
947 # revision caching behavior.
948 f = self._makefilefn()
948 f = self._makefilefn()
949
949
950 fulltext0 = b'foo\n' * 30
950 fulltext0 = b'foo\n' * 30
951 fulltext1 = fulltext0 + b'bar\n'
951 fulltext1 = fulltext0 + b'bar\n'
952
952
953 with self._maketransactionfn() as tr:
953 with self._maketransactionfn() as tr:
954 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
954 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
955 node1 = b'\xaa' * 20
955 node1 = b'\xaa' * 20
956
956
957 self._addrawrevisionfn(
957 self._addrawrevisionfn(
958 f, tr, node1, node0, nullid, 1, rawtext=fulltext1
958 f, tr, node1, node0, nullid, 1, rawtext=fulltext1
959 )
959 )
960
960
961 with self.assertRaises(error.StorageError):
961 with self.assertRaises(error.StorageError):
962 f.rawdata(node1)
962 f.rawdata(node1)
963
963
964 with self.assertRaises(error.StorageError):
964 with self.assertRaises(error.StorageError):
965 f.rawdata(node1)
965 f.rawdata(node1)
966
966
967 def testbadnoderevision(self):
967 def testbadnoderevision(self):
968 # Like above except we test read() first to isolate revision caching
968 # Like above except we test read() first to isolate revision caching
969 # behavior.
969 # behavior.
970 f = self._makefilefn()
970 f = self._makefilefn()
971
971
972 fulltext0 = b'foo\n' * 30
972 fulltext0 = b'foo\n' * 30
973 fulltext1 = fulltext0 + b'bar\n'
973 fulltext1 = fulltext0 + b'bar\n'
974
974
975 with self._maketransactionfn() as tr:
975 with self._maketransactionfn() as tr:
976 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
976 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
977 node1 = b'\xaa' * 20
977 node1 = b'\xaa' * 20
978
978
979 self._addrawrevisionfn(
979 self._addrawrevisionfn(
980 f, tr, node1, node0, nullid, 1, rawtext=fulltext1
980 f, tr, node1, node0, nullid, 1, rawtext=fulltext1
981 )
981 )
982
982
983 with self.assertRaises(error.StorageError):
983 with self.assertRaises(error.StorageError):
984 f.read(node1)
984 f.read(node1)
985
985
986 with self.assertRaises(error.StorageError):
986 with self.assertRaises(error.StorageError):
987 f.read(node1)
987 f.read(node1)
988
988
989 def testbadnodedelta(self):
989 def testbadnodedelta(self):
990 f = self._makefilefn()
990 f = self._makefilefn()
991
991
992 fulltext0 = b'foo\n' * 31
992 fulltext0 = b'foo\n' * 31
993 fulltext1 = fulltext0 + b'bar\n'
993 fulltext1 = fulltext0 + b'bar\n'
994 fulltext2 = fulltext1 + b'baz\n'
994 fulltext2 = fulltext1 + b'baz\n'
995
995
996 with self._maketransactionfn() as tr:
996 with self._maketransactionfn() as tr:
997 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
997 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
998 node1 = b'\xaa' * 20
998 node1 = b'\xaa' * 20
999
999
1000 self._addrawrevisionfn(
1000 self._addrawrevisionfn(
1001 f, tr, node1, node0, nullid, 1, rawtext=fulltext1
1001 f, tr, node1, node0, nullid, 1, rawtext=fulltext1
1002 )
1002 )
1003
1003
1004 with self.assertRaises(error.StorageError):
1004 with self.assertRaises(error.StorageError):
1005 f.read(node1)
1005 f.read(node1)
1006
1006
1007 node2 = storageutil.hashrevisionsha1(fulltext2, node1, nullid)
1007 node2 = storageutil.hashrevisionsha1(fulltext2, node1, nullid)
1008
1008
1009 with self._maketransactionfn() as tr:
1009 with self._maketransactionfn() as tr:
1010 delta = mdiff.textdiff(fulltext1, fulltext2)
1010 delta = mdiff.textdiff(fulltext1, fulltext2)
1011 self._addrawrevisionfn(
1011 self._addrawrevisionfn(
1012 f, tr, node2, node1, nullid, 2, delta=(1, delta)
1012 f, tr, node2, node1, nullid, 2, delta=(1, delta)
1013 )
1013 )
1014
1014
1015 self.assertEqual(len(f), 3)
1015 self.assertEqual(len(f), 3)
1016
1016
1017 # Assuming a delta is stored, we shouldn't need to validate node1 in
1017 # Assuming a delta is stored, we shouldn't need to validate node1 in
1018 # order to retrieve node2.
1018 # order to retrieve node2.
1019 self.assertEqual(f.read(node2), fulltext2)
1019 self.assertEqual(f.read(node2), fulltext2)
1020
1020
1021 def testcensored(self):
1021 def testcensored(self):
1022 f = self._makefilefn()
1022 f = self._makefilefn()
1023
1023
1024 stored1 = storageutil.packmeta(
1024 stored1 = storageutil.packmeta(
1025 {
1025 {
1026 b'censored': b'tombstone',
1026 b'censored': b'tombstone',
1027 },
1027 },
1028 b'',
1028 b'',
1029 )
1029 )
1030
1030
1031 with self._maketransactionfn() as tr:
1031 with self._maketransactionfn() as tr:
1032 node0 = f.add(b'foo', None, tr, 0, nullid, nullid)
1032 node0 = f.add(b'foo', None, tr, 0, nullid, nullid)
1033
1033
1034 # The node value doesn't matter since we can't verify it.
1034 # The node value doesn't matter since we can't verify it.
1035 node1 = b'\xbb' * 20
1035 node1 = b'\xbb' * 20
1036
1036
1037 self._addrawrevisionfn(
1037 self._addrawrevisionfn(
1038 f, tr, node1, node0, nullid, 1, stored1, censored=True
1038 f, tr, node1, node0, nullid, 1, stored1, censored=True
1039 )
1039 )
1040
1040
1041 self.assertTrue(f.iscensored(1))
1041 self.assertTrue(f.iscensored(1))
1042
1042
1043 with self.assertRaises(error.CensoredNodeError):
1043 with self.assertRaises(error.CensoredNodeError):
1044 f.revision(1)
1044 f.revision(1)
1045
1045
1046 with self.assertRaises(error.CensoredNodeError):
1046 with self.assertRaises(error.CensoredNodeError):
1047 f.rawdata(1)
1047 f.rawdata(1)
1048
1048
1049 with self.assertRaises(error.CensoredNodeError):
1049 with self.assertRaises(error.CensoredNodeError):
1050 f.read(1)
1050 f.read(1)
1051
1051
1052 def testcensoredrawrevision(self):
1052 def testcensoredrawrevision(self):
1053 # Like above, except we do the rawdata() request first to
1053 # Like above, except we do the rawdata() request first to
1054 # isolate revision caching behavior.
1054 # isolate revision caching behavior.
1055
1055
1056 f = self._makefilefn()
1056 f = self._makefilefn()
1057
1057
1058 stored1 = storageutil.packmeta(
1058 stored1 = storageutil.packmeta(
1059 {
1059 {
1060 b'censored': b'tombstone',
1060 b'censored': b'tombstone',
1061 },
1061 },
1062 b'',
1062 b'',
1063 )
1063 )
1064
1064
1065 with self._maketransactionfn() as tr:
1065 with self._maketransactionfn() as tr:
1066 node0 = f.add(b'foo', None, tr, 0, nullid, nullid)
1066 node0 = f.add(b'foo', None, tr, 0, nullid, nullid)
1067
1067
1068 # The node value doesn't matter since we can't verify it.
1068 # The node value doesn't matter since we can't verify it.
1069 node1 = b'\xbb' * 20
1069 node1 = b'\xbb' * 20
1070
1070
1071 self._addrawrevisionfn(
1071 self._addrawrevisionfn(
1072 f, tr, node1, node0, nullid, 1, stored1, censored=True
1072 f, tr, node1, node0, nullid, 1, stored1, censored=True
1073 )
1073 )
1074
1074
1075 with self.assertRaises(error.CensoredNodeError):
1075 with self.assertRaises(error.CensoredNodeError):
1076 f.rawdata(1)
1076 f.rawdata(1)
1077
1077
1078
1078
1079 class ifilemutationtests(basetestcase):
1079 class ifilemutationtests(basetestcase):
1080 """Generic tests for the ifilemutation interface.
1080 """Generic tests for the ifilemutation interface.
1081
1081
1082 All file storage backends that support writing should conform to this
1082 All file storage backends that support writing should conform to this
1083 interface.
1083 interface.
1084
1084
1085 Use ``makeifilemutationtests()`` to create an instance of this type.
1085 Use ``makeifilemutationtests()`` to create an instance of this type.
1086 """
1086 """
1087
1087
1088 def testaddnoop(self):
1088 def testaddnoop(self):
1089 f = self._makefilefn()
1089 f = self._makefilefn()
1090 with self._maketransactionfn() as tr:
1090 with self._maketransactionfn() as tr:
1091 node0 = f.add(b'foo', None, tr, 0, nullid, nullid)
1091 node0 = f.add(b'foo', None, tr, 0, nullid, nullid)
1092 node1 = f.add(b'foo', None, tr, 0, nullid, nullid)
1092 node1 = f.add(b'foo', None, tr, 0, nullid, nullid)
1093 # Varying by linkrev shouldn't impact hash.
1093 # Varying by linkrev shouldn't impact hash.
1094 node2 = f.add(b'foo', None, tr, 1, nullid, nullid)
1094 node2 = f.add(b'foo', None, tr, 1, nullid, nullid)
1095
1095
1096 self.assertEqual(node1, node0)
1096 self.assertEqual(node1, node0)
1097 self.assertEqual(node2, node0)
1097 self.assertEqual(node2, node0)
1098 self.assertEqual(len(f), 1)
1098 self.assertEqual(len(f), 1)
1099
1099
1100 def testaddrevisionbadnode(self):
1100 def testaddrevisionbadnode(self):
1101 f = self._makefilefn()
1101 f = self._makefilefn()
1102 with self._maketransactionfn() as tr:
1102 with self._maketransactionfn() as tr:
1103 # Adding a revision with bad node value fails.
1103 # Adding a revision with bad node value fails.
1104 with self.assertRaises(error.StorageError):
1104 with self.assertRaises(error.StorageError):
1105 f.addrevision(b'foo', tr, 0, nullid, nullid, node=b'\x01' * 20)
1105 f.addrevision(b'foo', tr, 0, nullid, nullid, node=b'\x01' * 20)
1106
1106
1107 def testaddrevisionunknownflag(self):
1107 def testaddrevisionunknownflag(self):
1108 f = self._makefilefn()
1108 f = self._makefilefn()
1109 with self._maketransactionfn() as tr:
1109 with self._maketransactionfn() as tr:
1110 for i in range(15, 0, -1):
1110 for i in range(15, 0, -1):
1111 if (1 << i) & ~repository.REVISION_FLAGS_KNOWN:
1111 if (1 << i) & ~repository.REVISION_FLAGS_KNOWN:
1112 flags = 1 << i
1112 flags = 1 << i
1113 break
1113 break
1114
1114
1115 with self.assertRaises(error.StorageError):
1115 with self.assertRaises(error.StorageError):
1116 f.addrevision(b'foo', tr, 0, nullid, nullid, flags=flags)
1116 f.addrevision(b'foo', tr, 0, nullid, nullid, flags=flags)
1117
1117
1118 def testaddgroupsimple(self):
1118 def testaddgroupsimple(self):
1119 f = self._makefilefn()
1119 f = self._makefilefn()
1120
1120
1121 callbackargs = []
1121 callbackargs = []
1122
1122
1123 def cb(*args, **kwargs):
1123 def cb(*args, **kwargs):
1124 callbackargs.append((args, kwargs))
1124 callbackargs.append((args, kwargs))
1125
1125
1126 def linkmapper(node):
1126 def linkmapper(node):
1127 return 0
1127 return 0
1128
1128
1129 with self._maketransactionfn() as tr:
1129 with self._maketransactionfn() as tr:
1130 nodes = []
1130 nodes = []
1131
1131
1132 def onchangeset(cl, node):
1132 def onchangeset(cl, rev):
1133 node = cl.node(rev)
1133 nodes.append(node)
1134 nodes.append(node)
1134 cb(cl, node)
1135 cb(cl, node)
1135
1136
1136 def ondupchangeset(cl, node):
1137 def ondupchangeset(cl, rev):
1137 nodes.append(node)
1138 nodes.append(cl.node(rev))
1138
1139
1139 f.addgroup(
1140 f.addgroup(
1140 [],
1141 [],
1141 None,
1142 None,
1142 tr,
1143 tr,
1143 addrevisioncb=onchangeset,
1144 addrevisioncb=onchangeset,
1144 duplicaterevisioncb=ondupchangeset,
1145 duplicaterevisioncb=ondupchangeset,
1145 )
1146 )
1146
1147
1147 self.assertEqual(nodes, [])
1148 self.assertEqual(nodes, [])
1148 self.assertEqual(callbackargs, [])
1149 self.assertEqual(callbackargs, [])
1149 self.assertEqual(len(f), 0)
1150 self.assertEqual(len(f), 0)
1150
1151
1151 fulltext0 = b'foo'
1152 fulltext0 = b'foo'
1152 delta0 = mdiff.trivialdiffheader(len(fulltext0)) + fulltext0
1153 delta0 = mdiff.trivialdiffheader(len(fulltext0)) + fulltext0
1153
1154
1154 with self._maketransactionfn() as tr:
1155 with self._maketransactionfn() as tr:
1155 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
1156 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
1156
1157
1157 f = self._makefilefn()
1158 f = self._makefilefn()
1158
1159
1159 deltas = [
1160 deltas = [
1160 (node0, nullid, nullid, nullid, nullid, delta0, 0),
1161 (node0, nullid, nullid, nullid, nullid, delta0, 0),
1161 ]
1162 ]
1162
1163
1163 with self._maketransactionfn() as tr:
1164 with self._maketransactionfn() as tr:
1164 nodes = []
1165 nodes = []
1165
1166
1166 def onchangeset(cl, node):
1167 def onchangeset(cl, rev):
1168 node = cl.node(rev)
1167 nodes.append(node)
1169 nodes.append(node)
1168 cb(cl, node)
1170 cb(cl, node)
1169
1171
1170 def ondupchangeset(cl, node):
1172 def ondupchangeset(cl, rev):
1171 nodes.append(node)
1173 nodes.append(cl.node(rev))
1172
1174
1173 f.addgroup(
1175 f.addgroup(
1174 deltas,
1176 deltas,
1175 linkmapper,
1177 linkmapper,
1176 tr,
1178 tr,
1177 addrevisioncb=onchangeset,
1179 addrevisioncb=onchangeset,
1178 duplicaterevisioncb=ondupchangeset,
1180 duplicaterevisioncb=ondupchangeset,
1179 )
1181 )
1180
1182
1181 self.assertEqual(
1183 self.assertEqual(
1182 nodes,
1184 nodes,
1183 [
1185 [
1184 b'\x49\xd8\xcb\xb1\x5c\xe2\x57\x92\x04\x47'
1186 b'\x49\xd8\xcb\xb1\x5c\xe2\x57\x92\x04\x47'
1185 b'\x00\x6b\x46\x97\x8b\x7a\xf9\x80\xa9\x79'
1187 b'\x00\x6b\x46\x97\x8b\x7a\xf9\x80\xa9\x79'
1186 ],
1188 ],
1187 )
1189 )
1188
1190
1189 self.assertEqual(len(callbackargs), 1)
1191 self.assertEqual(len(callbackargs), 1)
1190 self.assertEqual(callbackargs[0][0][1], nodes[0])
1192 self.assertEqual(callbackargs[0][0][1], nodes[0])
1191
1193
1192 self.assertEqual(list(f.revs()), [0])
1194 self.assertEqual(list(f.revs()), [0])
1193 self.assertEqual(f.rev(nodes[0]), 0)
1195 self.assertEqual(f.rev(nodes[0]), 0)
1194 self.assertEqual(f.node(0), nodes[0])
1196 self.assertEqual(f.node(0), nodes[0])
1195
1197
1196 def testaddgroupmultiple(self):
1198 def testaddgroupmultiple(self):
1197 f = self._makefilefn()
1199 f = self._makefilefn()
1198
1200
1199 fulltexts = [
1201 fulltexts = [
1200 b'foo',
1202 b'foo',
1201 b'bar',
1203 b'bar',
1202 b'x' * 1024,
1204 b'x' * 1024,
1203 ]
1205 ]
1204
1206
1205 nodes = []
1207 nodes = []
1206 with self._maketransactionfn() as tr:
1208 with self._maketransactionfn() as tr:
1207 for fulltext in fulltexts:
1209 for fulltext in fulltexts:
1208 nodes.append(f.add(fulltext, None, tr, 0, nullid, nullid))
1210 nodes.append(f.add(fulltext, None, tr, 0, nullid, nullid))
1209
1211
1210 f = self._makefilefn()
1212 f = self._makefilefn()
1211 deltas = []
1213 deltas = []
1212 for i, fulltext in enumerate(fulltexts):
1214 for i, fulltext in enumerate(fulltexts):
1213 delta = mdiff.trivialdiffheader(len(fulltext)) + fulltext
1215 delta = mdiff.trivialdiffheader(len(fulltext)) + fulltext
1214
1216
1215 deltas.append((nodes[i], nullid, nullid, nullid, nullid, delta, 0))
1217 deltas.append((nodes[i], nullid, nullid, nullid, nullid, delta, 0))
1216
1218
1217 with self._maketransactionfn() as tr:
1219 with self._maketransactionfn() as tr:
1218 newnodes = []
1220 newnodes = []
1219
1221
1220 def onchangeset(cl, node):
1222 def onchangeset(cl, rev):
1221 newnodes.append(node)
1223 newnodes.append(cl.node(rev))
1222
1224
1223 f.addgroup(
1225 f.addgroup(
1224 deltas,
1226 deltas,
1225 lambda x: 0,
1227 lambda x: 0,
1226 tr,
1228 tr,
1227 addrevisioncb=onchangeset,
1229 addrevisioncb=onchangeset,
1228 duplicaterevisioncb=onchangeset,
1230 duplicaterevisioncb=onchangeset,
1229 )
1231 )
1230 self.assertEqual(newnodes, nodes)
1232 self.assertEqual(newnodes, nodes)
1231
1233
1232 self.assertEqual(len(f), len(deltas))
1234 self.assertEqual(len(f), len(deltas))
1233 self.assertEqual(list(f.revs()), [0, 1, 2])
1235 self.assertEqual(list(f.revs()), [0, 1, 2])
1234 self.assertEqual(f.rev(nodes[0]), 0)
1236 self.assertEqual(f.rev(nodes[0]), 0)
1235 self.assertEqual(f.rev(nodes[1]), 1)
1237 self.assertEqual(f.rev(nodes[1]), 1)
1236 self.assertEqual(f.rev(nodes[2]), 2)
1238 self.assertEqual(f.rev(nodes[2]), 2)
1237 self.assertEqual(f.node(0), nodes[0])
1239 self.assertEqual(f.node(0), nodes[0])
1238 self.assertEqual(f.node(1), nodes[1])
1240 self.assertEqual(f.node(1), nodes[1])
1239 self.assertEqual(f.node(2), nodes[2])
1241 self.assertEqual(f.node(2), nodes[2])
1240
1242
1241 def testdeltaagainstcensored(self):
1243 def testdeltaagainstcensored(self):
1242 # Attempt to apply a delta made against a censored revision.
1244 # Attempt to apply a delta made against a censored revision.
1243 f = self._makefilefn()
1245 f = self._makefilefn()
1244
1246
1245 stored1 = storageutil.packmeta(
1247 stored1 = storageutil.packmeta(
1246 {
1248 {
1247 b'censored': b'tombstone',
1249 b'censored': b'tombstone',
1248 },
1250 },
1249 b'',
1251 b'',
1250 )
1252 )
1251
1253
1252 with self._maketransactionfn() as tr:
1254 with self._maketransactionfn() as tr:
1253 node0 = f.add(b'foo\n' * 30, None, tr, 0, nullid, nullid)
1255 node0 = f.add(b'foo\n' * 30, None, tr, 0, nullid, nullid)
1254
1256
1255 # The node value doesn't matter since we can't verify it.
1257 # The node value doesn't matter since we can't verify it.
1256 node1 = b'\xbb' * 20
1258 node1 = b'\xbb' * 20
1257
1259
1258 self._addrawrevisionfn(
1260 self._addrawrevisionfn(
1259 f, tr, node1, node0, nullid, 1, stored1, censored=True
1261 f, tr, node1, node0, nullid, 1, stored1, censored=True
1260 )
1262 )
1261
1263
1262 delta = mdiff.textdiff(b'bar\n' * 30, (b'bar\n' * 30) + b'baz\n')
1264 delta = mdiff.textdiff(b'bar\n' * 30, (b'bar\n' * 30) + b'baz\n')
1263 deltas = [(b'\xcc' * 20, node1, nullid, b'\x01' * 20, node1, delta, 0)]
1265 deltas = [(b'\xcc' * 20, node1, nullid, b'\x01' * 20, node1, delta, 0)]
1264
1266
1265 with self._maketransactionfn() as tr:
1267 with self._maketransactionfn() as tr:
1266 with self.assertRaises(error.CensoredBaseError):
1268 with self.assertRaises(error.CensoredBaseError):
1267 f.addgroup(deltas, lambda x: 0, tr)
1269 f.addgroup(deltas, lambda x: 0, tr)
1268
1270
1269 def testcensorrevisionbasic(self):
1271 def testcensorrevisionbasic(self):
1270 f = self._makefilefn()
1272 f = self._makefilefn()
1271
1273
1272 with self._maketransactionfn() as tr:
1274 with self._maketransactionfn() as tr:
1273 node0 = f.add(b'foo\n' * 30, None, tr, 0, nullid, nullid)
1275 node0 = f.add(b'foo\n' * 30, None, tr, 0, nullid, nullid)
1274 node1 = f.add(b'foo\n' * 31, None, tr, 1, node0, nullid)
1276 node1 = f.add(b'foo\n' * 31, None, tr, 1, node0, nullid)
1275 node2 = f.add(b'foo\n' * 32, None, tr, 2, node1, nullid)
1277 node2 = f.add(b'foo\n' * 32, None, tr, 2, node1, nullid)
1276
1278
1277 with self._maketransactionfn() as tr:
1279 with self._maketransactionfn() as tr:
1278 f.censorrevision(tr, node1)
1280 f.censorrevision(tr, node1)
1279
1281
1280 self.assertEqual(len(f), 3)
1282 self.assertEqual(len(f), 3)
1281 self.assertEqual(list(f.revs()), [0, 1, 2])
1283 self.assertEqual(list(f.revs()), [0, 1, 2])
1282
1284
1283 self.assertEqual(f.read(node0), b'foo\n' * 30)
1285 self.assertEqual(f.read(node0), b'foo\n' * 30)
1284 self.assertEqual(f.read(node2), b'foo\n' * 32)
1286 self.assertEqual(f.read(node2), b'foo\n' * 32)
1285
1287
1286 with self.assertRaises(error.CensoredNodeError):
1288 with self.assertRaises(error.CensoredNodeError):
1287 f.read(node1)
1289 f.read(node1)
1288
1290
1289 def testgetstrippointnoparents(self):
1291 def testgetstrippointnoparents(self):
1290 # N revisions where none have parents.
1292 # N revisions where none have parents.
1291 f = self._makefilefn()
1293 f = self._makefilefn()
1292
1294
1293 with self._maketransactionfn() as tr:
1295 with self._maketransactionfn() as tr:
1294 for rev in range(10):
1296 for rev in range(10):
1295 f.add(b'%d' % rev, None, tr, rev, nullid, nullid)
1297 f.add(b'%d' % rev, None, tr, rev, nullid, nullid)
1296
1298
1297 for rev in range(10):
1299 for rev in range(10):
1298 self.assertEqual(f.getstrippoint(rev), (rev, set()))
1300 self.assertEqual(f.getstrippoint(rev), (rev, set()))
1299
1301
1300 def testgetstrippointlinear(self):
1302 def testgetstrippointlinear(self):
1301 # N revisions in a linear chain.
1303 # N revisions in a linear chain.
1302 f = self._makefilefn()
1304 f = self._makefilefn()
1303
1305
1304 with self._maketransactionfn() as tr:
1306 with self._maketransactionfn() as tr:
1305 p1 = nullid
1307 p1 = nullid
1306
1308
1307 for rev in range(10):
1309 for rev in range(10):
1308 f.add(b'%d' % rev, None, tr, rev, p1, nullid)
1310 f.add(b'%d' % rev, None, tr, rev, p1, nullid)
1309
1311
1310 for rev in range(10):
1312 for rev in range(10):
1311 self.assertEqual(f.getstrippoint(rev), (rev, set()))
1313 self.assertEqual(f.getstrippoint(rev), (rev, set()))
1312
1314
1313 def testgetstrippointmultipleheads(self):
1315 def testgetstrippointmultipleheads(self):
1314 f = self._makefilefn()
1316 f = self._makefilefn()
1315
1317
1316 with self._maketransactionfn() as tr:
1318 with self._maketransactionfn() as tr:
1317 node0 = f.add(b'0', None, tr, 0, nullid, nullid)
1319 node0 = f.add(b'0', None, tr, 0, nullid, nullid)
1318 node1 = f.add(b'1', None, tr, 1, node0, nullid)
1320 node1 = f.add(b'1', None, tr, 1, node0, nullid)
1319 f.add(b'2', None, tr, 2, node1, nullid)
1321 f.add(b'2', None, tr, 2, node1, nullid)
1320 f.add(b'3', None, tr, 3, node0, nullid)
1322 f.add(b'3', None, tr, 3, node0, nullid)
1321 f.add(b'4', None, tr, 4, node0, nullid)
1323 f.add(b'4', None, tr, 4, node0, nullid)
1322
1324
1323 for rev in range(5):
1325 for rev in range(5):
1324 self.assertEqual(f.getstrippoint(rev), (rev, set()))
1326 self.assertEqual(f.getstrippoint(rev), (rev, set()))
1325
1327
1326 def testgetstrippointearlierlinkrevs(self):
1328 def testgetstrippointearlierlinkrevs(self):
1327 f = self._makefilefn()
1329 f = self._makefilefn()
1328
1330
1329 with self._maketransactionfn() as tr:
1331 with self._maketransactionfn() as tr:
1330 node0 = f.add(b'0', None, tr, 0, nullid, nullid)
1332 node0 = f.add(b'0', None, tr, 0, nullid, nullid)
1331 f.add(b'1', None, tr, 10, node0, nullid)
1333 f.add(b'1', None, tr, 10, node0, nullid)
1332 f.add(b'2', None, tr, 5, node0, nullid)
1334 f.add(b'2', None, tr, 5, node0, nullid)
1333
1335
1334 self.assertEqual(f.getstrippoint(0), (0, set()))
1336 self.assertEqual(f.getstrippoint(0), (0, set()))
1335 self.assertEqual(f.getstrippoint(1), (1, set()))
1337 self.assertEqual(f.getstrippoint(1), (1, set()))
1336 self.assertEqual(f.getstrippoint(2), (1, set()))
1338 self.assertEqual(f.getstrippoint(2), (1, set()))
1337 self.assertEqual(f.getstrippoint(3), (1, set()))
1339 self.assertEqual(f.getstrippoint(3), (1, set()))
1338 self.assertEqual(f.getstrippoint(4), (1, set()))
1340 self.assertEqual(f.getstrippoint(4), (1, set()))
1339 self.assertEqual(f.getstrippoint(5), (1, set()))
1341 self.assertEqual(f.getstrippoint(5), (1, set()))
1340 self.assertEqual(f.getstrippoint(6), (1, {2}))
1342 self.assertEqual(f.getstrippoint(6), (1, {2}))
1341 self.assertEqual(f.getstrippoint(7), (1, {2}))
1343 self.assertEqual(f.getstrippoint(7), (1, {2}))
1342 self.assertEqual(f.getstrippoint(8), (1, {2}))
1344 self.assertEqual(f.getstrippoint(8), (1, {2}))
1343 self.assertEqual(f.getstrippoint(9), (1, {2}))
1345 self.assertEqual(f.getstrippoint(9), (1, {2}))
1344 self.assertEqual(f.getstrippoint(10), (1, {2}))
1346 self.assertEqual(f.getstrippoint(10), (1, {2}))
1345 self.assertEqual(f.getstrippoint(11), (3, set()))
1347 self.assertEqual(f.getstrippoint(11), (3, set()))
1346
1348
1347 def teststripempty(self):
1349 def teststripempty(self):
1348 f = self._makefilefn()
1350 f = self._makefilefn()
1349
1351
1350 with self._maketransactionfn() as tr:
1352 with self._maketransactionfn() as tr:
1351 f.strip(0, tr)
1353 f.strip(0, tr)
1352
1354
1353 self.assertEqual(len(f), 0)
1355 self.assertEqual(len(f), 0)
1354
1356
1355 def teststripall(self):
1357 def teststripall(self):
1356 f = self._makefilefn()
1358 f = self._makefilefn()
1357
1359
1358 with self._maketransactionfn() as tr:
1360 with self._maketransactionfn() as tr:
1359 p1 = nullid
1361 p1 = nullid
1360 for rev in range(10):
1362 for rev in range(10):
1361 p1 = f.add(b'%d' % rev, None, tr, rev, p1, nullid)
1363 p1 = f.add(b'%d' % rev, None, tr, rev, p1, nullid)
1362
1364
1363 self.assertEqual(len(f), 10)
1365 self.assertEqual(len(f), 10)
1364
1366
1365 with self._maketransactionfn() as tr:
1367 with self._maketransactionfn() as tr:
1366 f.strip(0, tr)
1368 f.strip(0, tr)
1367
1369
1368 self.assertEqual(len(f), 0)
1370 self.assertEqual(len(f), 0)
1369
1371
1370 def teststrippartial(self):
1372 def teststrippartial(self):
1371 f = self._makefilefn()
1373 f = self._makefilefn()
1372
1374
1373 with self._maketransactionfn() as tr:
1375 with self._maketransactionfn() as tr:
1374 f.add(b'0', None, tr, 0, nullid, nullid)
1376 f.add(b'0', None, tr, 0, nullid, nullid)
1375 node1 = f.add(b'1', None, tr, 5, nullid, nullid)
1377 node1 = f.add(b'1', None, tr, 5, nullid, nullid)
1376 node2 = f.add(b'2', None, tr, 10, nullid, nullid)
1378 node2 = f.add(b'2', None, tr, 10, nullid, nullid)
1377
1379
1378 self.assertEqual(len(f), 3)
1380 self.assertEqual(len(f), 3)
1379
1381
1380 with self._maketransactionfn() as tr:
1382 with self._maketransactionfn() as tr:
1381 f.strip(11, tr)
1383 f.strip(11, tr)
1382
1384
1383 self.assertEqual(len(f), 3)
1385 self.assertEqual(len(f), 3)
1384
1386
1385 with self._maketransactionfn() as tr:
1387 with self._maketransactionfn() as tr:
1386 f.strip(10, tr)
1388 f.strip(10, tr)
1387
1389
1388 self.assertEqual(len(f), 2)
1390 self.assertEqual(len(f), 2)
1389
1391
1390 with self.assertRaises(error.LookupError):
1392 with self.assertRaises(error.LookupError):
1391 f.rev(node2)
1393 f.rev(node2)
1392
1394
1393 with self._maketransactionfn() as tr:
1395 with self._maketransactionfn() as tr:
1394 f.strip(6, tr)
1396 f.strip(6, tr)
1395
1397
1396 self.assertEqual(len(f), 2)
1398 self.assertEqual(len(f), 2)
1397
1399
1398 with self._maketransactionfn() as tr:
1400 with self._maketransactionfn() as tr:
1399 f.strip(3, tr)
1401 f.strip(3, tr)
1400
1402
1401 self.assertEqual(len(f), 1)
1403 self.assertEqual(len(f), 1)
1402
1404
1403 with self.assertRaises(error.LookupError):
1405 with self.assertRaises(error.LookupError):
1404 f.rev(node1)
1406 f.rev(node1)
1405
1407
1406
1408
1407 def makeifileindextests(makefilefn, maketransactionfn, addrawrevisionfn):
1409 def makeifileindextests(makefilefn, maketransactionfn, addrawrevisionfn):
1408 """Create a unittest.TestCase class suitable for testing file storage.
1410 """Create a unittest.TestCase class suitable for testing file storage.
1409
1411
1410 ``makefilefn`` is a callable which receives the test case as an
1412 ``makefilefn`` is a callable which receives the test case as an
1411 argument and returns an object implementing the ``ifilestorage`` interface.
1413 argument and returns an object implementing the ``ifilestorage`` interface.
1412
1414
1413 ``maketransactionfn`` is a callable which receives the test case as an
1415 ``maketransactionfn`` is a callable which receives the test case as an
1414 argument and returns a transaction object.
1416 argument and returns a transaction object.
1415
1417
1416 ``addrawrevisionfn`` is a callable which receives arguments describing a
1418 ``addrawrevisionfn`` is a callable which receives arguments describing a
1417 low-level revision to add. This callable allows the insertion of
1419 low-level revision to add. This callable allows the insertion of
1418 potentially bad data into the store in order to facilitate testing.
1420 potentially bad data into the store in order to facilitate testing.
1419
1421
1420 Returns a type that is a ``unittest.TestCase`` that can be used for
1422 Returns a type that is a ``unittest.TestCase`` that can be used for
1421 testing the object implementing the file storage interface. Simply
1423 testing the object implementing the file storage interface. Simply
1422 assign the returned value to a module-level attribute and a test loader
1424 assign the returned value to a module-level attribute and a test loader
1423 should find and run it automatically.
1425 should find and run it automatically.
1424 """
1426 """
1425 d = {
1427 d = {
1426 '_makefilefn': makefilefn,
1428 '_makefilefn': makefilefn,
1427 '_maketransactionfn': maketransactionfn,
1429 '_maketransactionfn': maketransactionfn,
1428 '_addrawrevisionfn': addrawrevisionfn,
1430 '_addrawrevisionfn': addrawrevisionfn,
1429 }
1431 }
1430 return type('ifileindextests', (ifileindextests,), d)
1432 return type('ifileindextests', (ifileindextests,), d)
1431
1433
1432
1434
1433 def makeifiledatatests(makefilefn, maketransactionfn, addrawrevisionfn):
1435 def makeifiledatatests(makefilefn, maketransactionfn, addrawrevisionfn):
1434 d = {
1436 d = {
1435 '_makefilefn': makefilefn,
1437 '_makefilefn': makefilefn,
1436 '_maketransactionfn': maketransactionfn,
1438 '_maketransactionfn': maketransactionfn,
1437 '_addrawrevisionfn': addrawrevisionfn,
1439 '_addrawrevisionfn': addrawrevisionfn,
1438 }
1440 }
1439 return type('ifiledatatests', (ifiledatatests,), d)
1441 return type('ifiledatatests', (ifiledatatests,), d)
1440
1442
1441
1443
1442 def makeifilemutationtests(makefilefn, maketransactionfn, addrawrevisionfn):
1444 def makeifilemutationtests(makefilefn, maketransactionfn, addrawrevisionfn):
1443 d = {
1445 d = {
1444 '_makefilefn': makefilefn,
1446 '_makefilefn': makefilefn,
1445 '_maketransactionfn': maketransactionfn,
1447 '_maketransactionfn': maketransactionfn,
1446 '_addrawrevisionfn': addrawrevisionfn,
1448 '_addrawrevisionfn': addrawrevisionfn,
1447 }
1449 }
1448 return type('ifilemutationtests', (ifilemutationtests,), d)
1450 return type('ifilemutationtests', (ifilemutationtests,), d)
@@ -1,738 +1,738 b''
1 # simplestorerepo.py - Extension that swaps in alternate repository storage.
1 # simplestorerepo.py - Extension that swaps in alternate repository storage.
2 #
2 #
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.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 # To use this with the test suite:
8 # To use this with the test suite:
9 #
9 #
10 # $ HGREPOFEATURES="simplestore" ./run-tests.py \
10 # $ HGREPOFEATURES="simplestore" ./run-tests.py \
11 # --extra-config-opt extensions.simplestore=`pwd`/simplestorerepo.py
11 # --extra-config-opt extensions.simplestore=`pwd`/simplestorerepo.py
12
12
13 from __future__ import absolute_import
13 from __future__ import absolute_import
14
14
15 import stat
15 import stat
16
16
17 from mercurial.i18n import _
17 from mercurial.i18n import _
18 from mercurial.node import (
18 from mercurial.node import (
19 bin,
19 bin,
20 hex,
20 hex,
21 nullid,
21 nullid,
22 nullrev,
22 nullrev,
23 )
23 )
24 from mercurial.thirdparty import attr
24 from mercurial.thirdparty import attr
25 from mercurial import (
25 from mercurial import (
26 ancestor,
26 ancestor,
27 bundlerepo,
27 bundlerepo,
28 error,
28 error,
29 extensions,
29 extensions,
30 localrepo,
30 localrepo,
31 mdiff,
31 mdiff,
32 pycompat,
32 pycompat,
33 revlog,
33 revlog,
34 store,
34 store,
35 verify,
35 verify,
36 )
36 )
37 from mercurial.interfaces import (
37 from mercurial.interfaces import (
38 repository,
38 repository,
39 util as interfaceutil,
39 util as interfaceutil,
40 )
40 )
41 from mercurial.utils import (
41 from mercurial.utils import (
42 cborutil,
42 cborutil,
43 storageutil,
43 storageutil,
44 )
44 )
45 from mercurial.revlogutils import flagutil
45 from mercurial.revlogutils import flagutil
46
46
47 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
47 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
48 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
48 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
49 # be specifying the version(s) of Mercurial they are tested with, or
49 # be specifying the version(s) of Mercurial they are tested with, or
50 # leave the attribute unspecified.
50 # leave the attribute unspecified.
51 testedwith = b'ships-with-hg-core'
51 testedwith = b'ships-with-hg-core'
52
52
53 REQUIREMENT = b'testonly-simplestore'
53 REQUIREMENT = b'testonly-simplestore'
54
54
55
55
56 def validatenode(node):
56 def validatenode(node):
57 if isinstance(node, int):
57 if isinstance(node, int):
58 raise ValueError('expected node; got int')
58 raise ValueError('expected node; got int')
59
59
60 if len(node) != 20:
60 if len(node) != 20:
61 raise ValueError('expected 20 byte node')
61 raise ValueError('expected 20 byte node')
62
62
63
63
64 def validaterev(rev):
64 def validaterev(rev):
65 if not isinstance(rev, int):
65 if not isinstance(rev, int):
66 raise ValueError('expected int')
66 raise ValueError('expected int')
67
67
68
68
69 class simplestoreerror(error.StorageError):
69 class simplestoreerror(error.StorageError):
70 pass
70 pass
71
71
72
72
73 @interfaceutil.implementer(repository.irevisiondelta)
73 @interfaceutil.implementer(repository.irevisiondelta)
74 @attr.s(slots=True)
74 @attr.s(slots=True)
75 class simplestorerevisiondelta(object):
75 class simplestorerevisiondelta(object):
76 node = attr.ib()
76 node = attr.ib()
77 p1node = attr.ib()
77 p1node = attr.ib()
78 p2node = attr.ib()
78 p2node = attr.ib()
79 basenode = attr.ib()
79 basenode = attr.ib()
80 flags = attr.ib()
80 flags = attr.ib()
81 baserevisionsize = attr.ib()
81 baserevisionsize = attr.ib()
82 revision = attr.ib()
82 revision = attr.ib()
83 delta = attr.ib()
83 delta = attr.ib()
84 linknode = attr.ib(default=None)
84 linknode = attr.ib(default=None)
85
85
86
86
87 @interfaceutil.implementer(repository.iverifyproblem)
87 @interfaceutil.implementer(repository.iverifyproblem)
88 @attr.s(frozen=True)
88 @attr.s(frozen=True)
89 class simplefilestoreproblem(object):
89 class simplefilestoreproblem(object):
90 warning = attr.ib(default=None)
90 warning = attr.ib(default=None)
91 error = attr.ib(default=None)
91 error = attr.ib(default=None)
92 node = attr.ib(default=None)
92 node = attr.ib(default=None)
93
93
94
94
95 @interfaceutil.implementer(repository.ifilestorage)
95 @interfaceutil.implementer(repository.ifilestorage)
96 class filestorage(object):
96 class filestorage(object):
97 """Implements storage for a tracked path.
97 """Implements storage for a tracked path.
98
98
99 Data is stored in the VFS in a directory corresponding to the tracked
99 Data is stored in the VFS in a directory corresponding to the tracked
100 path.
100 path.
101
101
102 Index data is stored in an ``index`` file using CBOR.
102 Index data is stored in an ``index`` file using CBOR.
103
103
104 Fulltext data is stored in files having names of the node.
104 Fulltext data is stored in files having names of the node.
105 """
105 """
106
106
107 _flagserrorclass = simplestoreerror
107 _flagserrorclass = simplestoreerror
108
108
109 def __init__(self, svfs, path):
109 def __init__(self, svfs, path):
110 self._svfs = svfs
110 self._svfs = svfs
111 self._path = path
111 self._path = path
112
112
113 self._storepath = b'/'.join([b'data', path])
113 self._storepath = b'/'.join([b'data', path])
114 self._indexpath = b'/'.join([self._storepath, b'index'])
114 self._indexpath = b'/'.join([self._storepath, b'index'])
115
115
116 indexdata = self._svfs.tryread(self._indexpath)
116 indexdata = self._svfs.tryread(self._indexpath)
117 if indexdata:
117 if indexdata:
118 indexdata = cborutil.decodeall(indexdata)
118 indexdata = cborutil.decodeall(indexdata)
119
119
120 self._indexdata = indexdata or []
120 self._indexdata = indexdata or []
121 self._indexbynode = {}
121 self._indexbynode = {}
122 self._indexbyrev = {}
122 self._indexbyrev = {}
123 self._index = []
123 self._index = []
124 self._refreshindex()
124 self._refreshindex()
125
125
126 self._flagprocessors = dict(flagutil.flagprocessors)
126 self._flagprocessors = dict(flagutil.flagprocessors)
127
127
128 def _refreshindex(self):
128 def _refreshindex(self):
129 self._indexbynode.clear()
129 self._indexbynode.clear()
130 self._indexbyrev.clear()
130 self._indexbyrev.clear()
131 self._index = []
131 self._index = []
132
132
133 for i, entry in enumerate(self._indexdata):
133 for i, entry in enumerate(self._indexdata):
134 self._indexbynode[entry[b'node']] = entry
134 self._indexbynode[entry[b'node']] = entry
135 self._indexbyrev[i] = entry
135 self._indexbyrev[i] = entry
136
136
137 self._indexbynode[nullid] = {
137 self._indexbynode[nullid] = {
138 b'node': nullid,
138 b'node': nullid,
139 b'p1': nullid,
139 b'p1': nullid,
140 b'p2': nullid,
140 b'p2': nullid,
141 b'linkrev': nullrev,
141 b'linkrev': nullrev,
142 b'flags': 0,
142 b'flags': 0,
143 }
143 }
144
144
145 self._indexbyrev[nullrev] = {
145 self._indexbyrev[nullrev] = {
146 b'node': nullid,
146 b'node': nullid,
147 b'p1': nullid,
147 b'p1': nullid,
148 b'p2': nullid,
148 b'p2': nullid,
149 b'linkrev': nullrev,
149 b'linkrev': nullrev,
150 b'flags': 0,
150 b'flags': 0,
151 }
151 }
152
152
153 for i, entry in enumerate(self._indexdata):
153 for i, entry in enumerate(self._indexdata):
154 p1rev, p2rev = self.parentrevs(self.rev(entry[b'node']))
154 p1rev, p2rev = self.parentrevs(self.rev(entry[b'node']))
155
155
156 # start, length, rawsize, chainbase, linkrev, p1, p2, node
156 # start, length, rawsize, chainbase, linkrev, p1, p2, node
157 self._index.append(
157 self._index.append(
158 (0, 0, 0, -1, entry[b'linkrev'], p1rev, p2rev, entry[b'node'])
158 (0, 0, 0, -1, entry[b'linkrev'], p1rev, p2rev, entry[b'node'])
159 )
159 )
160
160
161 self._index.append((0, 0, 0, -1, -1, -1, -1, nullid))
161 self._index.append((0, 0, 0, -1, -1, -1, -1, nullid))
162
162
163 def __len__(self):
163 def __len__(self):
164 return len(self._indexdata)
164 return len(self._indexdata)
165
165
166 def __iter__(self):
166 def __iter__(self):
167 return iter(range(len(self)))
167 return iter(range(len(self)))
168
168
169 def revs(self, start=0, stop=None):
169 def revs(self, start=0, stop=None):
170 step = 1
170 step = 1
171 if stop is not None:
171 if stop is not None:
172 if start > stop:
172 if start > stop:
173 step = -1
173 step = -1
174
174
175 stop += step
175 stop += step
176 else:
176 else:
177 stop = len(self)
177 stop = len(self)
178
178
179 return range(start, stop, step)
179 return range(start, stop, step)
180
180
181 def parents(self, node):
181 def parents(self, node):
182 validatenode(node)
182 validatenode(node)
183
183
184 if node not in self._indexbynode:
184 if node not in self._indexbynode:
185 raise KeyError('unknown node')
185 raise KeyError('unknown node')
186
186
187 entry = self._indexbynode[node]
187 entry = self._indexbynode[node]
188
188
189 return entry[b'p1'], entry[b'p2']
189 return entry[b'p1'], entry[b'p2']
190
190
191 def parentrevs(self, rev):
191 def parentrevs(self, rev):
192 p1, p2 = self.parents(self._indexbyrev[rev][b'node'])
192 p1, p2 = self.parents(self._indexbyrev[rev][b'node'])
193 return self.rev(p1), self.rev(p2)
193 return self.rev(p1), self.rev(p2)
194
194
195 def rev(self, node):
195 def rev(self, node):
196 validatenode(node)
196 validatenode(node)
197
197
198 try:
198 try:
199 self._indexbynode[node]
199 self._indexbynode[node]
200 except KeyError:
200 except KeyError:
201 raise error.LookupError(node, self._indexpath, _('no node'))
201 raise error.LookupError(node, self._indexpath, _('no node'))
202
202
203 for rev, entry in self._indexbyrev.items():
203 for rev, entry in self._indexbyrev.items():
204 if entry[b'node'] == node:
204 if entry[b'node'] == node:
205 return rev
205 return rev
206
206
207 raise error.ProgrammingError(b'this should not occur')
207 raise error.ProgrammingError(b'this should not occur')
208
208
209 def node(self, rev):
209 def node(self, rev):
210 validaterev(rev)
210 validaterev(rev)
211
211
212 return self._indexbyrev[rev][b'node']
212 return self._indexbyrev[rev][b'node']
213
213
214 def hasnode(self, node):
214 def hasnode(self, node):
215 validatenode(node)
215 validatenode(node)
216 return node in self._indexbynode
216 return node in self._indexbynode
217
217
218 def censorrevision(self, tr, censornode, tombstone=b''):
218 def censorrevision(self, tr, censornode, tombstone=b''):
219 raise NotImplementedError('TODO')
219 raise NotImplementedError('TODO')
220
220
221 def lookup(self, node):
221 def lookup(self, node):
222 if isinstance(node, int):
222 if isinstance(node, int):
223 return self.node(node)
223 return self.node(node)
224
224
225 if len(node) == 20:
225 if len(node) == 20:
226 self.rev(node)
226 self.rev(node)
227 return node
227 return node
228
228
229 try:
229 try:
230 rev = int(node)
230 rev = int(node)
231 if '%d' % rev != node:
231 if '%d' % rev != node:
232 raise ValueError
232 raise ValueError
233
233
234 if rev < 0:
234 if rev < 0:
235 rev = len(self) + rev
235 rev = len(self) + rev
236 if rev < 0 or rev >= len(self):
236 if rev < 0 or rev >= len(self):
237 raise ValueError
237 raise ValueError
238
238
239 return self.node(rev)
239 return self.node(rev)
240 except (ValueError, OverflowError):
240 except (ValueError, OverflowError):
241 pass
241 pass
242
242
243 if len(node) == 40:
243 if len(node) == 40:
244 try:
244 try:
245 rawnode = bin(node)
245 rawnode = bin(node)
246 self.rev(rawnode)
246 self.rev(rawnode)
247 return rawnode
247 return rawnode
248 except TypeError:
248 except TypeError:
249 pass
249 pass
250
250
251 raise error.LookupError(node, self._path, _('invalid lookup input'))
251 raise error.LookupError(node, self._path, _('invalid lookup input'))
252
252
253 def linkrev(self, rev):
253 def linkrev(self, rev):
254 validaterev(rev)
254 validaterev(rev)
255
255
256 return self._indexbyrev[rev][b'linkrev']
256 return self._indexbyrev[rev][b'linkrev']
257
257
258 def _flags(self, rev):
258 def _flags(self, rev):
259 validaterev(rev)
259 validaterev(rev)
260
260
261 return self._indexbyrev[rev][b'flags']
261 return self._indexbyrev[rev][b'flags']
262
262
263 def _candelta(self, baserev, rev):
263 def _candelta(self, baserev, rev):
264 validaterev(baserev)
264 validaterev(baserev)
265 validaterev(rev)
265 validaterev(rev)
266
266
267 if (self._flags(baserev) & revlog.REVIDX_RAWTEXT_CHANGING_FLAGS) or (
267 if (self._flags(baserev) & revlog.REVIDX_RAWTEXT_CHANGING_FLAGS) or (
268 self._flags(rev) & revlog.REVIDX_RAWTEXT_CHANGING_FLAGS
268 self._flags(rev) & revlog.REVIDX_RAWTEXT_CHANGING_FLAGS
269 ):
269 ):
270 return False
270 return False
271
271
272 return True
272 return True
273
273
274 def checkhash(self, text, node, p1=None, p2=None, rev=None):
274 def checkhash(self, text, node, p1=None, p2=None, rev=None):
275 if p1 is None and p2 is None:
275 if p1 is None and p2 is None:
276 p1, p2 = self.parents(node)
276 p1, p2 = self.parents(node)
277 if node != storageutil.hashrevisionsha1(text, p1, p2):
277 if node != storageutil.hashrevisionsha1(text, p1, p2):
278 raise simplestoreerror(
278 raise simplestoreerror(
279 _("integrity check failed on %s") % self._path
279 _("integrity check failed on %s") % self._path
280 )
280 )
281
281
282 def revision(self, nodeorrev, raw=False):
282 def revision(self, nodeorrev, raw=False):
283 if isinstance(nodeorrev, int):
283 if isinstance(nodeorrev, int):
284 node = self.node(nodeorrev)
284 node = self.node(nodeorrev)
285 else:
285 else:
286 node = nodeorrev
286 node = nodeorrev
287 validatenode(node)
287 validatenode(node)
288
288
289 if node == nullid:
289 if node == nullid:
290 return b''
290 return b''
291
291
292 rev = self.rev(node)
292 rev = self.rev(node)
293 flags = self._flags(rev)
293 flags = self._flags(rev)
294
294
295 path = b'/'.join([self._storepath, hex(node)])
295 path = b'/'.join([self._storepath, hex(node)])
296 rawtext = self._svfs.read(path)
296 rawtext = self._svfs.read(path)
297
297
298 if raw:
298 if raw:
299 validatehash = flagutil.processflagsraw(self, rawtext, flags)
299 validatehash = flagutil.processflagsraw(self, rawtext, flags)
300 text = rawtext
300 text = rawtext
301 else:
301 else:
302 r = flagutil.processflagsread(self, rawtext, flags)
302 r = flagutil.processflagsread(self, rawtext, flags)
303 text, validatehash, sidedata = r
303 text, validatehash, sidedata = r
304 if validatehash:
304 if validatehash:
305 self.checkhash(text, node, rev=rev)
305 self.checkhash(text, node, rev=rev)
306
306
307 return text
307 return text
308
308
309 def rawdata(self, nodeorrev):
309 def rawdata(self, nodeorrev):
310 return self.revision(raw=True)
310 return self.revision(raw=True)
311
311
312 def read(self, node):
312 def read(self, node):
313 validatenode(node)
313 validatenode(node)
314
314
315 revision = self.revision(node)
315 revision = self.revision(node)
316
316
317 if not revision.startswith(b'\1\n'):
317 if not revision.startswith(b'\1\n'):
318 return revision
318 return revision
319
319
320 start = revision.index(b'\1\n', 2)
320 start = revision.index(b'\1\n', 2)
321 return revision[start + 2 :]
321 return revision[start + 2 :]
322
322
323 def renamed(self, node):
323 def renamed(self, node):
324 validatenode(node)
324 validatenode(node)
325
325
326 if self.parents(node)[0] != nullid:
326 if self.parents(node)[0] != nullid:
327 return False
327 return False
328
328
329 fulltext = self.revision(node)
329 fulltext = self.revision(node)
330 m = storageutil.parsemeta(fulltext)[0]
330 m = storageutil.parsemeta(fulltext)[0]
331
331
332 if m and 'copy' in m:
332 if m and 'copy' in m:
333 return m['copy'], bin(m['copyrev'])
333 return m['copy'], bin(m['copyrev'])
334
334
335 return False
335 return False
336
336
337 def cmp(self, node, text):
337 def cmp(self, node, text):
338 validatenode(node)
338 validatenode(node)
339
339
340 t = text
340 t = text
341
341
342 if text.startswith(b'\1\n'):
342 if text.startswith(b'\1\n'):
343 t = b'\1\n\1\n' + text
343 t = b'\1\n\1\n' + text
344
344
345 p1, p2 = self.parents(node)
345 p1, p2 = self.parents(node)
346
346
347 if storageutil.hashrevisionsha1(t, p1, p2) == node:
347 if storageutil.hashrevisionsha1(t, p1, p2) == node:
348 return False
348 return False
349
349
350 if self.iscensored(self.rev(node)):
350 if self.iscensored(self.rev(node)):
351 return text != b''
351 return text != b''
352
352
353 if self.renamed(node):
353 if self.renamed(node):
354 t2 = self.read(node)
354 t2 = self.read(node)
355 return t2 != text
355 return t2 != text
356
356
357 return True
357 return True
358
358
359 def size(self, rev):
359 def size(self, rev):
360 validaterev(rev)
360 validaterev(rev)
361
361
362 node = self._indexbyrev[rev][b'node']
362 node = self._indexbyrev[rev][b'node']
363
363
364 if self.renamed(node):
364 if self.renamed(node):
365 return len(self.read(node))
365 return len(self.read(node))
366
366
367 if self.iscensored(rev):
367 if self.iscensored(rev):
368 return 0
368 return 0
369
369
370 return len(self.revision(node))
370 return len(self.revision(node))
371
371
372 def iscensored(self, rev):
372 def iscensored(self, rev):
373 validaterev(rev)
373 validaterev(rev)
374
374
375 return self._flags(rev) & repository.REVISION_FLAG_CENSORED
375 return self._flags(rev) & repository.REVISION_FLAG_CENSORED
376
376
377 def commonancestorsheads(self, a, b):
377 def commonancestorsheads(self, a, b):
378 validatenode(a)
378 validatenode(a)
379 validatenode(b)
379 validatenode(b)
380
380
381 a = self.rev(a)
381 a = self.rev(a)
382 b = self.rev(b)
382 b = self.rev(b)
383
383
384 ancestors = ancestor.commonancestorsheads(self.parentrevs, a, b)
384 ancestors = ancestor.commonancestorsheads(self.parentrevs, a, b)
385 return pycompat.maplist(self.node, ancestors)
385 return pycompat.maplist(self.node, ancestors)
386
386
387 def descendants(self, revs):
387 def descendants(self, revs):
388 # This is a copy of revlog.descendants()
388 # This is a copy of revlog.descendants()
389 first = min(revs)
389 first = min(revs)
390 if first == nullrev:
390 if first == nullrev:
391 for i in self:
391 for i in self:
392 yield i
392 yield i
393 return
393 return
394
394
395 seen = set(revs)
395 seen = set(revs)
396 for i in self.revs(start=first + 1):
396 for i in self.revs(start=first + 1):
397 for x in self.parentrevs(i):
397 for x in self.parentrevs(i):
398 if x != nullrev and x in seen:
398 if x != nullrev and x in seen:
399 seen.add(i)
399 seen.add(i)
400 yield i
400 yield i
401 break
401 break
402
402
403 # Required by verify.
403 # Required by verify.
404 def files(self):
404 def files(self):
405 entries = self._svfs.listdir(self._storepath)
405 entries = self._svfs.listdir(self._storepath)
406
406
407 # Strip out undo.backup.* files created as part of transaction
407 # Strip out undo.backup.* files created as part of transaction
408 # recording.
408 # recording.
409 entries = [f for f in entries if not f.startswith('undo.backup.')]
409 entries = [f for f in entries if not f.startswith('undo.backup.')]
410
410
411 return [b'/'.join((self._storepath, f)) for f in entries]
411 return [b'/'.join((self._storepath, f)) for f in entries]
412
412
413 def storageinfo(
413 def storageinfo(
414 self,
414 self,
415 exclusivefiles=False,
415 exclusivefiles=False,
416 sharedfiles=False,
416 sharedfiles=False,
417 revisionscount=False,
417 revisionscount=False,
418 trackedsize=False,
418 trackedsize=False,
419 storedsize=False,
419 storedsize=False,
420 ):
420 ):
421 # TODO do a real implementation of this
421 # TODO do a real implementation of this
422 return {
422 return {
423 'exclusivefiles': [],
423 'exclusivefiles': [],
424 'sharedfiles': [],
424 'sharedfiles': [],
425 'revisionscount': len(self),
425 'revisionscount': len(self),
426 'trackedsize': 0,
426 'trackedsize': 0,
427 'storedsize': None,
427 'storedsize': None,
428 }
428 }
429
429
430 def verifyintegrity(self, state):
430 def verifyintegrity(self, state):
431 state['skipread'] = set()
431 state['skipread'] = set()
432 for rev in self:
432 for rev in self:
433 node = self.node(rev)
433 node = self.node(rev)
434 try:
434 try:
435 self.revision(node)
435 self.revision(node)
436 except Exception as e:
436 except Exception as e:
437 yield simplefilestoreproblem(
437 yield simplefilestoreproblem(
438 error='unpacking %s: %s' % (node, e), node=node
438 error='unpacking %s: %s' % (node, e), node=node
439 )
439 )
440 state['skipread'].add(node)
440 state['skipread'].add(node)
441
441
442 def emitrevisions(
442 def emitrevisions(
443 self,
443 self,
444 nodes,
444 nodes,
445 nodesorder=None,
445 nodesorder=None,
446 revisiondata=False,
446 revisiondata=False,
447 assumehaveparentrevisions=False,
447 assumehaveparentrevisions=False,
448 deltamode=repository.CG_DELTAMODE_STD,
448 deltamode=repository.CG_DELTAMODE_STD,
449 ):
449 ):
450 # TODO this will probably break on some ordering options.
450 # TODO this will probably break on some ordering options.
451 nodes = [n for n in nodes if n != nullid]
451 nodes = [n for n in nodes if n != nullid]
452 if not nodes:
452 if not nodes:
453 return
453 return
454 for delta in storageutil.emitrevisions(
454 for delta in storageutil.emitrevisions(
455 self,
455 self,
456 nodes,
456 nodes,
457 nodesorder,
457 nodesorder,
458 simplestorerevisiondelta,
458 simplestorerevisiondelta,
459 revisiondata=revisiondata,
459 revisiondata=revisiondata,
460 assumehaveparentrevisions=assumehaveparentrevisions,
460 assumehaveparentrevisions=assumehaveparentrevisions,
461 deltamode=deltamode,
461 deltamode=deltamode,
462 ):
462 ):
463 yield delta
463 yield delta
464
464
465 def add(self, text, meta, transaction, linkrev, p1, p2):
465 def add(self, text, meta, transaction, linkrev, p1, p2):
466 if meta or text.startswith(b'\1\n'):
466 if meta or text.startswith(b'\1\n'):
467 text = storageutil.packmeta(meta, text)
467 text = storageutil.packmeta(meta, text)
468
468
469 return self.addrevision(text, transaction, linkrev, p1, p2)
469 return self.addrevision(text, transaction, linkrev, p1, p2)
470
470
471 def addrevision(
471 def addrevision(
472 self,
472 self,
473 text,
473 text,
474 transaction,
474 transaction,
475 linkrev,
475 linkrev,
476 p1,
476 p1,
477 p2,
477 p2,
478 node=None,
478 node=None,
479 flags=revlog.REVIDX_DEFAULT_FLAGS,
479 flags=revlog.REVIDX_DEFAULT_FLAGS,
480 cachedelta=None,
480 cachedelta=None,
481 ):
481 ):
482 validatenode(p1)
482 validatenode(p1)
483 validatenode(p2)
483 validatenode(p2)
484
484
485 if flags:
485 if flags:
486 node = node or storageutil.hashrevisionsha1(text, p1, p2)
486 node = node or storageutil.hashrevisionsha1(text, p1, p2)
487
487
488 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
488 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
489
489
490 node = node or storageutil.hashrevisionsha1(text, p1, p2)
490 node = node or storageutil.hashrevisionsha1(text, p1, p2)
491
491
492 if node in self._indexbynode:
492 if node in self._indexbynode:
493 return node
493 return node
494
494
495 if validatehash:
495 if validatehash:
496 self.checkhash(rawtext, node, p1=p1, p2=p2)
496 self.checkhash(rawtext, node, p1=p1, p2=p2)
497
497
498 return self._addrawrevision(
498 return self._addrawrevision(
499 node, rawtext, transaction, linkrev, p1, p2, flags
499 node, rawtext, transaction, linkrev, p1, p2, flags
500 )
500 )
501
501
502 def _addrawrevision(self, node, rawtext, transaction, link, p1, p2, flags):
502 def _addrawrevision(self, node, rawtext, transaction, link, p1, p2, flags):
503 transaction.addbackup(self._indexpath)
503 transaction.addbackup(self._indexpath)
504
504
505 path = b'/'.join([self._storepath, hex(node)])
505 path = b'/'.join([self._storepath, hex(node)])
506
506
507 self._svfs.write(path, rawtext)
507 self._svfs.write(path, rawtext)
508
508
509 self._indexdata.append(
509 self._indexdata.append(
510 {
510 {
511 b'node': node,
511 b'node': node,
512 b'p1': p1,
512 b'p1': p1,
513 b'p2': p2,
513 b'p2': p2,
514 b'linkrev': link,
514 b'linkrev': link,
515 b'flags': flags,
515 b'flags': flags,
516 }
516 }
517 )
517 )
518
518
519 self._reflectindexupdate()
519 self._reflectindexupdate()
520
520
521 return node
521 return node
522
522
523 def _reflectindexupdate(self):
523 def _reflectindexupdate(self):
524 self._refreshindex()
524 self._refreshindex()
525 self._svfs.write(
525 self._svfs.write(
526 self._indexpath, ''.join(cborutil.streamencode(self._indexdata))
526 self._indexpath, ''.join(cborutil.streamencode(self._indexdata))
527 )
527 )
528
528
529 def addgroup(
529 def addgroup(
530 self,
530 self,
531 deltas,
531 deltas,
532 linkmapper,
532 linkmapper,
533 transaction,
533 transaction,
534 addrevisioncb=None,
534 addrevisioncb=None,
535 duplicaterevisioncb=None,
535 duplicaterevisioncb=None,
536 maybemissingparents=False,
536 maybemissingparents=False,
537 ):
537 ):
538 if maybemissingparents:
538 if maybemissingparents:
539 raise error.Abort(
539 raise error.Abort(
540 _('simple store does not support missing parents ' 'write mode')
540 _('simple store does not support missing parents ' 'write mode')
541 )
541 )
542
542
543 empty = True
543 empty = True
544
544
545 transaction.addbackup(self._indexpath)
545 transaction.addbackup(self._indexpath)
546
546
547 for node, p1, p2, linknode, deltabase, delta, flags in deltas:
547 for node, p1, p2, linknode, deltabase, delta, flags in deltas:
548 linkrev = linkmapper(linknode)
548 linkrev = linkmapper(linknode)
549 flags = flags or revlog.REVIDX_DEFAULT_FLAGS
549 flags = flags or revlog.REVIDX_DEFAULT_FLAGS
550
550
551 if node in self._indexbynode:
551 if node in self._indexbynode:
552 if duplicaterevisioncb:
552 if duplicaterevisioncb:
553 duplicaterevisioncb(self, node)
553 duplicaterevisioncb(self, self.rev(node))
554 empty = False
554 empty = False
555 continue
555 continue
556
556
557 # Need to resolve the fulltext from the delta base.
557 # Need to resolve the fulltext from the delta base.
558 if deltabase == nullid:
558 if deltabase == nullid:
559 text = mdiff.patch(b'', delta)
559 text = mdiff.patch(b'', delta)
560 else:
560 else:
561 text = mdiff.patch(self.revision(deltabase), delta)
561 text = mdiff.patch(self.revision(deltabase), delta)
562
562
563 self._addrawrevision(
563 rev = self._addrawrevision(
564 node, text, transaction, linkrev, p1, p2, flags
564 node, text, transaction, linkrev, p1, p2, flags
565 )
565 )
566
566
567 if addrevisioncb:
567 if addrevisioncb:
568 addrevisioncb(self, node)
568 addrevisioncb(self, rev)
569 empty = False
569 empty = False
570 return not empty
570 return not empty
571
571
572 def _headrevs(self):
572 def _headrevs(self):
573 # Assume all revisions are heads by default.
573 # Assume all revisions are heads by default.
574 revishead = {rev: True for rev in self._indexbyrev}
574 revishead = {rev: True for rev in self._indexbyrev}
575
575
576 for rev, entry in self._indexbyrev.items():
576 for rev, entry in self._indexbyrev.items():
577 # Unset head flag for all seen parents.
577 # Unset head flag for all seen parents.
578 revishead[self.rev(entry[b'p1'])] = False
578 revishead[self.rev(entry[b'p1'])] = False
579 revishead[self.rev(entry[b'p2'])] = False
579 revishead[self.rev(entry[b'p2'])] = False
580
580
581 return [rev for rev, ishead in sorted(revishead.items()) if ishead]
581 return [rev for rev, ishead in sorted(revishead.items()) if ishead]
582
582
583 def heads(self, start=None, stop=None):
583 def heads(self, start=None, stop=None):
584 # This is copied from revlog.py.
584 # This is copied from revlog.py.
585 if start is None and stop is None:
585 if start is None and stop is None:
586 if not len(self):
586 if not len(self):
587 return [nullid]
587 return [nullid]
588 return [self.node(r) for r in self._headrevs()]
588 return [self.node(r) for r in self._headrevs()]
589
589
590 if start is None:
590 if start is None:
591 start = nullid
591 start = nullid
592 if stop is None:
592 if stop is None:
593 stop = []
593 stop = []
594 stoprevs = {self.rev(n) for n in stop}
594 stoprevs = {self.rev(n) for n in stop}
595 startrev = self.rev(start)
595 startrev = self.rev(start)
596 reachable = {startrev}
596 reachable = {startrev}
597 heads = {startrev}
597 heads = {startrev}
598
598
599 parentrevs = self.parentrevs
599 parentrevs = self.parentrevs
600 for r in self.revs(start=startrev + 1):
600 for r in self.revs(start=startrev + 1):
601 for p in parentrevs(r):
601 for p in parentrevs(r):
602 if p in reachable:
602 if p in reachable:
603 if r not in stoprevs:
603 if r not in stoprevs:
604 reachable.add(r)
604 reachable.add(r)
605 heads.add(r)
605 heads.add(r)
606 if p in heads and p not in stoprevs:
606 if p in heads and p not in stoprevs:
607 heads.remove(p)
607 heads.remove(p)
608
608
609 return [self.node(r) for r in heads]
609 return [self.node(r) for r in heads]
610
610
611 def children(self, node):
611 def children(self, node):
612 validatenode(node)
612 validatenode(node)
613
613
614 # This is a copy of revlog.children().
614 # This is a copy of revlog.children().
615 c = []
615 c = []
616 p = self.rev(node)
616 p = self.rev(node)
617 for r in self.revs(start=p + 1):
617 for r in self.revs(start=p + 1):
618 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
618 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
619 if prevs:
619 if prevs:
620 for pr in prevs:
620 for pr in prevs:
621 if pr == p:
621 if pr == p:
622 c.append(self.node(r))
622 c.append(self.node(r))
623 elif p == nullrev:
623 elif p == nullrev:
624 c.append(self.node(r))
624 c.append(self.node(r))
625 return c
625 return c
626
626
627 def getstrippoint(self, minlink):
627 def getstrippoint(self, minlink):
628 return storageutil.resolvestripinfo(
628 return storageutil.resolvestripinfo(
629 minlink,
629 minlink,
630 len(self) - 1,
630 len(self) - 1,
631 self._headrevs(),
631 self._headrevs(),
632 self.linkrev,
632 self.linkrev,
633 self.parentrevs,
633 self.parentrevs,
634 )
634 )
635
635
636 def strip(self, minlink, transaction):
636 def strip(self, minlink, transaction):
637 if not len(self):
637 if not len(self):
638 return
638 return
639
639
640 rev, _ignored = self.getstrippoint(minlink)
640 rev, _ignored = self.getstrippoint(minlink)
641 if rev == len(self):
641 if rev == len(self):
642 return
642 return
643
643
644 # Purge index data starting at the requested revision.
644 # Purge index data starting at the requested revision.
645 self._indexdata[rev:] = []
645 self._indexdata[rev:] = []
646 self._reflectindexupdate()
646 self._reflectindexupdate()
647
647
648
648
649 def issimplestorefile(f, kind, st):
649 def issimplestorefile(f, kind, st):
650 if kind != stat.S_IFREG:
650 if kind != stat.S_IFREG:
651 return False
651 return False
652
652
653 if store.isrevlog(f, kind, st):
653 if store.isrevlog(f, kind, st):
654 return False
654 return False
655
655
656 # Ignore transaction undo files.
656 # Ignore transaction undo files.
657 if f.startswith('undo.'):
657 if f.startswith('undo.'):
658 return False
658 return False
659
659
660 # Otherwise assume it belongs to the simple store.
660 # Otherwise assume it belongs to the simple store.
661 return True
661 return True
662
662
663
663
664 class simplestore(store.encodedstore):
664 class simplestore(store.encodedstore):
665 def datafiles(self):
665 def datafiles(self):
666 for x in super(simplestore, self).datafiles():
666 for x in super(simplestore, self).datafiles():
667 yield x
667 yield x
668
668
669 # Supplement with non-revlog files.
669 # Supplement with non-revlog files.
670 extrafiles = self._walk('data', True, filefilter=issimplestorefile)
670 extrafiles = self._walk('data', True, filefilter=issimplestorefile)
671
671
672 for unencoded, encoded, size in extrafiles:
672 for unencoded, encoded, size in extrafiles:
673 try:
673 try:
674 unencoded = store.decodefilename(unencoded)
674 unencoded = store.decodefilename(unencoded)
675 except KeyError:
675 except KeyError:
676 unencoded = None
676 unencoded = None
677
677
678 yield unencoded, encoded, size
678 yield unencoded, encoded, size
679
679
680
680
681 def reposetup(ui, repo):
681 def reposetup(ui, repo):
682 if not repo.local():
682 if not repo.local():
683 return
683 return
684
684
685 if isinstance(repo, bundlerepo.bundlerepository):
685 if isinstance(repo, bundlerepo.bundlerepository):
686 raise error.Abort(_('cannot use simple store with bundlerepo'))
686 raise error.Abort(_('cannot use simple store with bundlerepo'))
687
687
688 class simplestorerepo(repo.__class__):
688 class simplestorerepo(repo.__class__):
689 def file(self, f):
689 def file(self, f):
690 return filestorage(self.svfs, f)
690 return filestorage(self.svfs, f)
691
691
692 repo.__class__ = simplestorerepo
692 repo.__class__ = simplestorerepo
693
693
694
694
695 def featuresetup(ui, supported):
695 def featuresetup(ui, supported):
696 supported.add(REQUIREMENT)
696 supported.add(REQUIREMENT)
697
697
698
698
699 def newreporequirements(orig, ui, createopts):
699 def newreporequirements(orig, ui, createopts):
700 """Modifies default requirements for new repos to use the simple store."""
700 """Modifies default requirements for new repos to use the simple store."""
701 requirements = orig(ui, createopts)
701 requirements = orig(ui, createopts)
702
702
703 # These requirements are only used to affect creation of the store
703 # These requirements are only used to affect creation of the store
704 # object. We have our own store. So we can remove them.
704 # object. We have our own store. So we can remove them.
705 # TODO do this once we feel like taking the test hit.
705 # TODO do this once we feel like taking the test hit.
706 # if 'fncache' in requirements:
706 # if 'fncache' in requirements:
707 # requirements.remove('fncache')
707 # requirements.remove('fncache')
708 # if 'dotencode' in requirements:
708 # if 'dotencode' in requirements:
709 # requirements.remove('dotencode')
709 # requirements.remove('dotencode')
710
710
711 requirements.add(REQUIREMENT)
711 requirements.add(REQUIREMENT)
712
712
713 return requirements
713 return requirements
714
714
715
715
716 def makestore(orig, requirements, path, vfstype):
716 def makestore(orig, requirements, path, vfstype):
717 if REQUIREMENT not in requirements:
717 if REQUIREMENT not in requirements:
718 return orig(requirements, path, vfstype)
718 return orig(requirements, path, vfstype)
719
719
720 return simplestore(path, vfstype)
720 return simplestore(path, vfstype)
721
721
722
722
723 def verifierinit(orig, self, *args, **kwargs):
723 def verifierinit(orig, self, *args, **kwargs):
724 orig(self, *args, **kwargs)
724 orig(self, *args, **kwargs)
725
725
726 # We don't care that files in the store don't align with what is
726 # We don't care that files in the store don't align with what is
727 # advertised. So suppress these warnings.
727 # advertised. So suppress these warnings.
728 self.warnorphanstorefiles = False
728 self.warnorphanstorefiles = False
729
729
730
730
731 def extsetup(ui):
731 def extsetup(ui):
732 localrepo.featuresetupfuncs.add(featuresetup)
732 localrepo.featuresetupfuncs.add(featuresetup)
733
733
734 extensions.wrapfunction(
734 extensions.wrapfunction(
735 localrepo, 'newreporequirements', newreporequirements
735 localrepo, 'newreporequirements', newreporequirements
736 )
736 )
737 extensions.wrapfunction(localrepo, 'makestore', makestore)
737 extensions.wrapfunction(localrepo, 'makestore', makestore)
738 extensions.wrapfunction(verify.verifier, '__init__', verifierinit)
738 extensions.wrapfunction(verify.verifier, '__init__', verifierinit)
General Comments 0
You need to be logged in to leave comments. Login now