##// END OF EJS Templates
revlog: flag processor...
Remi Chaintron -
r30745:c1b7b228 default
parent child Browse files
Show More
@@ -0,0 +1,137 b''
1 # coding=UTF-8
2
3 from __future__ import absolute_import
4
5 import base64
6 import zlib
7
8 from mercurial import (
9 changegroup,
10 extensions,
11 filelog,
12 revlog,
13 util,
14 )
15
16 # Test only: These flags are defined here only in the context of testing the
17 # behavior of the flag processor. The canonical way to add flags is to get in
18 # touch with the community and make them known in revlog.
19 REVIDX_NOOP = (1 << 3)
20 REVIDX_BASE64 = (1 << 2)
21 REVIDX_GZIP = (1 << 1)
22 REVIDX_FAIL = 1
23
24 def validatehash(self, text):
25 return True
26
27 def bypass(self, text):
28 return False
29
30 def noopdonothing(self, text):
31 return (text, True)
32
33 def b64encode(self, text):
34 return (base64.b64encode(text), False)
35
36 def b64decode(self, text):
37 return (base64.b64decode(text), True)
38
39 def gzipcompress(self, text):
40 return (zlib.compress(text), False)
41
42 def gzipdecompress(self, text):
43 return (zlib.decompress(text), True)
44
45 def supportedoutgoingversions(orig, repo):
46 versions = orig(repo)
47 versions.discard('01')
48 versions.discard('02')
49 versions.add('03')
50 return versions
51
52 def allsupportedversions(orig, ui):
53 versions = orig(ui)
54 versions.add('03')
55 return versions
56
57 def noopaddrevision(orig, self, text, transaction, link, p1, p2,
58 cachedelta=None, node=None,
59 flags=revlog.REVIDX_DEFAULT_FLAGS):
60 if '[NOOP]' in text:
61 flags |= REVIDX_NOOP
62 return orig(self, text, transaction, link, p1, p2, cachedelta=cachedelta,
63 node=node, flags=flags)
64
65 def b64addrevision(orig, self, text, transaction, link, p1, p2,
66 cachedelta=None, node=None,
67 flags=revlog.REVIDX_DEFAULT_FLAGS):
68 if '[BASE64]' in text:
69 flags |= REVIDX_BASE64
70 return orig(self, text, transaction, link, p1, p2, cachedelta=cachedelta,
71 node=node, flags=flags)
72
73 def gzipaddrevision(orig, self, text, transaction, link, p1, p2,
74 cachedelta=None, node=None,
75 flags=revlog.REVIDX_DEFAULT_FLAGS):
76 if '[GZIP]' in text:
77 flags |= REVIDX_GZIP
78 return orig(self, text, transaction, link, p1, p2, cachedelta=cachedelta,
79 node=node, flags=flags)
80
81 def failaddrevision(orig, self, text, transaction, link, p1, p2,
82 cachedelta=None, node=None,
83 flags=revlog.REVIDX_DEFAULT_FLAGS):
84 # This addrevision wrapper is meant to add a flag we will not have
85 # transforms registered for, ensuring we handle this error case.
86 if '[FAIL]' in text:
87 flags |= REVIDX_FAIL
88 return orig(self, text, transaction, link, p1, p2, cachedelta=cachedelta,
89 node=node, flags=flags)
90
91 def extsetup(ui):
92 # Enable changegroup3 for flags to be sent over the wire
93 wrapfunction = extensions.wrapfunction
94 wrapfunction(changegroup,
95 'supportedoutgoingversions',
96 supportedoutgoingversions)
97 wrapfunction(changegroup,
98 'allsupportedversions',
99 allsupportedversions)
100
101 # Teach revlog about our test flags
102 flags = [REVIDX_NOOP, REVIDX_BASE64, REVIDX_GZIP, REVIDX_FAIL]
103 revlog.REVIDX_KNOWN_FLAGS |= util.bitsfrom(flags)
104 revlog.REVIDX_FLAGS_ORDER.extend(flags)
105
106 # Add wrappers for addrevision, responsible to set flags depending on the
107 # revision data contents.
108 wrapfunction(filelog.filelog, 'addrevision', noopaddrevision)
109 wrapfunction(filelog.filelog, 'addrevision', b64addrevision)
110 wrapfunction(filelog.filelog, 'addrevision', gzipaddrevision)
111 wrapfunction(filelog.filelog, 'addrevision', failaddrevision)
112
113 # Register flag processors for each extension
114 revlog.addflagprocessor(
115 REVIDX_NOOP,
116 (
117 noopdonothing,
118 noopdonothing,
119 validatehash,
120 )
121 )
122 revlog.addflagprocessor(
123 REVIDX_BASE64,
124 (
125 b64decode,
126 b64encode,
127 bypass,
128 ),
129 )
130 revlog.addflagprocessor(
131 REVIDX_GZIP,
132 (
133 gzipdecompress,
134 gzipcompress,
135 bypass
136 )
137 )
@@ -0,0 +1,165 b''
1 # Create server
2 $ hg init server
3 $ cd server
4 $ cat >> .hg/hgrc << EOF
5 > [extensions]
6 > extension=$TESTDIR/flagprocessorext.py
7 > EOF
8 $ cd ../
9
10 # Clone server and enable extensions
11 $ hg clone -q server client
12 $ cd client
13 $ cat >> .hg/hgrc << EOF
14 > [extensions]
15 > extension=$TESTDIR/flagprocessorext.py
16 > EOF
17
18 # Commit file that will trigger the noop extension
19 $ echo '[NOOP]' > noop
20 $ hg commit -Aqm "noop"
21
22 # Commit file that will trigger the base64 extension
23 $ echo '[BASE64]' > base64
24 $ hg commit -Aqm 'base64'
25
26 # Commit file that will trigger the gzip extension
27 $ echo '[GZIP]' > gzip
28 $ hg commit -Aqm 'gzip'
29
30 # Commit file that will trigger noop and base64
31 $ echo '[NOOP][BASE64]' > noop-base64
32 $ hg commit -Aqm 'noop+base64'
33
34 # Commit file that will trigger noop and gzip
35 $ echo '[NOOP][GZIP]' > noop-gzip
36 $ hg commit -Aqm 'noop+gzip'
37
38 # Commit file that will trigger base64 and gzip
39 $ echo '[BASE64][GZIP]' > base64-gzip
40 $ hg commit -Aqm 'base64+gzip'
41
42 # Commit file that will trigger base64, gzip and noop
43 $ echo '[BASE64][GZIP][NOOP]' > base64-gzip-noop
44 $ hg commit -Aqm 'base64+gzip+noop'
45
46 # TEST: ensure the revision data is consistent
47 $ hg cat noop
48 [NOOP]
49 $ hg debugdata noop 0
50 [NOOP]
51
52 $ hg cat -r . base64
53 [BASE64]
54 $ hg debugdata base64 0
55 W0JBU0U2NF0K (no-eol)
56
57 $ hg cat -r . gzip
58 [GZIP]
59 $ hg debugdata gzip 0
60 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
61
62 $ hg cat -r . noop-base64
63 [NOOP][BASE64]
64 $ hg debugdata noop-base64 0
65 W05PT1BdW0JBU0U2NF0K (no-eol)
66
67 $ hg cat -r . noop-gzip
68 [NOOP][GZIP]
69 $ hg debugdata noop-gzip 0
70 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
71
72 $ hg cat -r . base64-gzip
73 [BASE64][GZIP]
74 $ hg debugdata base64-gzip 0
75 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
76
77 $ hg cat -r . base64-gzip-noop
78 [BASE64][GZIP][NOOP]
79 $ hg debugdata base64-gzip-noop 0
80 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
81
82 # Push to the server
83 $ hg push
84 pushing to $TESTTMP/server (glob)
85 searching for changes
86 adding changesets
87 adding manifests
88 adding file changes
89 added 7 changesets with 7 changes to 7 files
90
91 # Initialize new client (not cloning) and setup extension
92 $ cd ..
93 $ hg init client2
94 $ cd client2
95 $ cat >> .hg/hgrc << EOF
96 > [paths]
97 > default = $TESTTMP/server
98 > [extensions]
99 > extension=$TESTDIR/flagprocessorext.py
100 > EOF
101
102 # Pull from server and update to latest revision
103 $ hg pull default
104 pulling from $TESTTMP/server (glob)
105 requesting all changes
106 adding changesets
107 adding manifests
108 adding file changes
109 added 7 changesets with 7 changes to 7 files
110 (run 'hg update' to get a working copy)
111 $ hg update
112 7 files updated, 0 files merged, 0 files removed, 0 files unresolved
113
114 # TEST: ensure the revision data is consistent
115 $ hg cat noop
116 [NOOP]
117 $ hg debugdata noop 0
118 [NOOP]
119
120 $ hg cat -r . base64
121 [BASE64]
122 $ hg debugdata base64 0
123 W0JBU0U2NF0K (no-eol)
124
125 $ hg cat -r . gzip
126 [GZIP]
127 $ hg debugdata gzip 0
128 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
129
130 $ hg cat -r . noop-base64
131 [NOOP][BASE64]
132 $ hg debugdata noop-base64 0
133 W05PT1BdW0JBU0U2NF0K (no-eol)
134
135 $ hg cat -r . noop-gzip
136 [NOOP][GZIP]
137 $ hg debugdata noop-gzip 0
138 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
139
140 $ hg cat -r . base64-gzip
141 [BASE64][GZIP]
142 $ hg debugdata base64-gzip 0
143 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
144
145 $ hg cat -r . base64-gzip-noop
146 [BASE64][GZIP][NOOP]
147 $ hg debugdata base64-gzip-noop 0
148 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
149
150 # TEST: ensure a missing processor is handled
151 $ echo '[FAIL][BASE64][GZIP][NOOP]' > fail-base64-gzip-noop
152 $ hg commit -Aqm 'fail+base64+gzip+noop'
153 abort: missing processor for flag '0x1'!
154 [255]
155
156 # TEST: ensure we cannot register several flag processors on the same flag
157 $ cat >> .hg/hgrc << EOF
158 > [extensions]
159 > extension=$TESTDIR/flagprocessorext.py
160 > duplicate=$TESTDIR/flagprocessorext.py
161 > EOF
162 $ echo 'this should fail' > file
163 $ hg commit -Aqm 'add file'
164 abort: cannot register multiple processors on flag '0x8'.
165 [255]
@@ -148,6 +148,9 b' class bundlerevlog(revlog.revlog):'
148 delta = self._chunk(chain.pop())
148 delta = self._chunk(chain.pop())
149 text = mdiff.patches(text, [delta])
149 text = mdiff.patches(text, [delta])
150
150
151 text, validatehash = self._processflags(text, self.flags(rev),
152 'read', raw=raw)
153 if validatehash:
151 self.checkhash(text, node, rev=rev)
154 self.checkhash(text, node, rev=rev)
152 self._cache = (node, rev, text)
155 self._cache = (node, rev, text)
153 return text
156 return text
@@ -55,7 +55,11 b' REVLOGNG_FLAGS = REVLOGNGINLINEDATA | RE'
55 # revlog index flags
55 # revlog index flags
56 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
56 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
57 REVIDX_DEFAULT_FLAGS = 0
57 REVIDX_DEFAULT_FLAGS = 0
58 REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED
58 # stable order in which flags need to be processed and their processors applied
59 REVIDX_FLAGS_ORDER = [
60 REVIDX_ISCENSORED,
61 ]
62 REVIDX_KNOWN_FLAGS = util.bitsfrom(REVIDX_FLAGS_ORDER)
59
63
60 # max size of revlog with inline data
64 # max size of revlog with inline data
61 _maxinline = 131072
65 _maxinline = 131072
@@ -64,6 +68,41 b' REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED'
64 RevlogError = error.RevlogError
68 RevlogError = error.RevlogError
65 LookupError = error.LookupError
69 LookupError = error.LookupError
66 CensoredNodeError = error.CensoredNodeError
70 CensoredNodeError = error.CensoredNodeError
71 ProgrammingError = error.ProgrammingError
72
73 # Store flag processors (cf. 'addflagprocessor()' to register)
74 _flagprocessors = {
75 REVIDX_ISCENSORED: None,
76 }
77
78 def addflagprocessor(flag, processor):
79 """Register a flag processor on a revision data flag.
80
81 Invariant:
82 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER.
83 - Only one flag processor can be registered on a specific flag.
84 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
85 following signatures:
86 - (read) f(self, text) -> newtext, bool
87 - (write) f(self, text) -> newtext, bool
88 - (raw) f(self, text) -> bool
89 The boolean returned by these transforms is used to determine whether
90 'newtext' can be used for hash integrity checking.
91
92 Note: The 'raw' transform is used for changegroup generation and in some
93 debug commands. In this case the transform only indicates whether the
94 contents can be used for hash integrity checks.
95 """
96 if not flag & REVIDX_KNOWN_FLAGS:
97 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
98 raise ProgrammingError(msg)
99 if flag not in REVIDX_FLAGS_ORDER:
100 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
101 raise ProgrammingError(msg)
102 if flag in _flagprocessors:
103 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
104 raise error.Abort(msg)
105 _flagprocessors[flag] = processor
67
106
68 def getoffset(q):
107 def getoffset(q):
69 return int(q >> 16)
108 return int(q >> 16)
@@ -1231,11 +1270,6 b' class revlog(object):'
1231 if rev is None:
1270 if rev is None:
1232 rev = self.rev(node)
1271 rev = self.rev(node)
1233
1272
1234 # check rev flags
1235 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
1236 raise RevlogError(_('incompatible revision flag %x') %
1237 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
1238
1239 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1273 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1240 if stopped:
1274 if stopped:
1241 text = self._cache[2]
1275 text = self._cache[2]
@@ -1249,7 +1283,12 b' class revlog(object):'
1249 bins = bins[1:]
1283 bins = bins[1:]
1250
1284
1251 text = mdiff.patches(text, bins)
1285 text = mdiff.patches(text, bins)
1286
1287 text, validatehash = self._processflags(text, self.flags(rev), 'read',
1288 raw=raw)
1289 if validatehash:
1252 self.checkhash(text, node, rev=rev)
1290 self.checkhash(text, node, rev=rev)
1291
1253 self._cache = (node, rev, text)
1292 self._cache = (node, rev, text)
1254 return text
1293 return text
1255
1294
@@ -1261,6 +1300,65 b' class revlog(object):'
1261 """
1300 """
1262 return hash(text, p1, p2)
1301 return hash(text, p1, p2)
1263
1302
1303 def _processflags(self, text, flags, operation, raw=False):
1304 """Inspect revision data flags and applies transforms defined by
1305 registered flag processors.
1306
1307 ``text`` - the revision data to process
1308 ``flags`` - the revision flags
1309 ``operation`` - the operation being performed (read or write)
1310 ``raw`` - an optional argument describing if the raw transform should be
1311 applied.
1312
1313 This method processes the flags in the order (or reverse order if
1314 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
1315 flag processors registered for present flags. The order of flags defined
1316 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
1317
1318 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
1319 processed text and ``validatehash`` is a bool indicating whether the
1320 returned text should be checked for hash integrity.
1321
1322 Note: If the ``raw`` argument is set, it has precedence over the
1323 operation and will only update the value of ``validatehash``.
1324 """
1325 if not operation in ('read', 'write'):
1326 raise ProgrammingError(_("invalid '%s' operation ") % (operation))
1327 # Check all flags are known.
1328 if flags & ~REVIDX_KNOWN_FLAGS:
1329 raise RevlogError(_("incompatible revision flag '%#x'") %
1330 (flags & ~REVIDX_KNOWN_FLAGS))
1331 validatehash = True
1332 # Depending on the operation (read or write), the order might be
1333 # reversed due to non-commutative transforms.
1334 orderedflags = REVIDX_FLAGS_ORDER
1335 if operation == 'write':
1336 orderedflags = reversed(orderedflags)
1337
1338 for flag in orderedflags:
1339 # If a flagprocessor has been registered for a known flag, apply the
1340 # related operation transform and update result tuple.
1341 if flag & flags:
1342 vhash = True
1343
1344 if flag not in _flagprocessors:
1345 message = _("missing processor for flag '%#x'") % (flag)
1346 raise RevlogError(message)
1347
1348 processor = _flagprocessors[flag]
1349 if processor is not None:
1350 readtransform, writetransform, rawtransform = processor
1351
1352 if raw:
1353 vhash = rawtransform(self, text)
1354 elif operation == 'read':
1355 text, vhash = readtransform(self, text)
1356 else: # write operation
1357 text, vhash = writetransform(self, text)
1358 validatehash = validatehash and vhash
1359
1360 return text, validatehash
1361
1264 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1362 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1265 """Check node hash integrity.
1363 """Check node hash integrity.
1266
1364
@@ -1345,6 +1443,17 b' class revlog(object):'
1345 raise RevlogError(_("attempted to add linkrev -1 to %s")
1443 raise RevlogError(_("attempted to add linkrev -1 to %s")
1346 % self.indexfile)
1444 % self.indexfile)
1347
1445
1446 if flags:
1447 node = node or self.hash(text, p1, p2)
1448
1449 newtext, validatehash = self._processflags(text, flags, 'write')
1450
1451 # If the flag processor modifies the revision data, ignore any provided
1452 # cachedelta.
1453 if newtext != text:
1454 cachedelta = None
1455 text = newtext
1456
1348 if len(text) > _maxentrysize:
1457 if len(text) > _maxentrysize:
1349 raise RevlogError(
1458 raise RevlogError(
1350 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1459 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
@@ -1354,6 +1463,9 b' class revlog(object):'
1354 if node in self.nodemap:
1463 if node in self.nodemap:
1355 return node
1464 return node
1356
1465
1466 if validatehash:
1467 self.checkhash(text, node, p1=p1, p2=p2)
1468
1357 dfh = None
1469 dfh = None
1358 if not self._inline:
1470 if not self._inline:
1359 dfh = self.opener(self.datafile, "a+")
1471 dfh = self.opener(self.datafile, "a+")
@@ -1448,6 +1560,9 b' class revlog(object):'
1448 btext[0] = mdiff.patch(basetext, delta)
1560 btext[0] = mdiff.patch(basetext, delta)
1449
1561
1450 try:
1562 try:
1563 res = self._processflags(btext[0], flags, 'read', raw=raw)
1564 btext[0], validatehash = res
1565 if validatehash:
1451 self.checkhash(btext[0], node, p1=p1, p2=p2)
1566 self.checkhash(btext[0], node, p1=p1, p2=p2)
1452 if flags & REVIDX_ISCENSORED:
1567 if flags & REVIDX_ISCENSORED:
1453 raise RevlogError(_('node %s is not censored') % node)
1568 raise RevlogError(_('node %s is not censored') % node)
@@ -138,6 +138,12 b' os.stat_float_times(False)'
138 def safehasattr(thing, attr):
138 def safehasattr(thing, attr):
139 return getattr(thing, attr, _notset) is not _notset
139 return getattr(thing, attr, _notset) is not _notset
140
140
141 def bitsfrom(container):
142 bits = 0
143 for bit in container:
144 bits |= bit
145 return bits
146
141 DIGESTS = {
147 DIGESTS = {
142 'md5': hashlib.md5,
148 'md5': hashlib.md5,
143 'sha1': hashlib.sha1,
149 'sha1': hashlib.sha1,
General Comments 0
You need to be logged in to leave comments. Login now