comparison mercurial/utils/storageutil.py @ 40008:842ffcf1d42f

storageutil: extract most of emitrevisions() to standalone function As part of implementing a storage backend, I found myself copying most of revlog.emitrevisions(). This code is highly nuanced and it bothered me greatly to be copying such low-level code. This commit extracts the bulk of revlog.emitrevisions() into a new standalone function. In order to make the function generally usable, all "self" function calls that aren't exposed on the ifilestorage interface are passed in via callable arguments. No meaningful behavior should have changed as part of the port. Upcoming commits will tweak behavior to make the code more generically usable. Differential Revision: https://phab.mercurial-scm.org/D4803
author Gregory Szorc <gregory.szorc@gmail.com>
date Fri, 28 Sep 2018 16:16:22 -0700
parents 1470183068b8
children 631c6f5058b9
comparison
equal deleted inserted replaced
40007:1470183068b8 40008:842ffcf1d42f
260 heads[p] = plinkrev 260 heads[p] = plinkrev
261 if plinkrev >= minlinkrev: 261 if plinkrev >= minlinkrev:
262 futurelargelinkrevs.add(plinkrev) 262 futurelargelinkrevs.add(plinkrev)
263 263
264 return strippoint, brokenrevs 264 return strippoint, brokenrevs
265
266 def emitrevisions(store, revs, resultcls, deltaparentfn, candeltafn,
267 rawsizefn, revdifffn, flagsfn, sendfulltext=False,
268 revisiondata=False, assumehaveparentrevisions=False,
269 deltaprevious=False):
270 """Generic implementation of ifiledata.emitrevisions().
271
272 Emitting revision data is subtly complex. This function attempts to
273 encapsulate all the logic for doing so in a backend-agnostic way.
274
275 ``store``
276 Object conforming to ``ifilestorage`` interface.
277
278 ``revs``
279 List of integer revision numbers whose data to emit.
280
281 ``resultcls``
282 A type implementing the ``irevisiondelta`` interface that will be
283 constructed and returned.
284
285 ``deltaparentfn``
286 Callable receiving a revision number and returning the revision number
287 of a revision that the internal delta is stored against. This delta
288 will be preferred over computing a new arbitrary delta.
289
290 ``candeltafn``
291 Callable receiving a pair of revision numbers that returns a bool
292 indicating whether a delta between them can be produced.
293
294 ``rawsizefn``
295 Callable receiving a revision number and returning the length of the
296 ``store.revision(rev, raw=True)``.
297
298 ``revdifffn``
299 Callable receiving a pair of revision numbers that returns a delta
300 between them.
301
302 ``flagsfn``
303 Callable receiving a revision number and returns the integer flags
304 value for it.
305
306 ``sendfulltext``
307 Whether to send fulltext revisions instead of deltas, if allowed.
308
309 ``revisiondata``
310 ``assumehaveparentrevisions``
311 ``deltaprevious``
312 See ``ifiledata.emitrevisions()`` interface documentation.
313 """
314
315 fnode = store.node
316
317 prevrev = None
318
319 if deltaprevious or assumehaveparentrevisions:
320 prevrev = store.parentrevs(revs[0])[0]
321
322 # Set of revs available to delta against.
323 available = set()
324
325 for rev in revs:
326 if rev == nullrev:
327 continue
328
329 node = fnode(rev)
330 deltaparentrev = deltaparentfn(rev)
331 p1rev, p2rev = store.parentrevs(rev)
332
333 # Forced delta against previous mode.
334 if deltaprevious:
335 baserev = prevrev
336
337 # We're instructed to send fulltext. Honor that.
338 elif sendfulltext:
339 baserev = nullrev
340
341 # There is a delta in storage. We try to use that because it
342 # amounts to effectively copying data from storage and is
343 # therefore the fastest.
344 elif deltaparentrev != nullrev:
345 # Base revision was already emitted in this group. We can
346 # always safely use the delta.
347 if deltaparentrev in available:
348 baserev = deltaparentrev
349
350 # Base revision is a parent that hasn't been emitted already.
351 # Use it if we can assume the receiver has the parent revision.
352 elif (assumehaveparentrevisions
353 and deltaparentrev in (p1rev, p2rev)):
354 baserev = deltaparentrev
355
356 # No guarantee the receiver has the delta parent. Send delta
357 # against last revision (if possible), which in the common case
358 # should be similar enough to this revision that the delta is
359 # reasonable.
360 elif prevrev is not None:
361 baserev = prevrev
362 else:
363 baserev = nullrev
364
365 # Storage has a fulltext revision.
366
367 # Let's use the previous revision, which is as good a guess as any.
368 # There is definitely room to improve this logic.
369 elif prevrev is not None:
370 baserev = prevrev
371 else:
372 baserev = nullrev
373
374 # But we can't actually use our chosen delta base for whatever
375 # reason. Reset to fulltext.
376 if baserev != nullrev and not candeltafn(baserev, rev):
377 baserev = nullrev
378
379 revision = None
380 delta = None
381 baserevisionsize = None
382
383 if revisiondata:
384 if store.iscensored(baserev) or store.iscensored(rev):
385 try:
386 revision = store.revision(node, raw=True)
387 except error.CensoredNodeError as e:
388 revision = e.tombstone
389
390 if baserev != nullrev:
391 baserevisionsize = rawsizefn(baserev)
392
393 elif baserev == nullrev and not deltaprevious:
394 revision = store.revision(node, raw=True)
395 available.add(rev)
396 else:
397 delta = revdifffn(baserev, rev)
398 available.add(rev)
399
400 yield resultcls(
401 node=node,
402 p1node=fnode(p1rev),
403 p2node=fnode(p2rev),
404 basenode=fnode(baserev),
405 flags=flagsfn(rev),
406 baserevisionsize=baserevisionsize,
407 revision=revision,
408 delta=delta)
409
410 prevrev = rev