##// END OF EJS Templates
storageutil: allow modern hash sizes for fileids...
Joerg Sonnenberger -
r45963:c4a4a495 default
parent child Browse files
Show More
@@ -1,512 +1,512 b''
1 # storageutil.py - Storage functionality agnostic of backend implementation.
1 # storageutil.py - Storage functionality agnostic of backend implementation.
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 re
10 import re
11 import struct
11 import struct
12
12
13 from ..i18n import _
13 from ..i18n import _
14 from ..node import (
14 from ..node import (
15 bin,
15 bin,
16 nullid,
16 nullid,
17 nullrev,
17 nullrev,
18 )
18 )
19 from .. import (
19 from .. import (
20 dagop,
20 dagop,
21 error,
21 error,
22 mdiff,
22 mdiff,
23 pycompat,
23 pycompat,
24 )
24 )
25 from ..interfaces import repository
25 from ..interfaces import repository
26 from ..utils import hashutil
26 from ..utils import hashutil
27
27
28 _nullhash = hashutil.sha1(nullid)
28 _nullhash = hashutil.sha1(nullid)
29
29
30
30
31 def hashrevisionsha1(text, p1, p2):
31 def hashrevisionsha1(text, p1, p2):
32 """Compute the SHA-1 for revision data and its parents.
32 """Compute the SHA-1 for revision data and its parents.
33
33
34 This hash combines both the current file contents and its history
34 This hash combines both the current file contents and its history
35 in a manner that makes it easy to distinguish nodes with the same
35 in a manner that makes it easy to distinguish nodes with the same
36 content in the revision graph.
36 content in the revision graph.
37 """
37 """
38 # As of now, if one of the parent node is null, p2 is null
38 # As of now, if one of the parent node is null, p2 is null
39 if p2 == nullid:
39 if p2 == nullid:
40 # deep copy of a hash is faster than creating one
40 # deep copy of a hash is faster than creating one
41 s = _nullhash.copy()
41 s = _nullhash.copy()
42 s.update(p1)
42 s.update(p1)
43 else:
43 else:
44 # none of the parent nodes are nullid
44 # none of the parent nodes are nullid
45 if p1 < p2:
45 if p1 < p2:
46 a = p1
46 a = p1
47 b = p2
47 b = p2
48 else:
48 else:
49 a = p2
49 a = p2
50 b = p1
50 b = p1
51 s = hashutil.sha1(a)
51 s = hashutil.sha1(a)
52 s.update(b)
52 s.update(b)
53 s.update(text)
53 s.update(text)
54 return s.digest()
54 return s.digest()
55
55
56
56
57 METADATA_RE = re.compile(b'\x01\n')
57 METADATA_RE = re.compile(b'\x01\n')
58
58
59
59
60 def parsemeta(text):
60 def parsemeta(text):
61 """Parse metadata header from revision data.
61 """Parse metadata header from revision data.
62
62
63 Returns a 2-tuple of (metadata, offset), where both can be None if there
63 Returns a 2-tuple of (metadata, offset), where both can be None if there
64 is no metadata.
64 is no metadata.
65 """
65 """
66 # text can be buffer, so we can't use .startswith or .index
66 # text can be buffer, so we can't use .startswith or .index
67 if text[:2] != b'\x01\n':
67 if text[:2] != b'\x01\n':
68 return None, None
68 return None, None
69 s = METADATA_RE.search(text, 2).start()
69 s = METADATA_RE.search(text, 2).start()
70 mtext = text[2:s]
70 mtext = text[2:s]
71 meta = {}
71 meta = {}
72 for l in mtext.splitlines():
72 for l in mtext.splitlines():
73 k, v = l.split(b': ', 1)
73 k, v = l.split(b': ', 1)
74 meta[k] = v
74 meta[k] = v
75 return meta, s + 2
75 return meta, s + 2
76
76
77
77
78 def packmeta(meta, text):
78 def packmeta(meta, text):
79 """Add metadata to fulltext to produce revision text."""
79 """Add metadata to fulltext to produce revision text."""
80 keys = sorted(meta)
80 keys = sorted(meta)
81 metatext = b''.join(b'%s: %s\n' % (k, meta[k]) for k in keys)
81 metatext = b''.join(b'%s: %s\n' % (k, meta[k]) for k in keys)
82 return b'\x01\n%s\x01\n%s' % (metatext, text)
82 return b'\x01\n%s\x01\n%s' % (metatext, text)
83
83
84
84
85 def iscensoredtext(text):
85 def iscensoredtext(text):
86 meta = parsemeta(text)[0]
86 meta = parsemeta(text)[0]
87 return meta and b'censored' in meta
87 return meta and b'censored' in meta
88
88
89
89
90 def filtermetadata(text):
90 def filtermetadata(text):
91 """Extract just the revision data from source text.
91 """Extract just the revision data from source text.
92
92
93 Returns ``text`` unless it has a metadata header, in which case we return
93 Returns ``text`` unless it has a metadata header, in which case we return
94 a new buffer without hte metadata.
94 a new buffer without hte metadata.
95 """
95 """
96 if not text.startswith(b'\x01\n'):
96 if not text.startswith(b'\x01\n'):
97 return text
97 return text
98
98
99 offset = text.index(b'\x01\n', 2)
99 offset = text.index(b'\x01\n', 2)
100 return text[offset + 2 :]
100 return text[offset + 2 :]
101
101
102
102
103 def filerevisioncopied(store, node):
103 def filerevisioncopied(store, node):
104 """Resolve file revision copy metadata.
104 """Resolve file revision copy metadata.
105
105
106 Returns ``False`` if the file has no copy metadata. Otherwise a
106 Returns ``False`` if the file has no copy metadata. Otherwise a
107 2-tuple of the source filename and node.
107 2-tuple of the source filename and node.
108 """
108 """
109 if store.parents(node)[0] != nullid:
109 if store.parents(node)[0] != nullid:
110 return False
110 return False
111
111
112 meta = parsemeta(store.revision(node))[0]
112 meta = parsemeta(store.revision(node))[0]
113
113
114 # copy and copyrev occur in pairs. In rare cases due to old bugs,
114 # copy and copyrev occur in pairs. In rare cases due to old bugs,
115 # one can occur without the other. So ensure both are present to flag
115 # one can occur without the other. So ensure both are present to flag
116 # as a copy.
116 # as a copy.
117 if meta and b'copy' in meta and b'copyrev' in meta:
117 if meta and b'copy' in meta and b'copyrev' in meta:
118 return meta[b'copy'], bin(meta[b'copyrev'])
118 return meta[b'copy'], bin(meta[b'copyrev'])
119
119
120 return False
120 return False
121
121
122
122
123 def filedataequivalent(store, node, filedata):
123 def filedataequivalent(store, node, filedata):
124 """Determines whether file data is equivalent to a stored node.
124 """Determines whether file data is equivalent to a stored node.
125
125
126 Returns True if the passed file data would hash to the same value
126 Returns True if the passed file data would hash to the same value
127 as a stored revision and False otherwise.
127 as a stored revision and False otherwise.
128
128
129 When a stored revision is censored, filedata must be empty to have
129 When a stored revision is censored, filedata must be empty to have
130 equivalence.
130 equivalence.
131
131
132 When a stored revision has copy metadata, it is ignored as part
132 When a stored revision has copy metadata, it is ignored as part
133 of the compare.
133 of the compare.
134 """
134 """
135
135
136 if filedata.startswith(b'\x01\n'):
136 if filedata.startswith(b'\x01\n'):
137 revisiontext = b'\x01\n\x01\n' + filedata
137 revisiontext = b'\x01\n\x01\n' + filedata
138 else:
138 else:
139 revisiontext = filedata
139 revisiontext = filedata
140
140
141 p1, p2 = store.parents(node)
141 p1, p2 = store.parents(node)
142
142
143 computednode = hashrevisionsha1(revisiontext, p1, p2)
143 computednode = hashrevisionsha1(revisiontext, p1, p2)
144
144
145 if computednode == node:
145 if computednode == node:
146 return True
146 return True
147
147
148 # Censored files compare against the empty file.
148 # Censored files compare against the empty file.
149 if store.iscensored(store.rev(node)):
149 if store.iscensored(store.rev(node)):
150 return filedata == b''
150 return filedata == b''
151
151
152 # Renaming a file produces a different hash, even if the data
152 # Renaming a file produces a different hash, even if the data
153 # remains unchanged. Check if that's the case.
153 # remains unchanged. Check if that's the case.
154 if store.renamed(node):
154 if store.renamed(node):
155 return store.read(node) == filedata
155 return store.read(node) == filedata
156
156
157 return False
157 return False
158
158
159
159
160 def iterrevs(storelen, start=0, stop=None):
160 def iterrevs(storelen, start=0, stop=None):
161 """Iterate over revision numbers in a store."""
161 """Iterate over revision numbers in a store."""
162 step = 1
162 step = 1
163
163
164 if stop is not None:
164 if stop is not None:
165 if start > stop:
165 if start > stop:
166 step = -1
166 step = -1
167 stop += step
167 stop += step
168 if stop > storelen:
168 if stop > storelen:
169 stop = storelen
169 stop = storelen
170 else:
170 else:
171 stop = storelen
171 stop = storelen
172
172
173 return pycompat.xrange(start, stop, step)
173 return pycompat.xrange(start, stop, step)
174
174
175
175
176 def fileidlookup(store, fileid, identifier):
176 def fileidlookup(store, fileid, identifier):
177 """Resolve the file node for a value.
177 """Resolve the file node for a value.
178
178
179 ``store`` is an object implementing the ``ifileindex`` interface.
179 ``store`` is an object implementing the ``ifileindex`` interface.
180
180
181 ``fileid`` can be:
181 ``fileid`` can be:
182
182
183 * A 20 byte binary node.
183 * A 20 or 32 byte binary node.
184 * An integer revision number
184 * An integer revision number
185 * A 40 byte hex node.
185 * A 40 or 64 byte hex node.
186 * A bytes that can be parsed as an integer representing a revision number.
186 * A bytes that can be parsed as an integer representing a revision number.
187
187
188 ``identifier`` is used to populate ``error.LookupError`` with an identifier
188 ``identifier`` is used to populate ``error.LookupError`` with an identifier
189 for the store.
189 for the store.
190
190
191 Raises ``error.LookupError`` on failure.
191 Raises ``error.LookupError`` on failure.
192 """
192 """
193 if isinstance(fileid, int):
193 if isinstance(fileid, int):
194 try:
194 try:
195 return store.node(fileid)
195 return store.node(fileid)
196 except IndexError:
196 except IndexError:
197 raise error.LookupError(
197 raise error.LookupError(
198 b'%d' % fileid, identifier, _(b'no match found')
198 b'%d' % fileid, identifier, _(b'no match found')
199 )
199 )
200
200
201 if len(fileid) == 20:
201 if len(fileid) in (20, 32):
202 try:
202 try:
203 store.rev(fileid)
203 store.rev(fileid)
204 return fileid
204 return fileid
205 except error.LookupError:
205 except error.LookupError:
206 pass
206 pass
207
207
208 if len(fileid) == 40:
208 if len(fileid) in (40, 64):
209 try:
209 try:
210 rawnode = bin(fileid)
210 rawnode = bin(fileid)
211 store.rev(rawnode)
211 store.rev(rawnode)
212 return rawnode
212 return rawnode
213 except TypeError:
213 except TypeError:
214 pass
214 pass
215
215
216 try:
216 try:
217 rev = int(fileid)
217 rev = int(fileid)
218
218
219 if b'%d' % rev != fileid:
219 if b'%d' % rev != fileid:
220 raise ValueError
220 raise ValueError
221
221
222 try:
222 try:
223 return store.node(rev)
223 return store.node(rev)
224 except (IndexError, TypeError):
224 except (IndexError, TypeError):
225 pass
225 pass
226 except (ValueError, OverflowError):
226 except (ValueError, OverflowError):
227 pass
227 pass
228
228
229 raise error.LookupError(fileid, identifier, _(b'no match found'))
229 raise error.LookupError(fileid, identifier, _(b'no match found'))
230
230
231
231
232 def resolvestripinfo(minlinkrev, tiprev, headrevs, linkrevfn, parentrevsfn):
232 def resolvestripinfo(minlinkrev, tiprev, headrevs, linkrevfn, parentrevsfn):
233 """Resolve information needed to strip revisions.
233 """Resolve information needed to strip revisions.
234
234
235 Finds the minimum revision number that must be stripped in order to
235 Finds the minimum revision number that must be stripped in order to
236 strip ``minlinkrev``.
236 strip ``minlinkrev``.
237
237
238 Returns a 2-tuple of the minimum revision number to do that and a set
238 Returns a 2-tuple of the minimum revision number to do that and a set
239 of all revision numbers that have linkrevs that would be broken
239 of all revision numbers that have linkrevs that would be broken
240 by that strip.
240 by that strip.
241
241
242 ``tiprev`` is the current tip-most revision. It is ``len(store) - 1``.
242 ``tiprev`` is the current tip-most revision. It is ``len(store) - 1``.
243 ``headrevs`` is an iterable of head revisions.
243 ``headrevs`` is an iterable of head revisions.
244 ``linkrevfn`` is a callable that receives a revision and returns a linked
244 ``linkrevfn`` is a callable that receives a revision and returns a linked
245 revision.
245 revision.
246 ``parentrevsfn`` is a callable that receives a revision number and returns
246 ``parentrevsfn`` is a callable that receives a revision number and returns
247 an iterable of its parent revision numbers.
247 an iterable of its parent revision numbers.
248 """
248 """
249 brokenrevs = set()
249 brokenrevs = set()
250 strippoint = tiprev + 1
250 strippoint = tiprev + 1
251
251
252 heads = {}
252 heads = {}
253 futurelargelinkrevs = set()
253 futurelargelinkrevs = set()
254 for head in headrevs:
254 for head in headrevs:
255 headlinkrev = linkrevfn(head)
255 headlinkrev = linkrevfn(head)
256 heads[head] = headlinkrev
256 heads[head] = headlinkrev
257 if headlinkrev >= minlinkrev:
257 if headlinkrev >= minlinkrev:
258 futurelargelinkrevs.add(headlinkrev)
258 futurelargelinkrevs.add(headlinkrev)
259
259
260 # This algorithm involves walking down the rev graph, starting at the
260 # This algorithm involves walking down the rev graph, starting at the
261 # heads. Since the revs are topologically sorted according to linkrev,
261 # heads. Since the revs are topologically sorted according to linkrev,
262 # once all head linkrevs are below the minlink, we know there are
262 # once all head linkrevs are below the minlink, we know there are
263 # no more revs that could have a linkrev greater than minlink.
263 # no more revs that could have a linkrev greater than minlink.
264 # So we can stop walking.
264 # So we can stop walking.
265 while futurelargelinkrevs:
265 while futurelargelinkrevs:
266 strippoint -= 1
266 strippoint -= 1
267 linkrev = heads.pop(strippoint)
267 linkrev = heads.pop(strippoint)
268
268
269 if linkrev < minlinkrev:
269 if linkrev < minlinkrev:
270 brokenrevs.add(strippoint)
270 brokenrevs.add(strippoint)
271 else:
271 else:
272 futurelargelinkrevs.remove(linkrev)
272 futurelargelinkrevs.remove(linkrev)
273
273
274 for p in parentrevsfn(strippoint):
274 for p in parentrevsfn(strippoint):
275 if p != nullrev:
275 if p != nullrev:
276 plinkrev = linkrevfn(p)
276 plinkrev = linkrevfn(p)
277 heads[p] = plinkrev
277 heads[p] = plinkrev
278 if plinkrev >= minlinkrev:
278 if plinkrev >= minlinkrev:
279 futurelargelinkrevs.add(plinkrev)
279 futurelargelinkrevs.add(plinkrev)
280
280
281 return strippoint, brokenrevs
281 return strippoint, brokenrevs
282
282
283
283
284 def emitrevisions(
284 def emitrevisions(
285 store,
285 store,
286 nodes,
286 nodes,
287 nodesorder,
287 nodesorder,
288 resultcls,
288 resultcls,
289 deltaparentfn=None,
289 deltaparentfn=None,
290 candeltafn=None,
290 candeltafn=None,
291 rawsizefn=None,
291 rawsizefn=None,
292 revdifffn=None,
292 revdifffn=None,
293 flagsfn=None,
293 flagsfn=None,
294 deltamode=repository.CG_DELTAMODE_STD,
294 deltamode=repository.CG_DELTAMODE_STD,
295 revisiondata=False,
295 revisiondata=False,
296 assumehaveparentrevisions=False,
296 assumehaveparentrevisions=False,
297 ):
297 ):
298 """Generic implementation of ifiledata.emitrevisions().
298 """Generic implementation of ifiledata.emitrevisions().
299
299
300 Emitting revision data is subtly complex. This function attempts to
300 Emitting revision data is subtly complex. This function attempts to
301 encapsulate all the logic for doing so in a backend-agnostic way.
301 encapsulate all the logic for doing so in a backend-agnostic way.
302
302
303 ``store``
303 ``store``
304 Object conforming to ``ifilestorage`` interface.
304 Object conforming to ``ifilestorage`` interface.
305
305
306 ``nodes``
306 ``nodes``
307 List of revision nodes whose data to emit.
307 List of revision nodes whose data to emit.
308
308
309 ``resultcls``
309 ``resultcls``
310 A type implementing the ``irevisiondelta`` interface that will be
310 A type implementing the ``irevisiondelta`` interface that will be
311 constructed and returned.
311 constructed and returned.
312
312
313 ``deltaparentfn`` (optional)
313 ``deltaparentfn`` (optional)
314 Callable receiving a revision number and returning the revision number
314 Callable receiving a revision number and returning the revision number
315 of a revision that the internal delta is stored against. This delta
315 of a revision that the internal delta is stored against. This delta
316 will be preferred over computing a new arbitrary delta.
316 will be preferred over computing a new arbitrary delta.
317
317
318 If not defined, a delta will always be computed from raw revision
318 If not defined, a delta will always be computed from raw revision
319 data.
319 data.
320
320
321 ``candeltafn`` (optional)
321 ``candeltafn`` (optional)
322 Callable receiving a pair of revision numbers that returns a bool
322 Callable receiving a pair of revision numbers that returns a bool
323 indicating whether a delta between them can be produced.
323 indicating whether a delta between them can be produced.
324
324
325 If not defined, it is assumed that any two revisions can delta with
325 If not defined, it is assumed that any two revisions can delta with
326 each other.
326 each other.
327
327
328 ``rawsizefn`` (optional)
328 ``rawsizefn`` (optional)
329 Callable receiving a revision number and returning the length of the
329 Callable receiving a revision number and returning the length of the
330 ``store.rawdata(rev)``.
330 ``store.rawdata(rev)``.
331
331
332 If not defined, ``len(store.rawdata(rev))`` will be called.
332 If not defined, ``len(store.rawdata(rev))`` will be called.
333
333
334 ``revdifffn`` (optional)
334 ``revdifffn`` (optional)
335 Callable receiving a pair of revision numbers that returns a delta
335 Callable receiving a pair of revision numbers that returns a delta
336 between them.
336 between them.
337
337
338 If not defined, a delta will be computed by invoking mdiff code
338 If not defined, a delta will be computed by invoking mdiff code
339 on ``store.revision()`` results.
339 on ``store.revision()`` results.
340
340
341 Defining this function allows a precomputed or stored delta to be
341 Defining this function allows a precomputed or stored delta to be
342 used without having to compute on.
342 used without having to compute on.
343
343
344 ``flagsfn`` (optional)
344 ``flagsfn`` (optional)
345 Callable receiving a revision number and returns the integer flags
345 Callable receiving a revision number and returns the integer flags
346 value for it. If not defined, flags value will be 0.
346 value for it. If not defined, flags value will be 0.
347
347
348 ``deltamode``
348 ``deltamode``
349 constaint on delta to be sent:
349 constaint on delta to be sent:
350 * CG_DELTAMODE_STD - normal mode, try to reuse storage deltas,
350 * CG_DELTAMODE_STD - normal mode, try to reuse storage deltas,
351 * CG_DELTAMODE_PREV - only delta against "prev",
351 * CG_DELTAMODE_PREV - only delta against "prev",
352 * CG_DELTAMODE_FULL - only issue full snapshot.
352 * CG_DELTAMODE_FULL - only issue full snapshot.
353
353
354 Whether to send fulltext revisions instead of deltas, if allowed.
354 Whether to send fulltext revisions instead of deltas, if allowed.
355
355
356 ``nodesorder``
356 ``nodesorder``
357 ``revisiondata``
357 ``revisiondata``
358 ``assumehaveparentrevisions``
358 ``assumehaveparentrevisions``
359 """
359 """
360
360
361 fnode = store.node
361 fnode = store.node
362 frev = store.rev
362 frev = store.rev
363
363
364 if nodesorder == b'nodes':
364 if nodesorder == b'nodes':
365 revs = [frev(n) for n in nodes]
365 revs = [frev(n) for n in nodes]
366 elif nodesorder == b'linear':
366 elif nodesorder == b'linear':
367 revs = {frev(n) for n in nodes}
367 revs = {frev(n) for n in nodes}
368 revs = dagop.linearize(revs, store.parentrevs)
368 revs = dagop.linearize(revs, store.parentrevs)
369 else: # storage and default
369 else: # storage and default
370 revs = sorted(frev(n) for n in nodes)
370 revs = sorted(frev(n) for n in nodes)
371
371
372 prevrev = None
372 prevrev = None
373
373
374 if deltamode == repository.CG_DELTAMODE_PREV or assumehaveparentrevisions:
374 if deltamode == repository.CG_DELTAMODE_PREV or assumehaveparentrevisions:
375 prevrev = store.parentrevs(revs[0])[0]
375 prevrev = store.parentrevs(revs[0])[0]
376
376
377 # Set of revs available to delta against.
377 # Set of revs available to delta against.
378 available = set()
378 available = set()
379
379
380 for rev in revs:
380 for rev in revs:
381 if rev == nullrev:
381 if rev == nullrev:
382 continue
382 continue
383
383
384 node = fnode(rev)
384 node = fnode(rev)
385 p1rev, p2rev = store.parentrevs(rev)
385 p1rev, p2rev = store.parentrevs(rev)
386
386
387 if deltaparentfn:
387 if deltaparentfn:
388 deltaparentrev = deltaparentfn(rev)
388 deltaparentrev = deltaparentfn(rev)
389 else:
389 else:
390 deltaparentrev = nullrev
390 deltaparentrev = nullrev
391
391
392 # Forced delta against previous mode.
392 # Forced delta against previous mode.
393 if deltamode == repository.CG_DELTAMODE_PREV:
393 if deltamode == repository.CG_DELTAMODE_PREV:
394 baserev = prevrev
394 baserev = prevrev
395
395
396 # We're instructed to send fulltext. Honor that.
396 # We're instructed to send fulltext. Honor that.
397 elif deltamode == repository.CG_DELTAMODE_FULL:
397 elif deltamode == repository.CG_DELTAMODE_FULL:
398 baserev = nullrev
398 baserev = nullrev
399 # We're instructed to use p1. Honor that
399 # We're instructed to use p1. Honor that
400 elif deltamode == repository.CG_DELTAMODE_P1:
400 elif deltamode == repository.CG_DELTAMODE_P1:
401 baserev = p1rev
401 baserev = p1rev
402
402
403 # There is a delta in storage. We try to use that because it
403 # There is a delta in storage. We try to use that because it
404 # amounts to effectively copying data from storage and is
404 # amounts to effectively copying data from storage and is
405 # therefore the fastest.
405 # therefore the fastest.
406 elif deltaparentrev != nullrev:
406 elif deltaparentrev != nullrev:
407 # Base revision was already emitted in this group. We can
407 # Base revision was already emitted in this group. We can
408 # always safely use the delta.
408 # always safely use the delta.
409 if deltaparentrev in available:
409 if deltaparentrev in available:
410 baserev = deltaparentrev
410 baserev = deltaparentrev
411
411
412 # Base revision is a parent that hasn't been emitted already.
412 # Base revision is a parent that hasn't been emitted already.
413 # Use it if we can assume the receiver has the parent revision.
413 # Use it if we can assume the receiver has the parent revision.
414 elif assumehaveparentrevisions and deltaparentrev in (p1rev, p2rev):
414 elif assumehaveparentrevisions and deltaparentrev in (p1rev, p2rev):
415 baserev = deltaparentrev
415 baserev = deltaparentrev
416
416
417 # No guarantee the receiver has the delta parent. Send delta
417 # No guarantee the receiver has the delta parent. Send delta
418 # against last revision (if possible), which in the common case
418 # against last revision (if possible), which in the common case
419 # should be similar enough to this revision that the delta is
419 # should be similar enough to this revision that the delta is
420 # reasonable.
420 # reasonable.
421 elif prevrev is not None:
421 elif prevrev is not None:
422 baserev = prevrev
422 baserev = prevrev
423 else:
423 else:
424 baserev = nullrev
424 baserev = nullrev
425
425
426 # Storage has a fulltext revision.
426 # Storage has a fulltext revision.
427
427
428 # Let's use the previous revision, which is as good a guess as any.
428 # Let's use the previous revision, which is as good a guess as any.
429 # There is definitely room to improve this logic.
429 # There is definitely room to improve this logic.
430 elif prevrev is not None:
430 elif prevrev is not None:
431 baserev = prevrev
431 baserev = prevrev
432 else:
432 else:
433 baserev = nullrev
433 baserev = nullrev
434
434
435 # But we can't actually use our chosen delta base for whatever
435 # But we can't actually use our chosen delta base for whatever
436 # reason. Reset to fulltext.
436 # reason. Reset to fulltext.
437 if baserev != nullrev and (candeltafn and not candeltafn(baserev, rev)):
437 if baserev != nullrev and (candeltafn and not candeltafn(baserev, rev)):
438 baserev = nullrev
438 baserev = nullrev
439
439
440 revision = None
440 revision = None
441 delta = None
441 delta = None
442 baserevisionsize = None
442 baserevisionsize = None
443
443
444 if revisiondata:
444 if revisiondata:
445 if store.iscensored(baserev) or store.iscensored(rev):
445 if store.iscensored(baserev) or store.iscensored(rev):
446 try:
446 try:
447 revision = store.rawdata(node)
447 revision = store.rawdata(node)
448 except error.CensoredNodeError as e:
448 except error.CensoredNodeError as e:
449 revision = e.tombstone
449 revision = e.tombstone
450
450
451 if baserev != nullrev:
451 if baserev != nullrev:
452 if rawsizefn:
452 if rawsizefn:
453 baserevisionsize = rawsizefn(baserev)
453 baserevisionsize = rawsizefn(baserev)
454 else:
454 else:
455 baserevisionsize = len(store.rawdata(baserev))
455 baserevisionsize = len(store.rawdata(baserev))
456
456
457 elif (
457 elif (
458 baserev == nullrev and deltamode != repository.CG_DELTAMODE_PREV
458 baserev == nullrev and deltamode != repository.CG_DELTAMODE_PREV
459 ):
459 ):
460 revision = store.rawdata(node)
460 revision = store.rawdata(node)
461 available.add(rev)
461 available.add(rev)
462 else:
462 else:
463 if revdifffn:
463 if revdifffn:
464 delta = revdifffn(baserev, rev)
464 delta = revdifffn(baserev, rev)
465 else:
465 else:
466 delta = mdiff.textdiff(
466 delta = mdiff.textdiff(
467 store.rawdata(baserev), store.rawdata(rev)
467 store.rawdata(baserev), store.rawdata(rev)
468 )
468 )
469
469
470 available.add(rev)
470 available.add(rev)
471
471
472 yield resultcls(
472 yield resultcls(
473 node=node,
473 node=node,
474 p1node=fnode(p1rev),
474 p1node=fnode(p1rev),
475 p2node=fnode(p2rev),
475 p2node=fnode(p2rev),
476 basenode=fnode(baserev),
476 basenode=fnode(baserev),
477 flags=flagsfn(rev) if flagsfn else 0,
477 flags=flagsfn(rev) if flagsfn else 0,
478 baserevisionsize=baserevisionsize,
478 baserevisionsize=baserevisionsize,
479 revision=revision,
479 revision=revision,
480 delta=delta,
480 delta=delta,
481 )
481 )
482
482
483 prevrev = rev
483 prevrev = rev
484
484
485
485
486 def deltaiscensored(delta, baserev, baselenfn):
486 def deltaiscensored(delta, baserev, baselenfn):
487 """Determine if a delta represents censored revision data.
487 """Determine if a delta represents censored revision data.
488
488
489 ``baserev`` is the base revision this delta is encoded against.
489 ``baserev`` is the base revision this delta is encoded against.
490 ``baselenfn`` is a callable receiving a revision number that resolves the
490 ``baselenfn`` is a callable receiving a revision number that resolves the
491 length of the revision fulltext.
491 length of the revision fulltext.
492
492
493 Returns a bool indicating if the result of the delta represents a censored
493 Returns a bool indicating if the result of the delta represents a censored
494 revision.
494 revision.
495 """
495 """
496 # Fragile heuristic: unless new file meta keys are added alphabetically
496 # Fragile heuristic: unless new file meta keys are added alphabetically
497 # preceding "censored", all censored revisions are prefixed by
497 # preceding "censored", all censored revisions are prefixed by
498 # "\1\ncensored:". A delta producing such a censored revision must be a
498 # "\1\ncensored:". A delta producing such a censored revision must be a
499 # full-replacement delta, so we inspect the first and only patch in the
499 # full-replacement delta, so we inspect the first and only patch in the
500 # delta for this prefix.
500 # delta for this prefix.
501 hlen = struct.calcsize(b">lll")
501 hlen = struct.calcsize(b">lll")
502 if len(delta) <= hlen:
502 if len(delta) <= hlen:
503 return False
503 return False
504
504
505 oldlen = baselenfn(baserev)
505 oldlen = baselenfn(baserev)
506 newlen = len(delta) - hlen
506 newlen = len(delta) - hlen
507 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
507 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
508 return False
508 return False
509
509
510 add = b"\1\ncensored:"
510 add = b"\1\ncensored:"
511 addlen = len(add)
511 addlen = len(add)
512 return newlen >= addlen and delta[hlen : hlen + addlen] == add
512 return newlen >= addlen and delta[hlen : hlen + addlen] == add
General Comments 0
You need to be logged in to leave comments. Login now