##// END OF EJS Templates
branching: merge stable into default
Raphaël Gomès -
r51712:3ccef790 merge default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,250 +1,251 b''
1 1 35fb62a3a673d5322f6274a44ba6456e5e4b3b37 0 iD8DBQBEYmO2ywK+sNU5EO8RAnaYAKCO7x15xUn5mnhqWNXqk/ehlhRt2QCfRDfY0LrUq2q4oK/KypuJYPHgq1A=
2 2 2be3001847cb18a23c403439d9e7d0ace30804e9 0 iD8DBQBExUbjywK+sNU5EO8RAhzxAKCtyHAQUzcTSZTqlfJ0by6vhREwWQCghaQFHfkfN0l9/40EowNhuMOKnJk=
3 3 36a957364b1b89c150f2d0e60a99befe0ee08bd3 0 iD8DBQBFfL2QywK+sNU5EO8RAjYFAKCoGlaWRTeMsjdmxAjUYx6diZxOBwCfY6IpBYsKvPTwB3oktnPt5Rmrlys=
4 4 27230c29bfec36d5540fbe1c976810aefecfd1d2 0 iD8DBQBFheweywK+sNU5EO8RAt7VAKCrqJQWT2/uo2RWf0ZI4bLp6v82jACgjrMdsaTbxRsypcmEsdPhlG6/8F4=
5 5 fb4b6d5fe100b0886f8bc3d6731ec0e5ed5c4694 0 iD8DBQBGgHicywK+sNU5EO8RAgNxAJ0VG8ixAaeudx4sZbhngI1syu49HQCeNUJQfWBgA8bkJ2pvsFpNxwYaX3I=
6 6 23889160905a1b09fffe1c07378e9fc1827606eb 0 iD8DBQBHGTzoywK+sNU5EO8RAr/UAJ0Y8s4jQtzgS+G9vM8z6CWBThZ8fwCcCT5XDj2XwxKkz/0s6UELwjsO3LU=
7 7 bae2e9c838e90a393bae3973a7850280413e091a 0 iD8DBQBH6DO5ywK+sNU5EO8RAsfrAJ0e4r9c9GF/MJsM7Xjd3NesLRC3+ACffj6+6HXdZf8cswAoFPO+DY00oD0=
8 8 d5cbbe2c49cee22a9fbeb9ea41daa0ac4e26b846 0 iD8DBQBINdwsywK+sNU5EO8RAjIUAKCPmlFJSpsPAAUKF+iNHAwVnwmzeQCdEXrL27CWclXuUKdbQC8De7LICtE=
9 9 d2375bbee6d47e62ba8e415c86e83a465dc4dce9 0 iD8DBQBIo1wpywK+sNU5EO8RAmRNAJ94x3OFt6blbqu/yBoypm/AJ44fuACfUaldXcV5z9tht97hSp22DVTEPGc=
10 10 2a67430f92f15ea5159c26b09ec4839a0c549a26 0 iEYEABECAAYFAkk1hykACgkQywK+sNU5EO85QACeNJNUanjc2tl4wUoPHNuv+lSj0ZMAoIm93wSTc/feyYnO2YCaQ1iyd9Nu
11 11 3773e510d433969e277b1863c317b674cbee2065 0 iEYEABECAAYFAklNbbAACgkQywK+sNU5EO8o+gCfeb2/lfIJZMvyDA1m+G1CsBAxfFsAoIa6iAMG8SBY7hW1Q85Yf/LXEvaE
12 12 11a4eb81fb4f4742451591489e2797dc47903277 0 iEYEABECAAYFAklcAnsACgkQywK+sNU5EO+uXwCbBVHNNsLy1g7BlAyQJwadYVyHOXoAoKvtAVO71+bv7EbVoukwTzT+P4Sx
13 13 11efa41037e280d08cfb07c09ad485df30fb0ea8 0 iEYEABECAAYFAkmvJRQACgkQywK+sNU5EO9XZwCeLMgDgPSMWMm6vgjL4lDs2pEc5+0AnRxfiFbpbBfuEFTqKz9nbzeyoBlx
14 14 02981000012e3adf40c4849bd7b3d5618f9ce82d 0 iEYEABECAAYFAknEH3wACgkQywK+sNU5EO+uXwCeI+LbLMmhjU1lKSfU3UWJHjjUC7oAoIZLvYDGOL/tNZFUuatc3RnZ2eje
15 15 196d40e7c885fa6e95f89134809b3ec7bdbca34b 0 iEYEABECAAYFAkpL2X4ACgkQywK+sNU5EO9FOwCfXJycjyKJXsvQqKkHrglwOQhEKS4An36GfKzptfN8b1qNc3+ya/5c2WOM
16 16 3ef6c14a1e8e83a31226f5881b7fe6095bbfa6f6 0 iEYEABECAAYFAkpopLIACgkQywK+sNU5EO8QSgCfZ0ztsd071rOa2lhmp9Fyue/WoI0AoLTei80/xrhRlB8L/rZEf2KBl8dA
17 17 31ec469f9b556f11819937cf68ee53f2be927ebf 0 iEYEABECAAYFAksBuxAACgkQywK+sNU5EO+mBwCfagB+A0txzWZ6dRpug3LEoK7Z1QsAoKpbk8vsLjv6/oRDicSk/qBu33+m
18 18 439d7ea6fe3aa4ab9ec274a68846779153789de9 0 iEYEABECAAYFAksVw0kACgkQywK+sNU5EO/oZwCfdfBEkgp38xq6wN2F4nj+SzofrJIAnjmxt04vaJSeOOeHylHvk6lzuQsw
19 19 296a0b14a68621f6990c54fdba0083f6f20935bf 0 iEYEABECAAYFAks+jCoACgkQywK+sNU5EO9J8wCeMUGF9E/gS2UBsqIz56WS4HMPRPUAoI5J95mwEIK8Clrl7qFRidNI6APq
20 20 4aa619c4c2c09907034d9824ebb1dd0e878206eb 0 iEYEABECAAYFAktm9IsACgkQywK+sNU5EO9XGgCgk4HclRQhexEtooPE5GcUCdB6M8EAn2ptOhMVbIoO+JncA+tNACPFXh0O
21 21 ff2704a8ded37fbebd8b6eb5ec733731d725da8a 0 iEYEABECAAYFAkuRoSQACgkQywK+sNU5EO//3QCeJDc5r2uFyFCtAlpSA27DEE5rrxAAn2FSwTy9fhrB3QAdDQlwkEZcQzDh
22 22 2b01dab594167bc0dd33331dbaa6dca3dca1b3aa 0 iEYEABECAAYFAku1IwIACgkQywK+sNU5EO9MjgCdHLVwkTZlNHxhcznZKBL1rjN+J7cAoLLWi9LTL6f/TgBaPSKOy1ublbaW
23 23 39f725929f0c48c5fb3b90c071fc3066012456ca 0 iEYEABECAAYFAkvclvsACgkQywK+sNU5EO9FSwCeL9i5x8ALW/LE5+lCX6MFEAe4MhwAn1ev5o6SX6GrNdDfKweiemfO2VBk
24 24 fdcf80f26604f233dc4d8f0a5ef9d7470e317e8a 0 iEYEABECAAYFAkvsKTkACgkQywK+sNU5EO9qEACgiSiRGvTG2vXGJ65tUSOIYihTuFAAnRzRIqEVSw8M8/RGeUXRps0IzaCO
25 25 24fe2629c6fd0c74c90bd066e77387c2b02e8437 0 iEYEABECAAYFAkwFLRsACgkQywK+sNU5EO+pJACgp13tPI+pbwKZV+LeMjcQ4H6tCZYAoJebzhd6a8yYx6qiwpJxA9BXZNXy
26 26 f786fc4b8764cd2a5526d259cf2f94d8a66924d9 0 iEYEABECAAYFAkwsyxcACgkQywK+sNU5EO+crACfUpNAF57PmClkSri9nJcBjb2goN4AniPCNaKvnki7TnUsi1u2oxltpKKL
27 27 bf1774d95bde614af3956d92b20e2a0c68c5fec7 0 iEYEABECAAYFAkxVwccACgkQywK+sNU5EO+oFQCeJzwZ+we1fIIyBGCddHceOUAN++cAnjvT6A8ZWW0zV21NXIFF1qQmjxJd
28 28 c00f03a4982e467fb6b6bd45908767db6df4771d 0 iEYEABECAAYFAkxXDqsACgkQywK+sNU5EO/GJACfT9Rz4hZOxPQEs91JwtmfjevO84gAmwSmtfo5mmWSm8gtTUebCcdTv0Kf
29 29 ff5cec76b1c5b6be9c3bb923aae8c3c6d079d6b9 0 iD8DBQBMdo+qywK+sNU5EO8RAqQpAJ975BL2CCAiWMz9SXthNQ9xG181IwCgp4O+KViHPkufZVFn2aTKMNvcr1A=
30 30 93d8bff78c96fe7e33237b257558ee97290048a4 0 iD8DBQBMpfvdywK+sNU5EO8RAsxVAJ0UaL1XB51C76JUBhafc9GBefuMxwCdEWkTOzwvE0SarJBe9i008jhbqW4=
31 31 333421b9e0f96c7bc788e5667c146a58a9440a55 0 iD8DBQBMz0HOywK+sNU5EO8RAlsEAJ0USh6yOG7OrWkADGunVt9QimBQnwCbBqeMnKgSbwEw8jZwE3Iz1mdrYlo=
32 32 4438875ec01bd0fc32be92b0872eb6daeed4d44f 0 iD8DBQBM4WYUywK+sNU5EO8RAhCVAJ0dJswachwFAHALmk1x0RJehxzqPQCbBNskP9n/X689jB+btNTZTyKU/fw=
33 33 6aff4f144ad356311318b0011df0bb21f2c97429 0 iD8DBQBM9uxXywK+sNU5EO8RAv+4AKCDj4qKP16GdPaq1tP6BUwpM/M1OACfRyzLPp/qiiN8xJTWoWYSe/XjJug=
34 34 e3bf16703e2601de99e563cdb3a5d50b64e6d320 0 iD8DBQBNH8WqywK+sNU5EO8RAiQTAJ9sBO+TeiGro4si77VVaQaA6jcRUgCfSA28dBbjj0oFoQwvPoZjANiZBH8=
35 35 a6c855c32ea081da3c3b8ff628f1847ff271482f 0 iD8DBQBNSJJ+ywK+sNU5EO8RAoJaAKCweDEF70fu+r1Zn7pYDXdlk5RuSgCeO9gK/eit8Lin/1n3pO7aYguFLok=
36 36 2b2155623ee2559caf288fd333f30475966c4525 0 iD8DBQBNSJeBywK+sNU5EO8RAm1KAJ4hW9Cm9nHaaGJguchBaPLlAr+O3wCgqgmMok8bdAS06N6PL60PSTM//Gg=
37 37 2616325766e3504c8ae7c84bd15ee610901fe91d 0 iD8DBQBNbWy9ywK+sNU5EO8RAlWCAJ4mW8HbzjJj9GpK98muX7k+7EvEHwCfaTLbC/DH3QEsZBhEP+M8tzL6RU4=
38 38 aa1f3be38ab127280761889d2dca906ca465b5f4 0 iD8DBQBNeQq7ywK+sNU5EO8RAlEOAJ4tlEDdetE9lKfjGgjbkcR8PrC3egCfXCfF3qNVvU/2YYjpgvRwevjvDy0=
39 39 b032bec2c0a651ca0ddecb65714bfe6770f67d70 0 iD8DBQBNlg5kywK+sNU5EO8RAnGEAJ9gmEx6MfaR4XcG2m/93vwtfyzs3gCgltzx8/YdHPwqDwRX/WbpYgi33is=
40 40 3cb1e95676ad089596bd81d0937cad37d6e3b7fb 0 iD8DBQBNvTy4ywK+sNU5EO8RAmp8AJ9QnxK4jTJ7G722MyeBxf0UXEdGwACgtlM7BKtNQfbEH/fOW5y+45W88VI=
41 41 733af5d9f6b22387913e1d11350fb8cb7c1487dd 0 iD8DBQBN5q/8ywK+sNU5EO8RArRGAKCNGT94GKIYtSuwZ57z1sQbcw6uLACfffpbMV4NAPMl8womAwg+7ZPKnIU=
42 42 de9eb6b1da4fc522b1cab16d86ca166204c24f25 0 iD8DBQBODhfhywK+sNU5EO8RAr2+AJ4ugbAj8ae8/K0bYZzx3sascIAg1QCeK3b+zbbVVqd3b7CDpwFnaX8kTd4=
43 43 4a43e23b8c55b4566b8200bf69fe2158485a2634 0 iD8DBQBONzIMywK+sNU5EO8RAj5SAJ0aPS3+JHnyI6bHB2Fl0LImbDmagwCdGbDLp1S7TFobxXudOH49bX45Iik=
44 44 d629f1e89021103f1753addcef6b310e4435b184 0 iD8DBQBOWAsBywK+sNU5EO8RAht4AJwJl9oNFopuGkj5m8aKuf7bqPkoAQCeNrEm7UhFsZKYT5iUOjnMV7s2LaM=
45 45 351a9292e430e35766c552066ed3e87c557b803b 0 iD8DBQBOh3zUywK+sNU5EO8RApFMAKCD3Y/u3avDFndznwqfG5UeTHMlvACfUivPIVQZyDZnhZMq0UhC6zhCEQg=
46 46 384082750f2c51dc917d85a7145748330fa6ef4d 0 iD8DBQBOmd+OywK+sNU5EO8RAgDgAJ9V/X+G7VLwhTpHrZNiOHabzSyzYQCdE2kKfIevJUYB9QLAWCWP6DPwrwI=
47 47 41453d55b481ddfcc1dacb445179649e24ca861d 0 iD8DBQBOsFhpywK+sNU5EO8RAqM6AKCyfxUae3/zLuiLdQz+JR78690eMACfQ6JTBQib4AbE+rUDdkeFYg9K/+4=
48 48 195dbd1cef0c2f9f8bcf4ea303238105f716bda3 0 iD8DBQBO1/fWywK+sNU5EO8RAmoPAKCR5lpv1D6JLURHD8KVLSV4GRVEBgCgnd0Sy78ligNfqAMafmACRDvj7vo=
49 49 6344043924497cd06d781d9014c66802285072e4 0 iD8DBQBPALgmywK+sNU5EO8RAlfhAJ9nYOdWnhfVDHYtDTJAyJtXBAQS9wCgnefoSQt7QABkbGxM+Q85UYEBuD0=
50 50 db33555eafeaf9df1e18950e29439eaa706d399b 0 iD8DBQBPGdzxywK+sNU5EO8RAppkAJ9jOXhUVE/97CPgiMA0pMGiIYnesQCfengAszcBiSiKGugiI8Okc9ghU+Y=
51 51 2aa5b51f310fb3befd26bed99c02267f5c12c734 0 iD8DBQBPKZ9bywK+sNU5EO8RAt1TAJ45r1eJ0YqSkInzrrayg4TVCh0SnQCgm0GA/Ua74jnnDwVQ60lAwROuz1Q=
52 52 53e2cd303ecf8ca7c7eeebd785c34e5ed6b0f4a4 0 iD8DBQBPT/fvywK+sNU5EO8RAnfYAKCn7d0vwqIb100YfWm1F7nFD5B+FACeM02YHpQLSNsztrBCObtqcnfod7Q=
53 53 b9bd95e61b49c221c4cca24e6da7c946fc02f992 0 iD8DBQBPeLsIywK+sNU5EO8RAvpNAKCtKe2gitz8dYn52IRF0hFOPCR7AQCfRJL/RWCFweu2T1vH/mUOCf8SXXc=
54 54 d9e2f09d5488c395ae9ddbb320ceacd24757e055 0 iD8DBQBPju/dywK+sNU5EO8RArBYAJ9xtifdbk+hCOJO8OZa4JfHX8OYZQCeKPMBaBWiT8N/WHoOm1XU0q+iono=
55 55 00182b3d087909e3c3ae44761efecdde8f319ef3 0 iD8DBQBPoFhIywK+sNU5EO8RAhzhAKCBj1n2jxPTkZNJJ5pSp3soa+XHIgCgsZZpAQxOpXwCp0eCdNGe0+pmxmg=
56 56 5983de86462c5a9f42a3ad0f5e90ce5b1d221d25 0 iD8DBQBPovNWywK+sNU5EO8RAhgiAJ980T91FdPTRMmVONDhpkMsZwVIMACgg3bKvoWSeuCW28llUhAJtUjrMv0=
57 57 85a358df5bbbe404ca25730c9c459b34263441dc 0 iD8DBQBPyZsWywK+sNU5EO8RAnpLAJ48qrGDJRT+pteS0mSQ11haqHstPwCdG4ccGbk+0JHb7aNy8/NRGAOqn9w=
58 58 b013baa3898e117959984fc64c29d8c784d2f28b 0 iD8DBQBP8QOPywK+sNU5EO8RAqimAKCFRSx0lvG6y8vne2IhNG062Hn0dACeMLI5/zhpWpHBIVeAAquYfx2XFeA=
59 59 7f5094bb3f423fc799e471aac2aee81a7ce57a0b 0 iD8DBQBQGiL8ywK+sNU5EO8RAq5oAJ4rMMCPx6O+OuzNXVOexogedWz/QgCeIiIxLd76I4pXO48tdXhr0hQcBuM=
60 60 072209ae4ddb654eb2d5fd35bff358c738414432 0 iD8DBQBQQkq0ywK+sNU5EO8RArDTAJ9nk5CySnNAjAXYvqvx4uWCw9ThZwCgqmFRehH/l+oTwj3f8nw8u8qTCdc=
61 61 b3f0f9a39c4e1d0250048cd803ab03542d6f140a 0 iD8DBQBQamltywK+sNU5EO8RAlsqAJ4qF/m6aFu4mJCOKTiAP5RvZFK02ACfawYShUZO6OXEFfveU0aAxDR0M1k=
62 62 d118a4f4fd16d9b558ec3f3e87bfee772861d2b7 0 iD8DBQBQgPV5ywK+sNU5EO8RArylAJ0abcx5NlDjyv3ZDWpAfRIHyRsJtQCgn4TMuEayqgxzrvadQZHdTEU2g38=
63 63 195ad823b5d58c68903a6153a25e3fb4ed25239d 0 iD8DBQBQkuT9ywK+sNU5EO8RAhB4AKCeerItoK2Jipm2cVf4euGofAa/WACeJj3TVd4pFILpb+ogj7ebweFLJi0=
64 64 0c10cf8191469e7c3c8844922e17e71a176cb7cb 0 iD8DBQBQvQWoywK+sNU5EO8RAnq3AJoCn98u4geFx5YaQaeh99gFhCd7bQCgjoBwBSUyOvGd0yBy60E3Vv3VZhM=
65 65 a4765077b65e6ae29ba42bab7834717b5072d5ba 0 iD8DBQBQ486sywK+sNU5EO8RAhmJAJ90aLfLKZhmcZN7kqphigQJxiFOQACeJ5IUZxjGKH4xzi3MrgIcx9n+dB0=
66 66 f5fbe15ca7449f2c9a3cf817c86d0ae68b307214 0 iD8DBQBQ+yuYywK+sNU5EO8RAm9JAJoD/UciWvpGeKBcpGtZJBFJVcL/HACghDXSgQ+xQDjB+6uGrdgAQsRR1Lg=
67 67 a6088c05e43a8aee0472ca3a4f6f8d7dd914ebbf 0 iD8DBQBRDDROywK+sNU5EO8RAh75AJ9uJCGoCWnP0Lv/+XuYs4hvUl+sAgCcD36QgAnuw8IQXrvv684BAXAnHcA=
68 68 7511d4df752e61fe7ae4f3682e0a0008573b0402 0 iD8DBQBRFYaoywK+sNU5EO8RAuErAJoDyhXn+lptU3+AevVdwAIeNFyR2gCdHzPHyWd+JDeWCUR+pSOBi8O2ppM=
69 69 5b7175377babacce80a6c1e12366d8032a6d4340 0 iD8DBQBRMCYgywK+sNU5EO8RAq1/AKCWKlt9ysibyQgYwoxxIOZv5J8rpwCcDSHQaaf1fFZUTnQsOePwcM2Y/Sg=
70 70 50c922c1b5145dab8baefefb0437d363b6a6c21c 0 iD8DBQBRWnUnywK+sNU5EO8RAuQRAJwM42cJqJPeqJ0jVNdMqKMDqr4dSACeP0cRVGz1gitMuV0x8f3mrZrqc7I=
71 71 8a7bd2dccd44ed571afe7424cd7f95594f27c092 0 iD8DBQBRXfBvywK+sNU5EO8RAn+LAKCsMmflbuXjYRxlzFwId5ptm8TZcwCdGkyLbZcASBOkzQUm/WW1qfknJHU=
72 72 292cd385856d98bacb2c3086f8897bc660c2beea 0 iD8DBQBRcM0BywK+sNU5EO8RAjp4AKCJBykQbvXhKuvLSMxKx3a2TBiXcACfbr/kLg5GlZTF/XDPmY+PyHgI/GM=
73 73 23f785b38af38d2fca6b8f3db56b8007a84cd73a 0 iD8DBQBRgZwNywK+sNU5EO8RAmO4AJ4u2ILGuimRP6MJgE2t65LZ5dAdkACgiENEstIdrlFC80p+sWKD81kKIYI=
74 74 ddc7a6be20212d18f3e27d9d7e6f079a66d96f21 0 iD8DBQBRkswvywK+sNU5EO8RAiYYAJsHTHyHbJeAgmGvBTmDrfcKu4doUgCeLm7eGBjx7yAPUvEtxef8rAkQmXI=
75 75 cceaf7af4c9e9e6fa2dbfdcfe9856c5da69c4ffd 0 iD8DBQBRqnFLywK+sNU5EO8RAsWNAJ9RR6t+y1DLFc2HeH0eN9VfZAKF9gCeJ8ezvhtKq/LMs0/nvcgKQc/d5jk=
76 76 009794acc6e37a650f0fae37872e733382ac1c0c 0 iD8DBQBR0guxywK+sNU5EO8RArNkAKCq9pMihVzP8Os5kCmgbWpe5C37wgCgqzuPZTHvAsXF5wTyaSTMVa9Ccq4=
77 77 f0d7721d7322dcfb5af33599c2543f27335334bb 0 iD8DBQBR8taaywK+sNU5EO8RAqeEAJ4idDhhDuEsgsUjeQgWNj498matHACfT67gSF5w0ylsrBx1Hb52HkGXDm0=
78 78 f37b5a17e6a0ee17afde2cdde5393dd74715fb58 0 iD8DBQBR+ymFywK+sNU5EO8RAuSdAJkBMcd9DAZ3rWE9WGKPm2YZ8LBoXACfXn/wbEsVy7ZgJoUwiWmHSnQaWCI=
79 79 335a558f81dc73afeab4d7be63617392b130117f 0 iQIVAwUAUiZrIyBXgaxoKi1yAQK2iw//cquNqqSkc8Re5/TZT9I6NH+lh6DbOKjJP0Xl1Wqq0K+KSIUgZG4G32ovaEb2l5X0uY+3unRPiZ0ebl0YSw4Fb2ZiPIADXLBTOYRrY2Wwd3tpJeGI6wEgZt3SfcITV/g7NJrCjT3FlYoSOIayrExM80InSdcEM0Q3Rx6HKzY2acyxzgZeAtAW5ohFvHilSvY6p5Gcm4+QptMxvw45GPdreUmjeXZxNXNXZ8P+MjMz/QJbai/N7PjmK8lqnhkBsT48Ng/KhhmOkGntNJ2/ImBWLFGcWngSvJ7sfWwnyhndvGhe0Hq1NcCf7I8TjNDxU5TR+m+uW7xjXdLoDbUjBdX4sKXnh8ZjbYiODKBOrrDq25cf8nA/tnpKyE/qsVy60kOk6loY4XKiYmn1V49Ta0emmDx0hqo3HgxHHsHX0NDnGdWGol7cPRET0RzVobKq1A0jnrhPooWidvLh9bPzLonrWDo+ib+DuySoRkuYUK4pgZJ2mbg6daFOBEZygkSyRB8bo1UQUP7EgQDrWe4khb/5GHEfDkrQz3qu/sXvc0Ir1mOUWBFPHC2DjjCn/oMJuUkG1SwM8l2Bfv7h67ssES6YQ2+RjOix4yid7EXS/Ogl45PzCIPSI5+BbNs10JhE0w5uErBHlF53EDTe/TSLc+GU6DB6PP6dH912Njdr3jpNSUQ=
80 80 e7fa36d2ad3a7944a52dca126458d6f482db3524 0 iQIVAwUAUktg4yBXgaxoKi1yAQLO0g//du/2ypYYUfmM/yZ4zztNKIvgMSGTDVbCCGB2y2/wk2EcolpjpGTkcgnJT413ksYtw78ZU+mvv0RjgrFCm8DQ8kroJaQZ2qHmtSUb42hPBPvtg6kL9YaA4yvp87uUBpFRavGS5uX4hhEIyvZKzhXUBvqtL3TfwR7ld21bj8j00wudqELyyU9IrojIY9jkJ3XL/4shBGgP7u6OK5g8yJ6zTnWgysUetxHBPrYjG25lziiiZQFvZqK1B3PUqAOaFPltQs0PB8ipOCAHQgJsjaREj8VmC3+rskmSSy66NHm6gAB9+E8oAgOcU7FzWbdYgnz4kR3M7TQvHX9U61NinPXC6Q9d1VPhO3E6sIGvqJ4YeQOn65V9ezYuIpFSlgQzCHMmLVnOV96Uv1R/Z39I4w7D3S5qoZcQT/siQwGbsZoPMGFYmqOK1da5TZWrrJWkYzc9xvzT9m3q3Wds5pmCmo4b/dIqDifWwYEcNAZ0/YLHwCN5SEZWuunkEwtU5o7TZAv3bvDDA6WxUrrHI/y9/qvvhXxsJnY8IueNhshdmWZfXKz+lJi2Dvk7DUlEQ1zZWSsozi1E+3biMPJO47jsxjoT/jmE5+GHLCgcnXXDVBeaVal99IOaTRFukiz2EMsry1s8fnwEE5XKDKRlU/dOPfsje0gc7bgE0QD/u3E4NJ99g9A=
81 81 1596f2d8f2421314b1ddead8f7d0c91009358994 0 iQIVAwUAUmRq+yBXgaxoKi1yAQLolhAAi+l4ZFdQTu9yJDv22YmkmHH4fI3d5VBYgvfJPufpyaj7pX626QNW18UNcGSw2BBpYHIJzWPkk/4XznLVKr4Ciw2N3/yqloEFV0V2SSrTbMWiR9qXI4KJH+Df3KZnKs3FgiYpXkErL4GWkc1jLVR50xQ5RnkMljjtCd0NTeV2PHZ6gP2qbu6CS+5sm3AFhTDGnx8GicbMw76ZNw5M2G+T48yH9jn5KQi2SBThfi4H9Bpr8FDuR7PzQLgw9SbtYxtdQxNkK55k0nG4oLDxduNakU6SH9t8n8tdCfMt58kTzlQVrPFiTFjKu2n2JioDTz2HEivbZ5H757cu7SvpX8gW3paeBc57e+GOLMisMZABXLICq59c3QnrMwFY4FG+5cpiHVXoaZz/0bYCJx+IhU4QLWqZuzb18KSyHUCqQRzXlzS6QV5O7dY5YNQXFC44j/dS5zdgWMYo2mc6mVP2OaPUn7F6aQh5MCDYorPIOkcNjOg7ytajo7DXbzWt5Al8qt6386BJksyR3GAonc09+l8IFeNxk8HZNP4ETQ8aWj0dC9jgBDPK43T2Bju/i84s+U/bRe4tGSQalZUEv06mkIH/VRJp5w2izYTsdIjA4FT9d36OhaxlfoO1X6tHR9AyA3bF/g/ozvBwuo3kTRUUqo+Ggvx/DmcPQdDiZZQIqDBXch0=
82 82 d825e4025e39d1c39db943cdc89818abd0a87c27 0 iQIVAwUAUnQlXiBXgaxoKi1yAQJd3BAAi7LjMSpXmdR7B8K98C3/By4YHsCOAocMl3JXiLd7SXwKmlta1zxtkgWwWJnNYE3lVJvGCl+l4YsGKmFu755MGXlyORh1x4ohckoC1a8cqnbNAgD6CSvjSaZfnINLGZQP1wIP4yWj0FftKVANQBjj/xkkxO530mjBYnUvyA4PeDd5A1AOUUu6qHzX6S5LcprEt7iktLI+Ae1dYTkiCpckDtyYUKIk3RK/4AGWwGCPddVWeV5bDxLs8GHyMbqdBwx+2EAMtyZfXT+z6MDRsL/gEBVOXHb/UR0qpYED+qFnbtTlxqQkRE/wBhwDoRzUgcSuukQ9iPn79WNDSdT5b6Jd393uEO5BNF/DB6rrOiWmlpoooWgTY9kcwGB02v0hhLrH5r1wkv8baaPl+qjCjBxf4CNKm/83KN5/umGbZlORqPSN5JVxK6vDNwFFmHLaZbMT1g27GsGOWm84VH+dgolgk4nmRNSO37eTNM5Y1C3Zf2amiqDSRcAxCgseg0Jh10G7i52SSTcZPI2MqrwT9eIyg8PTIxT1D5bPcCzkg5nTTL6S7bet7OSwynRnHslhvVUBly8aIj4eY/5cQqAucUUa5sq6xLD8N27Tl+sQi+kE6KtWu2c0ZhpouflYp55XNMHgU4KeFcVcDtHfJRF6THT6tFcHFNauCHbhfN2F33ANMP4=
83 83 209e04a06467e2969c0cc6501335be0406d46ef0 0 iQIVAwUAUpv1oCBXgaxoKi1yAQKOFBAAma2wlsr3w/5NvDwq2rmOrgtNDq1DnNqcXloaOdwegX1z3/N++5uVjLjI0VyguexnwK+7E8rypMZ+4glaiZvIiGPnGMYbG9iOoz5XBhtUHzI5ECYfm5QU81by9VmCIvArDFe5Hlnz4XaXpEGnAwPywD+yzV3/+tyoV7MgsVinCMtbX9OF84/ubWKNzq2810FpQRfYoCOrF8sUed/1TcQrSm1eMB/PnuxjFCFySiR6J7Urd9bJoJIDtdZOQeeHaL5Z8Pcsyzjoe/9oTwJ3L3tl/NMZtRxiQUWtfRA0zvEnQ4QEkZSDMd/JnGiWHPVeP4P92+YN15za9yhneEAtustrTNAmVF2Uh92RIlmkG475HFhvwPJ4DfCx0vU1OOKX/U4c1rifW7H7HaipoaMlsDU2VFsAHcc3YF8ulVt27bH2yUaLGJz7eqpt+3DzZTKp4d/brZA2EkbVgsoYP+XYLbzxfwWlaMwiN3iCnlTFbNogH8MxhfHFWBj6ouikqOz8HlNl6BmSQiUCBnz5fquVpXmW2Md+TDekk+uOW9mvk1QMU62br+Z6PEZupkdTrqKaz+8ZMWvTRct8SiOcu7R11LpfERyrwYGGPei0P2YrEGIWGgXvEobXoPTSl7J+mpOA/rp2Q1zA3ihjgzwtGZZF+ThQXZGIMGaA2YPgzuYRqY8l5oc=
84 84 ca387377df7a3a67dbb90b6336b781cdadc3ef41 0 iQIVAwUAUsThISBXgaxoKi1yAQJpvRAAkRkCWLjHBZnWxX9Oe6t2HQgkSsmn9wMHvXXGFkcAmrqJ86yfyrxLq2Ns0X7Qwky37kOwKsywM53FQlsx9j//Y+ncnGZoObFTz9YTuSbOHGVsTbAruXWxBrGOf1nFTlg8afcbH0jPfQXwxf3ptfBhgsFCzORcqc8HNopAW+2sgXGhHnbVtq6LF90PWkbKjCCQLiX3da1uETGAElrl4jA5Y2i64S1Q/2X+UFrNslkIIRCGmAJ6BnE6KLJaUftpfbN7Br7a3z9xxWqxRYDOinxDgfAPAucOJPLgMVQ0bJIallaRu7KTmIWKIuSBgg1/hgfoX8I1w49WrTGp0gGY140kl8RWwczAz/SB03Xtbl2+h6PV7rUV2K/5g61DkwdVbWqXM9wmJZmvjEKK0qQbBT0By4QSEDNcKKqtaFFwhFzx4dkXph0igHOtXhSNzMd8PsFx/NRn9NLFIpirxfqVDwakpDNBZw4Q9hUAlTPxSFL3vD9/Zs7lV4/dAvvl+tixJEi2k/iv248b/AI1PrPIQEqDvjrozzzYvrS4HtbkUn+IiHiepQaYnpqKoXvBu6btK/nv0GTxB5OwVJzMA1RPDcxIFfZA2AazHjrXiPAl5uWYEddEvRjaCiF8xkQkfiXzLOoqhKQHdwPGcfMFEs9lNR8BrB2ZOajBJc8RPsFDswhT5h4=
85 85 8862469e16f9236208581b20de5f96bd13cc039d 0 iQIVAwUAUt7cLSBXgaxoKi1yAQLOkRAAidp501zafqe+JnDwlf7ORcJc+FgCE6mK1gxDfReCbkMsY7AzspogU7orqfSmr6XXdrDwmk3Y5x3mf44OGzNQjvuNWhqnTgJ7sOcU/lICGQUc8WiGNzHEMFGX9S+K4dpUaBf8Tcl8pU3iArhlthDghW6SZeDFB/FDBaUx9dkdFp6eXrmu4OuGRZEvwUvPtCGxIL7nKNnufI1du/MsWQxvC2ORHbMNtRq6tjA0fLZi4SvbySuYifQRS32BfHkFS5Qu4/40+1k7kd0YFyyQUvIsVa17lrix3zDqMavG8x7oOlqM/axDMBT6DhpdBMAdc5qqf8myz8lwjlFjyDUL6u3Z4/yE0nUrmEudXiXwG0xbVoEN8SCNrDmmvFMt6qdCpdDMkHr2TuSh0Hh4FT5CDkzPI8ZRssv/01j/QvIO3c/xlbpGRPWpsPXEVOz3pmjYN4qyQesnBKWCENsQLy/8s2rey8iQgx2GtsrNw8+wGX6XE4v3QtwUrRe12hWoNrEHWl0xnLv2mvAFqdMAMpFY6EpOKLlE4hoCs2CmTJ2dv6e2tiGTXGU6/frI5iuNRK61OXnH5OjEc8DCGH/GC7NXyDOXOB+7BdBvvf50l2C/vxR2TKgTncLtHeLCrR0GHNHsxqRo1UDwOWur0r7fdfCRvb2tIr5LORCqKYVKd60/BAXjHWc=
86 86 3cec5134e9c4bceab6a00c60f52a4f80677a78f2 0 iQIVAwUAUu1lIyBXgaxoKi1yAQIzCBAAizSWvTkWt8+tReM9jUetoSToF+XahLhn381AYdErFCBErX4bNL+vyEj+Jt2DHsAfabkvNBe3k7rtFlXHwpq6POa/ciFGPDhFlplNv6yN1jOKBlMsgdjpn7plZKcLHODOigU7IMlgg70Um8qVrRgQ8FhvbVgR2I5+CD6bucFzqo78wNl9mCIHIQCpGKIUoz56GbwT+rUpEB182Z3u6rf4NWj35RZLGAicVV2A2eAAFh4ZvuC+Z0tXMkp6Gq9cINawZgqfLbzVYJeXBtJC39lHPyp5P3LaEVRhntc9YTwbfkVGjyJZR60iYrieeKpOYRnzgHauPVdgVhkTkBxshmEPY7svKYSQqlj8hLuFa+a3ajbIPrpQAAi1MgtamA991atNqGiSTjdZa9kLQvfdn0k80+gkCxpuO56PhvtdjKsYVRgQMTYmQVQdh3x4WbQOSqTADXXIZUaWxx4RmNSlxY7KD+3lPP09teOD+A3B2cP60bC5NsCfULtQFXQzdC7NvfIyYfYBTZa+Pv6HFkVe10cbnqTt83hBy0D77vdaegPRe56qDNU+GrIG2/rosnlKGFjFoK/pTYkR9uzfkrhEjLwyfkoXlBqY+376W0PC5fP10pJeQBS9DuXpCPlgtyW0Jy1ayCT1YR4QJC4n75vZwTFBFRBhSi0HqFquOgy83+O0Q/k=
87 87 b96cb15ec9e04d8ac5ee08b34fcbbe4200588965 0 iQIVAwUAUxJPlyBXgaxoKi1yAQLIRA//Qh9qzoYthPAWAUNbzybWXC/oMBI2X89NQC7l1ivKhv7cn9L79D8SWXM18q7LTwLdlwOkV/a0NTE3tkQTLvxJpfnRLCBbMOcGiIn/PxsAae8IhMAUbR7qz+XOynHOs60ZhK9X8seQHJRf1YtOI9gYTL/WYk8Cnpmc6xZQ90TNhoPPkpdfe8Y236V11SbYtN14fmrPaWQ3GXwyrvQaqM1F7BxSnC/sbm9+/wprsTa8gRQo7YQL/T5jJQgFiatG3yayrDdJtoRq3TZKtsxw8gtQdfVCrrBibbysjM8++dnwA92apHNUY8LzyptPy7rSDXRrIpPUWGGTQTD+6HQwkcLFtIuUpw4I75SV3z2r6LyOLKzDJUIunKOOYFS/rEIQGxZHxZOBAvbI+73mHAn3pJqm+UAA7R1n7tk3JyQncg50qJlm9zIUPGpNFcdEqak5iXzGYx292VlcE+fbJYeIPWggpilaVUgdmXtMCG0O0uX6C8MDmzVDCjd6FzDJ4GTZwgmWJaamvls85CkZgyN/UqlisfFXub0A1h7qAzBSVpP1+Ti+UbBjlrGX8BMRYHRGYIeIq16elcWwSpLgshjDwNn2r2EdwX8xKU5mucgTzSLprbOYGdQaqnvf6e8IX5WMBgwVW9YdY9yJKSLF7kE1AlM9nfVcXwOK4mHoMvnNgiX3zsw=
88 88 3f83fc5cfe715d292069ee8417c83804f6c6c1e4 0 iQIVAwUAUztENyBXgaxoKi1yAQIpkhAAmJj5JRTSn0Dn/OTAHggalw8KYFbAck1X35Wg9O7ku7sd+cOnNnkYfqAdz2m5ikqWHP7aWMiNkNy7Ree2110NqkQVYG/2AJStXBdIOmewqnjDlNt+rbJQN/JsjeKSCy+ToNvhqX5cTM9DF2pwRjMsTXVff307S6/3pga244i+RFAeG3WCUrzfDu641MGFLjG4atCj8ZFLg9DcW5bsRiOs5ZK5Il+UAb2yyoS2KNQ70VLhYULhGtqq9tuO4nLRGN3DX/eDcYfncPCav1GckW4OZKakcbLtAdW0goSgGWloxcM+j2E6Z1JZ9tOTTkFN77EvX0ZWZLmYM7sUN1meFnKbVxrtGKlMelwKwlT252c65PAKa9zsTaRUKvN7XclyxZAYVCsiCQ/V08NXhNgXJXcoKUAeGNf6wruOyvRU9teia8fAiuHJoY58WC8jC4nYG3iZTnl+zNj2A5xuEUpYHhjUfe3rNJeK7CwUpJKlbxopu5mnW9AE9ITfI490eaapRLTojOBDJNqCORAtbggMD46fLeCOzzB8Gl70U2p5P34F92Sn6mgERFKh/10XwJcj4ZIeexbQK8lqQ2cIanDN9dAmbvavPTY8grbANuq+vXDGxjIjfxapqzsSPqUJ5KnfTQyLq5NWwquR9t38XvHZfktkd140BFKwIUAIlKKaFfYXXtM=
89 89 564f55b251224f16508dd1311452db7780dafe2b 0 iQIVAwUAU1BmFSBXgaxoKi1yAQJ2Aw//bjK++xJuZCIdktg/i5FxBwoxdbipfTkKsN/YjUwrEmroYM8IkqIsO+U54OGCYWr3NPJ3VS8wUQeJ+NF3ffcjmjC297R9J+X0c5G90DdQUYX44jG/tP8Tqpev4Q7DLCXT26aRwEMdJQpq0eGaqv55E5Cxnyt3RrLCqe7RjPresZFg7iYrro5nq8TGYwBhessHXnCix9QI0HtXiLpms+0UGz8Sbi9nEYW+M0OZCyO1TvykCpFzEsLNwqqtFvhOMD/AMiWcTKNUpjmOn3V83xjWl+jnDUt7BxJ7n1efUnlwl4IeWlSUb73q/durtaymb97cSdKFmXHv4pdAShQEuEpVVGO1WELsKoXmbj30ItTW2V3KvNbjFsvIdDo7zLCpXyTq1HC56W7QCIMINX2qT+hrAMWC12tPQ05f89Cv1+jpk6eOPFqIHFdi663AjyrnGll8nwN7HJWwtA5wTXisu3bec51FAq4yJTzPMtOE9spz36E+Go2hZ1cAv9oCSceZcM0wB8KiMfaZJKNZNZk1jvsdiio4CcdASOFQPOspz07GqQxVP7W+F1Oz32LgwcNAEAS/f3juwDj45GYfAWJrTh3dnJy5DTD2LVC7KtkxxUVkWkqxivnDB9anj++FN9eyekxzut5eFED+WrCfZMcSPW0ai7wbslhKUhCwSf/v3DgGwsM=
90 90 2195ac506c6ababe86985b932f4948837c0891b5 0 iQIVAwUAU2LO/CBXgaxoKi1yAQI/3w/7BT/VRPyxey6tYp7i5cONIlEB3gznebGYwm0SGYNE6lsvS2VLh6ztb+j4eqOadr8Ssna6bslBx+dVsm+VuJ+vrNLMucD5Uc+fhn6dAfVqg+YBzUEaedI5yNsJizcJUDI7hUVsxiPiiYd9hchCWJ+z2tVt2jCyG2lMV2rbW36AM89sgz/wn5/AaAFsgoS6up/uzA3Tmw+qZSO6dZChb4Q8midIUWEbNzVhokgYcw7/HmjmvkvV9RJYiG8aBnMdQmxTE69q2dTjnnDL6wu61WU2FpTN09HRFbemUqzAfoJp8MmXq6jWgfLcm0cI3kRo7ZNpnEkmVKsfKQCXXiaR4alt9IQpQ6Jl7LSYsYI+D4ejpYysIsZyAE8qzltYhBKJWqO27A5V4WdJsoTgA/RwKfPRlci4PY8I4N466S7PBXVz/Cc5EpFkecvrgceTmBafb8JEi+gPiD2Po4vtW3bCeV4xldiEXHeJ77byUz7fZU7jL78SjJVOCCQTJfKZVr36kTz3KlaOz3E700RxzEFDYbK7I41mdANeQBmNNbcvRTy5ma6W6I3McEcAH4wqM5fFQ8YS+QWJxk85Si8KtaDPqoEdC/0dQPavuU/jAVjhV8IbmmkOtO7WvOHQDBtrR15yMxGMnUwMrPHaRNKdHNYRG0LL7lpCtdMi1mzLQgHYY9SRYvI=
91 91 269c80ee5b3cb3684fa8edc61501b3506d02eb10 0 iQIVAwUAU4uX5CBXgaxoKi1yAQLpdg/+OxulOKwZN+Nr7xsRhUijYjyAElRf2mGDvMrbAOA2xNf85DOXjOrX5TKETumf1qANA5cHa1twA8wYgxUzhx30H+w5EsLjyeSsOncRnD5WZNqSoIq2XevT0T4c8xdyNftyBqK4h/SC/t2h3vEiSCUaGcfNK8yk4XO45MIk4kk9nlA9jNWdA5ZMLgEFBye2ggz0JjEAPUkVDqlr9sNORDEbnwZxGPV8CK9HaL/I8VWClaFgjKQmjqV3SQsNFe2XPffzXmIipFJ+ODuXVxYpAsvLiGmcfuUfSDHQ4L9QvjBsWe1PgYMr/6CY/lPYmR+xW5mJUE9eIdN4MYcXgicLrmMpdF5pToNccNCMtfa6CDvEasPRqe2bDzL/Q9dQbdOVE/boaYBlgmYLL+/u+dpqip9KkyGgbSo9uJzst1mLTCzJmr5bw+surul28i9HM+4+Lewg4UUdHLz46no1lfTlB5o5EAhiOZBTEVdoBaKfewVpDa/aBRvtWX7UMVRG5qrtA0sXwydN00Jaqkr9m20W0jWjtc1ZC72QCrynVHOyfIb2rN98rnuy2QN4bTvjNpNjHOhhhPTOoVo0YYPdiUupm46vymUTQCmWsglU4Rlaa3vXneP7JenL5TV8WLPs9J28lF0IkOnyBXY7OFcpvYO1euu7iR1VdjfrQukMyaX18usymiA=
92 92 2d8cd3d0e83c7336c0cb45a9f88638363f993848 0 iQIVAwUAU7OLTCBXgaxoKi1yAQJ+pw/+M3yOesgf55eo3PUTZw02QZxDyEg9ElrRc6664/QFXaJuYdz8H3LGG/NYs8uEdYihiGpS1Qc70jwd1IoUlrCELsaSSZpzWQ+VpQFX29aooBoetfL+8WgqV8zJHCtY0E1EBg/Z3ZL3n2OS++fVeWlKtp5mwEq8uLTUmhIS7GseP3bIG/CwF2Zz4bzhmPGK8V2s74aUvELZLCfkBE1ULNs7Nou1iPDGnhYOD53eq1KGIPlIg1rnLbyYw5bhS20wy5IxkWf2eCaXfmQBTG61kO5m3nkzfVgtxmZHLqYggISTJXUovfGsWZcp5a71clCSMVal+Mfviw8L/UPHG0Ie1c36djJiFLxM0f2HlwVMjegQOZSAeMGg1YL1xnIys2zMMsKgEeR+JISTal1pJyLcT9x5mr1HCnUczSGXE5zsixN+PORRnZOqcEZTa2mHJ1h5jJeEm36B/eR57BMJG+i0QgZqTpLzYTFrp2eWokGMjFB1MvgAkL2YoRsw9h6TeIwqzK8mFwLi28bf1c90gX9uMbwY/NOqGzfQKBR9bvCjs2k/gmJ+qd5AbC3DvOxHnN6hRZUqNq76Bo4F+CUVcjQ/NXnfnOIVNbILpl5Un5kl+8wLFM+mNxDxduajaUwLhSHZofKmmCSLbuuaGmQTC7a/4wzhQM9e5dX0X/8sOo8CptW7uw4=
93 93 6c36dc6cd61a0e1b563f1d51e55bdf4dacf12162 0 iQIVAwUAU8n97yBXgaxoKi1yAQKqcA/+MT0VFoP6N8fHnlxj85maoM2HfZbAzX7oEW1B8F1WH6rHESHDexDWIYWJ2XnEeTD4GCXN0/1p+O/I0IMPNzqoSz8BU0SR4+ejhRkGrKG7mcFiF5G8enxaiISn9nmax6DyRfqtOQBzuXYGObXg9PGvMS6zbR0SorJK61xX7fSsUNN6BAvHJfpwcVkOrrFAIpEhs/Gh9wg0oUKCffO/Abs6oS+P6nGLylpIyXqC7rKZ4uPVc6Ljh9DOcpV4NCU6kQbNE7Ty79E0/JWWLsHOEY4F4WBzI7rVh7dOkRMmfNGaqvKkuNkJOEqTR1o1o73Hhbxn4NU7IPbVP/zFKC+/4QVtcPk2IPlpK1MqA1H2hBNYZhJlNhvAa7LwkIxM0916/zQ8dbFAzp6Ay/t/L0tSEcIrudTz2KTrY0WKw+pkzB/nTwaS3XZre6H2B+gszskmf1Y41clkIy/nH9K7zBuzANWyK3+bm40vmMoBbbnsweUAKkyCwqm4KTyQoYQWzu/ZiZcI+Uuk/ajJ9s7EhJbIlSnYG9ttWL/IZ1h+qPU9mqVO9fcaqkeL/NIRh+IsnzaWo0zmHU1bK+/E29PPGGf3v6+IEJmXg7lvNl5pHiMd2tb7RNO/UaNSv1Y2E9naD4FQwSWo38GRBcnRGuKCLdZNHGUR+6dYo6BJCGG8wtZvNXb3TOo=
94 94 3178e49892020336491cdc6945885c4de26ffa8b 0 iQIVAwUAU9whUCBXgaxoKi1yAQJDKxAAoGzdHXV/BvZ598VExEQ8IqkmBVIP1QZDVBr/orMc1eFM4tbGKxumMGbqgJsg+NetI0irkh/YWeJQ13lT4Og72iJ+4UC9eF9pcpUKr/0eBYdU2N/p2MIbVNWh3aF5QkbuQpSri0VbHOWkxqwoqrrwXEjgHaKYP4PKh+Dzukax4yzBUIyzAG38pt4a8hbjnozCl2uAikxk4Ojg+ZufhPoZWgFEuYzSfK5SrwVKOwuxKYFGbbVGTQMIXLvBhOipAmHp4JMEYHfG85kwuyx/DCDbGmXKPQYQfClwjJ4ob/IwG8asyMsPWs+09vrvpVO08HBuph3GjuiWJ1fhEef/ImWmZdQySI9Y4SjwP4dMVfzLCnY+PYPDM9Sq/5Iee13gI2lVM2NtAfQZPXh9l8u6SbCir1UhMNMx0qVMkqMAATmiZ+ETHCO75q4Wdcmnv5fk2PbvaGBVtrHGeiyuz5mK/j4cMbd0R9R0hR1PyC4dOhNqOnbqELNIe0rKNByG1RkpiQYsqZTU6insmnZrv4fVsxfA4JOObPfKNT4oa24MHS73ldLFCfQAuIxVE7RDJJ3bHeh/yO6Smo28FuVRldBl5e+wj2MykS8iVcuSa1smw6gJ14iLBH369nlR3fAAQxI0omVYPDHLr7SsH3vJasTaCD7V3SL4lW6vo/yaAh4ImlTAE+Y=
95 95 5dc91146f35369949ea56b40172308158b59063a 0 iQIVAwUAVAUgJyBXgaxoKi1yAQJkEg/9EXFZvPpuvU7AjII1dlIT8F534AXrO30+H6hweg+h2mUCSb/mZnbo3Jr1tATgBWbIKkYmmsiIKNlJMFNPZTWhImGcVA93t6v85tSFiNJRI2QP9ypl5wTt2KhiS/s7GbUYCtPDm6xyNYoSvDo6vXJ5mfGlgFZY5gYLwEHq/lIRWLWD4EWYWbk5yN+B7rHu6A1n3yro73UR8DudEhYYqC23KbWEqFOiNd1IGj3UJlxIHUE4AcDukxbfiMWrKvv1kuT/vXak3X7cLXlO56aUbMopvaUflA3PSr3XAqynDd69cxACo/T36fuwzCQN4ICpdzGTos0rQALSr7CKF5YP9LMhVhCsOn0pCsAkSiw4HxxbcHQLl+t+0rchNysc4dWGwDt6GAfYcdm3fPtGFtA3qsN8lOpCquFH3TAZ3TrIjLFoTOk6s1xX1x5rjP/DAHc/y3KZU0Ffx3TwdQEEEIFaAXaxQG848rdfzV42+dnFnXh1G/MIrKAmv3ZSUkQ3XJfGc7iu82FsYE1NLHriUQDmMRBzCoQ1Rn1Kji119Cxf5rsMcQ6ZISR1f0jDCUS/qxlHvSqETLp8H63NSUfvuKSC7uC6pGvq9XQm1JRNO5UuJfK6tHzy0jv9bt2IRo2xbmvpDu9L5oHHd3JePsAmFmbrFf/7Qem3JyzEvRcpdcdHtefxcxc=
96 96 f768c888aaa68d12dd7f509dcc7f01c9584357d0 0 iQIVAwUAVCxczSBXgaxoKi1yAQJYiA/9HnqKuU7IsGACgsUGt+YaqZQumg077Anj158kihSytmSts6xDxqVY1UQB38dqAKLJrQc7RbN0YK0NVCKZZrx/4OqgWvjiL5qWUJKqQzsDx4LGTUlbPlZNZawW2urmmYW6c9ZZDs1EVnVeZMDrOdntddtnBgtILDwrZ8o3U7FwSlfnm03vTkqUMj9okA3AsI8+lQIlo4qbqjQJYwvUC1ZezRdQwaT1LyoWUgjmhoZ1XWcWKOs9baikaJr6fMv8vZpwmaOY1+pztxYlROeSPVWt9P6yOf0Hi/2eg8AwSZLaX96xfk9IvXUSItg/wjTWP9BhnNs/ulwTnN8QOgSXpYxH4RXwsYOyU7BvwAekA9xi17wuzPrGEliScplxICIZ7jiiwv/VngMvM9AYw2mNBvZt2ZIGrrLaK6pq/zBm5tbviwqt5/8U5aqO8k1O0e4XYm5WmQ1c2AkXRO+xwvFpondlSF2y0flzf2FRXP82QMfsy7vxIP0KmaQ4ex+J8krZgMjNTwXh2M4tdYNtu5AehJQEP3l6giy2srkMDuFLqoe1yECjVlGdgA86ve3J/84I8KGgsufYMhfQnwHHGXCbONcNsDvO0QOee6CIQVcdKCG7dac3M89SC6Ns2CjuC8BIYDRnxbGQb7Fvn4ZcadyJKKbXQJzMgRV25K6BAwTIdvYAtgU=
97 97 7f8d16af8cae246fa5a48e723d48d58b015aed94 0 iQIVAwUAVEL0XyBXgaxoKi1yAQJLkRAAjZhpUju5nnSYtN9S0/vXS/tjuAtBTUdGwc0mz97VrM6Yhc6BjSCZL59tjeqQaoH7Lqf94pRAtZyIB2Vj/VVMDbM+/eaoSr1JixxppU+a4eqScaj82944u4C5YMSMC22PMvEwqKmy87RinZKJlFwSQ699zZ5g6mnNq8xeAiDlYhoF2QKzUXwnKxzpvjGsYhYGDMmVS1QPmky4WGvuTl6KeGkv8LidKf7r6/2RZeMcq+yjJ7R0RTtyjo1cM5dMcn/jRdwZxuV4cmFweCAeoy5guV+X6du022TpVndjOSDoKiRgdk7pTuaToXIy+9bleHpEo9bwKx58wvOMg7sirAYjrA4Xcx762RHiUuidTTPktm8sNsBQmgwJZ8Pzm+8TyHjFGLnBfeiDbQQEdLCXloz0jVOVRflDfMays1WpAYUV8XNOsgxnD2jDU8L0NLkJiX5Y0OerGq9AZ+XbgJFVBFhaOfsm2PEc3jq00GOLzrGzA+4b3CGpFzM3EyK9OnnwbP7SqCGb7PJgjmQ7IO8IWEmVYGaKtWONSm8zRLcKdH8xuk8iN1qCkBXMty/wfTEVTkIlMVEDbslYkVfj0rAPJ8B37bfe0Yz4CEMkCmARIB1rIOpMhnavXGuD50OP2PBBY/8DyC5aY97z9f04na/ffk+l7rWaHihjHufKIApt5OnfJ1w=
98 98 ced632394371a36953ce4d394f86278ae51a2aae 0 iQIVAwUAVFWpfSBXgaxoKi1yAQLCQw//cvCi/Di3z/2ZEDQt4Ayyxv18gzewqrYyoElgnEzr5uTynD9Mf25hprstKla/Y5C6q+y0K6qCHPimGOkz3H+wZ2GVUgLKAwMABkfSb5IZiLTGaB2DjAJKZRwB6h43wG/DSFggE3dYszWuyHW88c72ZzVF5CSNc4J1ARLjDSgnNYJQ6XdPw3C9KgiLFDXzynPpZbPg0AK5bdPUKJruMeIKPn36Hx/Tv5GXUrbc2/lcnyRDFWisaDl0X/5eLdA+r3ID0cSmyPLYOeCgszRiW++KGw+PPDsWVeM3ZaZ9SgaBWU7MIn9A7yQMnnSzgDbN+9v/VMT3zbk1WJXlQQK8oA+CCdHH9EY33RfZ6ST/lr3pSQbUG1hdK6Sw+H6WMkOnnEk6HtLwa4xZ3HjDpoPkhVV+S0C7D5WWOovbubxuBiW5v8tK4sIOS6bAaKevTBKRbo4Rs6qmS/Ish5Q+z5bKst80cyEdi4QSoPZ/W+6kh1KfOprMxynwPQhtEcDYW2gfLpgPIM7RdXPKukLlkV2qX3eF/tqApGU4KNdP4I3N80Ri0h+6tVU/K4TMYzlRV3ziLBumJ4TnBrTHU3X6AfZUfTgslQzokX8/7a3tbctX6kZuJPggLGisdFSdirHbrUc+y5VKuJtPr+LxxgZKRFbs2VpJRem6FvwGNyndWLv32v0GMtQ=
99 99 643c58303fb0ec020907af28b9e486be299ba043 0 iQIVAwUAVGKawCBXgaxoKi1yAQL7zxAAjpXKNvzm/PKVlTfDjuVOYZ9H8w9QKUZ0vfrNJrN6Eo6hULIostbdRc25FcMWocegTqvKbz3IG+L2TKOIdZJS9M9QS4URybUd37URq4Jai8kMiJY31KixNNnjO2G1B39aIXUhY+EPx12aY31/OVy4laXIVtN6qpSncjo9baXSOMZmx6RyA1dbyfwXRjT/aODCGHZXgLJHS/kHlkCsThVlqYQ4rUCDkXIeMqIGF1CR0KjfmKpp1fS14OMgpLgdnt9+pnBZ+qcf1YdpOeQob1zwunjMYOyYC74FyOTdwaynU2iDsuBrmkE8kgEedIn7+WWe9fp/6TQJMVOeTQPZBNSRRSUYCw5Tg/0L/+jLtzjc2mY4444sDPbR7scrtU+/GtvlR5z0Y5pofwEdFME7PZNOp9a4kMiSa7ZERyGdN7U1pDu9JU6BZRz+nPzW217PVnTF7YFV/GGUzMTk9i7EZb5M4T9r9gfxFSMPeT5ct712CdBfyRlsSbSWk8XclTXwW385kLVYNDtOukWrvEiwxpA14Xb/ZUXbIDZVf5rP2HrZHMkghzeUYPjRn/IlgYUt7sDNmqFZNIc9mRFrZC9uFQ/Nul5InZodNODQDM+nHpxaztt4xl4qKep8SDEPAQjNr8biC6T9MtLKbWbSKDlqYYNv0pb2PuGub3y9rvkF1Y05mgM=
100 100 902554884335e5ca3661d63be9978eb4aec3f68a 0 iQIVAwUAVH0KMyBXgaxoKi1yAQLUKxAAjgyYpmqD0Ji5OQ3995yX0dmwHOaaSuYpq71VUsOMYBskjH4xE2UgcTrX8RWUf0E+Ya91Nw3veTf+IZlYLaWuOYuJPRzw+zD1sVY8xprwqBOXNaA7n8SsTqZPSh6qgw4S0pUm0xJUOZzUP1l9S7BtIdJP7KwZ7hs9YZev4r9M3G15xOIPn5qJqBAtIeE6f5+ezoyOpSPZFtLFc4qKQ/YWzOT5uuSaYogXgVByXRFaO84+1TD93LR0PyVWxhwU9JrDU5d7P/bUTW1BXdjsxTbBnigWswKHC71EHpgz/HCYxivVL30qNdOm4Fow1Ec2GdUzGunSqTPrq18ScZDYW1x87f3JuqPM+ce/lxRWBBqP1yE30/8l/Us67m6enWXdGER8aL1lYTGOIWAhvJpfzv9KebaUq1gMFLo6j+OfwR3rYPiCHgi20nTNBa+LOceWFjCGzFa3T9UQWHW/MBElfAxK65uecbGRRYY9V1/+wxtTUiS6ixpmzL8S7uUd5n6oMaeeMiD82NLgPIbMyUHQv6eFEcCj0U9NT2uKbFRmclMs5V+8D+RTCsLJ55R9PD5OoRw/6K/coqqPShYmJvgYsFQPzXVpQdCRae31xdfGFmd5KUetqyrT+4GUdJWzSm0giSgovpEJNxXglrvNdvSO7fX3R1oahhwOwtGqMwNilcK+iDw=
101 101 6dad422ecc5adb63d9fa649eeb8e05a5f9bc4900 0 iQIVAwUAVJNALCBXgaxoKi1yAQKgmw/+OFbHHOMmN2zs2lI2Y0SoMALPNQBInMBq2E6RMCMbfcS9Cn75iD29DnvBwAYNWaWsYEGyheJ7JjGBiuNKPOrLaHkdjG+5ypbhAfNDyHDiteMsXfH7D1L+cTOAB8yvhimZHOTTVF0zb/uRyVIPNowAyervUVRjDptzdfcvjUS+X+/Ufgwms6Y4CcuzFLFCxpmryJhLtOpwUPLlzIqeNkFOYWkHanCgtZX03PNIWhorH3AWOc9yztwWPQ+kcKl3FMlyuNMPhS/ElxSF6GHGtreRbtP+ZLoSIOMb2QBKpGDpZLgJ3JQEHDcZ0h5CLZWL9dDUJR3M8pg1qglqMFSWMgRPTzxPS4QntPgT/Ewd3+U5oCZUh052fG41OeCZ0CnVCpqi5PjUIDhzQkONxRCN2zbjQ2GZY7glbXoqytissihEIVP9m7RmBVq1rbjOKr+yUetJ9gOZcsMtZiCEq4Uj2cbA1x32MQv7rxwAgQP1kgQ62b0sN08HTjQpI7/IkNALLIDHoQWWr45H97i34qK1dd5uCOnYk7juvhGNX5XispxNnC01/CUVNnqChfDHpgnDjgT+1H618LiTgUAD3zo4IVAhCqF5XWsS4pQEENOB3Msffi62fYowvJx7f/htWeRLZ2OA+B85hhDiD4QBdHCRoz3spVp0asNqDxX4f4ndj8RlzfM=
102 102 1265a3a71d75396f5d4cf6935ae7d9ba5407a547 0 iQIVAwUAVKXKYCBXgaxoKi1yAQIfsA/+PFfaWuZ6Jna12Y3MpKMnBCXYLWEJgMNlWHWzwU8lD26SKSlvMyHQsVZlkld2JmFugUCn1OV3OA4YWT6BA7VALq6Zsdcu5Dc8LRbyajBUkzGRpOUyWuFzjkCpGVbrQzbCR/bel/BBXzSqL4ipdtWgJ4y+WpZIhWkNXclBkR52b5hUTjN9vzhyhVVI7eURGwIEf7vVs1fDOcEGtaGY/ynzMTzyxIDsEEygCZau86wpKlYlqhCgxKDyzyGfpH3B1UlNGFt1afW8AWe1eHjdqC7TJZpMqmQ/Ju8vco8Xht6OXw4ZLHj7y39lpccfKTBLiK/cAKSg+xgyaH/BLhzoEkNAwYSFAB4i4IoV0KUC8nFxHfsoswBxJnMqU751ziMrpZ/XHZ1xQoEOdXgz2I04vlRn8xtynOVhcgjoAXwtbia7oNh/qCH/hl5/CdAtaawuCxJBf237F+cwur4PMAAvsGefRfZco/DInpr3qegr8rwInTxlO48ZG+o5xA4TPwT0QQTUjMdNfC146ZSbp65wG7VxJDocMZ8KJN/lqPaOvX+FVYWq4YnJhlldiV9DGgmym1AAaP0D3te2GcfHXpt/f6NYUPpgiBHy0GnOlNcQyGnnONg1A6oKVWB3k7WP28+PQbQEiCIFk2nkf5VZmye7OdHRGKOFfuprYFP1WwTWnVoNX9c=
103 103 db8e3f7948b1fdeb9ad12d448fc3525759908b9f 0 iQIVAwUAVLsaciBXgaxoKi1yAQKMIA//a90/GvySL9UID+iYvzV2oDaAPDD0T+4Xs43I7DT5NIoDz+3yq2VV54XevQe5lYiURmsb/Q9nX2VR/Qq1J9c/R6Gy+CIfmJ3HzMZ0aAX8ZlZgQPYZKh/2kY5Ojl++k6MTqbqcrICNs4+UE/4IAxPyOfu5gy7TpdJmRZo2J3lWVC2Jbhd02Mzb+tjtfbOM+QcQxPwt9PpqmQszJceyVYOSm3jvD1uJdSOC04tBQrQwrxktQ09Om0LUMMaB5zFXpJtqUzfw7l4U4AaddEmkd3vUfLtHxc21RB01c3cpe2dJnjifDfwseLsI8rS4jmi/91c74TeBatSOhvbqzEkm/p8xZFXE4Uh+EpWjTsVqmfQaRq6NfNCR7I/kvGv8Ps6w8mg8uX8fd8lx+GJbodj+Uy0X3oqHyqPMky/df5i79zADBDuz+yuxFfDD9i22DJPIYcilfGgwpIUuO2lER5nSMVmReuWTVBnT6SEN66Q4KR8zLtIRr+t1qUUCy6wYbgwrdHVCbgMF8RPOVZPjbs17RIqcHjch0Xc7bShKGhQg4WHDjXHK61w4tOa1Yp7jT6COkl01XC9BLcGxJYKFvNCbeDZQGvVgJNoEvHxBxD9rGMVRjfuxeJawc2fGzZJn0ySyLDW0pfd4EJNgTh9bLdPjWz2VlXqn4A6bgaLgTPqjmN0VBXw=
104 104 fbdd5195528fae4f41feebc1838215c110b25d6a 0 iQIVAwUAVM7fBCBXgaxoKi1yAQKoYw/+LeIGcjQmHIVFQULsiBtPDf+eGAADQoP3mKBy+eX/3Fa0qqUNfES2Q3Y6RRApyZ1maPRMt8BvvhZMgQsu9QIrmf3zsFxZGFwoyrIj4hM3xvAbEZXqmWiR85/Ywd4ImeLaZ0c7mkO1/HGF1n2Mv47bfM4hhNe7VGJSSrTY4srFHDfk4IG9f18DukJVzRD9/dZeBw6eUN1ukuLEgQAD5Sl47bUdKSetglOSR1PjXfZ1hjtz5ywUyBc5P9p3LC4wSvlcJKl22zEvB3L0hkoDcPsdIPEnJAeXxKlR1rQpoA3fEgrstGiSNUW/9Tj0VekAHLO95SExmQyoG/AhbjRRzIj4uQ0aevCJyiAhkv+ffOSf99PMW9L1k3tVjLhpMWEz9BOAWyX7cDFWj5t/iktI046O9HGN9SGVx18e9xM6pEgRcLA2TyjEmtkA4jX0JeN7WeCweMLiSxyGP7pSPSJdpJeXaFtRpSF62p/G0Z5wN9s05LHqDyqNVtCvg4WjkuV5LZSdLbMcYBWGBxQzCG6qowXFXIawmbaFiBZwTfOgNls9ndz5RGupAaxY317prxPFv/pXoesc1P8bdK09ZvjhbmmD66Q/BmS2dOMQ8rXRjuVdlR8j2QBtFZxekMcRD02nBAVnwHg1VWQMIRaGjdgmW4wOkirWVn7me177FnBxrxW1tG4=
105 105 5b4ed033390bf6e2879c8f5c28c84e1ee3b87231 0 iQIVAwUAVPQL9CBXgaxoKi1yAQJIXxAAtD2hWhaKa+lABmCOYG92FE/WdqY/91Xv5atTL8Xeko/MkirIKZiOuxNWX+J34TVevINZSWmMfDSc5TkGxktL9jW/pDB/CXn+CVZpxRabPYFH9HM2K3g8VaTV1MFtV2+feOMDIPCmq5ogMF9/kXjmifiEBrJcFsE82fdexJ3OHoOY4iHFxEhh3GzvNqEQygk4VeU6VYziNvSQj9G//PsK3Bmk7zm5ScsZcMVML3SIYFuej1b1PI1v0N8mmCRooVNBGhD/eA0iLtdh/hSb9s/8UgJ4f9HOcx9zqs8V4i14lpd/fo0+yvFuVrVbWGzrDrk5EKLENhVPwvc1KA32PTQ4Z9u7VQIBIxq3K5lL2VlCMIYc1BSaSQBjuiLm8VdN6iDuf5poNZhk1rvtpQgpxJzh362dlGtR/iTJuLCeW7gCqWUAorLTeHy0bLQ/jSOeTAGys8bUHtlRL4QbnhLbUmJmRYVvCJ+Yt1aTgTSNcoFjoLJarR1169BXgdCA38BgReUL6kB224UJSTzB1hJUyB2LvCWrXZMipZmR99Iwdq7MePD3+AoSIXQNUMY9blxuuF5x7W2ikNXmVWuab4Z8rQRtmGqEuIMBSunxAnZSn+i8057dFKlq+/yGy+WW3RQg+RnLnwZs1zCDTfu98/GT5k5hFpjXZeUWWiOVwQJ5HrqncCw=
106 106 07a92bbd02e5e3a625e0820389b47786b02b2cea 0 iQIVAwUAVPSP9SBXgaxoKi1yAQLkBQ//dRQExJHFepJfZ0gvGnUoYI4APsLmne5XtfeXJ8OtUyC4a6RylxA5BavDWgXwUh9BGhOX2cBSz1fyvzohrPrvNnlBrYKAvOIJGEAiBTXHYTxHINEKPtDF92Uz23T0Rn/wnSvvlbWF7Pvd+0DMJpFDEyr9n6jvVLR7mgxMaCqZbVaB1W/wTwDjni780WgVx8OPUXkLx3/DyarMcIiPeI5UN+FeHDovTsBWFC95msFLm80PMRPuHOejWp65yyEemGujZEPO2D5VVah7fshM2HTz63+bkEBYoqrftuv3vXKBRG78MIrUrKpqxmnCKNKDUUWJ4yk3+NwuOiHlKdly5kZ7MNFaL73XKo8HH287lDWz0lIazs91dQA9a9JOyTsp8YqGtIJGGCbhrUDtiQJ199oBU84mw3VH/EEzm4mPv4sW5fm7BnnoH/a+9vXySc+498rkdLlzFwxrQkWyJ/pFOx4UA3mCtGQK+OSwLPc+X4SRqA4fiyqKxVAL1kpLTSDL3QA82I7GzBaXsxUXzS4nmteMhUyzTdwAhKVydL0gC3d7NmkAFSyRjdGzutUUXshYxg0ywRgYebe8uzJcTj4nNRgaalYLdg3guuDulD+dJmILsrcLmA6KD/pvfDn8PYt+4ZjNIvN2E9GF6uXDu4Ux+AlOTLk9BChxUF8uBX9ev5cvWtQ=
107 107 2e2e9a0750f91a6fe0ad88e4de34f8efefdcab08 0 iQIVAwUAVRw4nyBXgaxoKi1yAQIFExAAkbCPtLjQlJvPaYCL1KhNR+ZVAmn7JrFH3XhvR26RayYbs4NxR3W1BhwhDy9+W+28szEx1kQvmr6t1bXAFywY0tNJOeuLU7uFfmbgAfYgkQ9kpsQNqFYkjbCyftw0S9vX9VOJ9DqUoDWuKfX7VzjkwE9dCfKI5F+dvzxnd6ZFjB85nyHBQuTZlzXl0+csY212RJ2G2j/mzEBVyeZj9l7Rm+1X8AC1xQMWRJGiyd0b7nhYqoOcceeJFAV1t9QO4+gjmkM5kL0orjxTnuVsxPTxcC5ca1BfidPWrZEto3duHWNiATGnCDylxxr52BxCAS+BWePW9J0PROtw1pYaZ9pF4N5X5LSXJzqX7ZiNGckxqIjry09+Tbsa8FS0VkkYBEiGotpuo4Jd05V6qpXfW2JqAfEVo6X6aGvPM2B7ZUtKi30I4J+WprrOP3WgZ/ZWHe1ERYKgjDqisn3t/D40q30WQUeQGltGsOX0Udqma2RjBugO5BHGzJ2yer4GdJXg7q1OMzrjAEuz1IoKvIB/o1pg86quVA4H2gQnL1B8t1M38/DIafyw7mrEY4Z3GL44Reev63XVvDE099Vbhqp7ufwq81Fpq7Xxa5vsr9SJ+8IqqQr8AcYSuK3G3L6BmIuSUAYMRqgl35FWoWkGyZIG5c6K6zI8w5Pb0aGi6Lb2Wfb9zbc=
108 108 e89f909edffad558b56f4affa8239e4832f88de0 0 iQIVAwUAVTBozCBXgaxoKi1yAQLHeg/+IvfpPmG7OSqCoHvMVETYdrqT7lKCwfCQWMFOC/2faWs1n4R/qQNm6ckE5OY888RK8tVQ7ue03Pg/iyWgQlYfS7Njd3WPjS4JsnEBxIvuGkIu6TPIXAUAH0PFTBh0cZEICDpPEVT2X3bPRwDHA+hUE9RrxM5zJ39Fpk/pTYCjQ9UKfEhXlEfka75YB39g2Y/ssaSbn5w/tAAx8sL72Y4G96D4IV2seLHZhB3VQ7UZKThEWn6UdVOoKj+urIwGaBYMeekGVtHSh6fnHOw3EtDO9mQ5HtAz2Bl4CwRYN8eSN+Dwgr+mdk8MWpQQJ+i1A8jUhUp8gn1Pe5GkIH4CWZ9+AvLLnshe2MkVaTT1g7EQk37tFkkdZDRBsOHIvpF71B9pEA1gMUlX4gKgh5YwukgpQlDmFCfY7XmX6eXw9Ub+EckEwYuGMz7Fbwe9J/Ce4DxvgJgq3/cu/jb3bmbewH6tZmcrlqziqqA8GySIwcURnF1c37e7+e7x1jhFJfCWpHzvCusjKhUp9tZsl9Rt1Bo/y41QY+avY7//ymhbwTMKgqjzCYoA+ipF4JfZlFiZF+JhvOSIFb0ltkfdqKD+qOjlkFaglvQU1bpGKLJ6cz4Xk2Jqt5zhcrpyDMGVv9aiWywCK2ZP34RNaJ6ZFwzwdpXihqgkm5dBGoZ4ztFUfmjXzIg=
109 109 8cc6036bca532e06681c5a8fa37efaa812de67b5 0 iQIVAwUAVUP0xCBXgaxoKi1yAQLIChAAme3kg1Z0V8t5PnWKDoIvscIeAsD2s6EhMy1SofmdZ4wvYD1VmGC6TgXMCY7ssvRBhxqwG3GxwYpwELASuw2GYfVot2scN7+b8Hs5jHtkQevKbxarYni+ZI9mw/KldnJixD1yW3j+LoJFh/Fu6GD2yrfGIhimFLozcwUu3EbLk7JzyHSn7/8NFjLJz0foAYfcbowU9/BFwNVLrQPnsUbWcEifsq5bYso9MBO9k+25yLgqHoqMbGpJcgjubNy1cWoKnlKS+lOJl0/waAk+aIjHXMzFpRRuJDjxEZn7V4VdV5d23nrBTcit1BfMzga5df7VrLPVRbom1Bi0kQ0BDeDex3hHNqHS5X+HSrd/njzP1xp8twG8hTE+njv85PWoGBTo1eUGW/esChIJKA5f3/F4B9ErgBNNOKnYmRgxixd562OWAwAQZK0r0roe2H/Mfg2VvgxT0kHd22NQLoAv0YI4jcXcCFrnV/80vHUQ8AsAYAbkLcz1jkfk3YwYDP8jbJCqcwJRt9ialYKJwvXlEe0TMeGdq7EjCO0z/pIpu82k2R/C0FtCFih3bUvJEmWoVVx8UGkDDQEORLbzxQCt0IOiQGFcoCCxgQmL0x9ZoljCWg5vZuuhU4uSOuRTuM+aa4xoLkeOcvgGRSOXrqfkV8JpWKoJB4dmY2qSuxw8LsAAzK0=
110 110 ed18f4acf435a2824c6f49fba40f42b9df5da7ad 0 iQIVAwUAVWy9mCBXgaxoKi1yAQIm+Q/+I/tV8DC51d4f/6T5OR+motlIx9U5za5p9XUUzfp3tzSY2PutVko/FclajVdFekZsK5pUzlh/GZhfe1jjyEEIr3UC3yWk8hMcvvS+2UDmfy81QxN7Uf0kz4mZOlME6d/fYDzf4cDKkkCXoec3kyZBw7L84mteUcrJoyb5K3fkQBrK5CG/CV7+uZN6b9+quKjtDhDEkAyc6phNanzWNgiHGucEbNgXsKM01HmV1TnN4GXTKx8y2UDalIJOPyes2OWHggibMHbaNnGnwSBAK+k29yaQ5FD0rsA+q0j3TijA1NfqvtluNEPbFOx/wJV4CxonYad93gWyEdgU34LRqqw1bx7PFUvew2/T3TJsxQLoCt67OElE7ScG8evuNEe8/4r3LDnzYFx7QMP5r5+B7PxVpj/DT+buS16BhYS8pXMMqLynFOQkX5uhEM7mNC0JTXQsBMHSDAcizVDrdFCF2OSfQjLpUfFP1VEWX7EInqj7hZrd+GE7TfBD8/rwSBSkkCX2aa9uKyt6Ius1GgQUuEETskAUvvpsNBzZxtvGpMMhqQLGlJYnBbhOmsbOyTSnXU66KJ5e/H3O0KRrF09i74v30DaY4uIH8xG6KpSkfw5s/oiLCtagfc0goUvvojk9pACDR3CKM/jVC63EVp2oUcjT72jUgSLxBgi7siLD8IW86wc=
111 111 540cd0ddac49c1125b2e013aa2ff18ecbd4dd954 0 iQIVAwUAVZRtzSBXgaxoKi1yAQJVLhAAtfn+8OzHIp6wRC4NUbkImAJRLsNTRPKeRSWPCF5O5XXQ84hp+86qjhndIE6mcJSAt4cVP8uky6sEa8ULd6b3ACRBvtgZtsecA9S/KtRjyE9CKr8nP+ogBNqJPaYlTz9RuwGedOd+8I9lYgsnRjfaHSByNMX08WEHtWqAWhSkAz/HO32ardS38cN97fckCgQtA8v7c77nBT7vcw4epgxyUQvMUxUhqmCVVhVfz8JXa5hyJxFrOtqgaVuQ1B5Y/EKxcyZT+JNHPtu3V1uc1awS/w16CEPstNBSFHax5MuT9UbY0mV2ZITP99EkM+vdomh82VHdnMo0i7Pz7XF45ychD4cteroO9gGqDDt9j7hd1rubBX1bfkPsd/APJlyeshusyTj+FqsUD/HDlvM9LRjY1HpU7i7yAlLQQ3851XKMLUPNFYu2r3bo8Wt/CCHtJvB4wYuH+7Wo3muudpU01ziJBxQrUWwPbUrG+7LvO1iEEVxB8l+8Vq0mU3Te7lJi1kGetm6xHNbtvQip5P2YUqvv+lLo/K8KoJDxsh63Y01JGwdmUDb8mnFlRx4J7hQJaoNEvz3cgnc4X8gDJD8sUOjGOPnbtz2QwTY+zj/5+FdLxWDCxNrHX5vvkVdJHcCqEfVvQTKfDMOUeKuhjI7GD7t3xRPfUxq19jjoLPe7aqn1Z1s=
112 112 96a38d44ba093bd1d1ecfd34119e94056030278b 0 iQIVAwUAVarUUyBXgaxoKi1yAQIfJw/+MG/0736F/9IvzgCTF6omIC+9kS8JH0n/JBGPhpbPAHK4xxjhOOz6m3Ia3c3HNoy+I6calwU6YV7k5dUzlyLhM0Z5oYpdrH+OBNxDEsD5SfhclfR63MK1kmgtD33izijsZ++6a+ZaVfyxpMTksKOktWSIDD63a5b/avb6nKY64KwJcbbeXPdelxvXV7TXYm0GvWc46BgvrHOJpYHCDaXorAn6BMq7EQF8sxdNK4GVMNMVk1njve0HOg3Kz8llPB/7QmddZXYLFGmWqICyUn1IsJDfePxzh8sOYVCbxAgitTJHJJmmH5gzVzw7t7ljtmxSJpcUGQJB2MphejmNFGfgvJPB9c6xOCfUqDjxN5m24V+UYesZntpfgs3lpfvE7785IpVnf6WfKG4PKty01ome/joHlDlrRTekKMlpiBapGMfv8EHvPBrOA+5yAHNfKsmcyCcjD1nvXYZ2/X9qY35AhdcBuNkyp55oPDOdtYIHfnOIxlYMKG1dusDx3Z4eveF0lQTzfRVoE5w+k9A2Ov3Zx0aiSkFFevJjrq5QBfs9dAiT8JYgBmWhaJzCtJm12lQirRMKR/br88Vwt/ry/UVY9cereMNvRYUGOGfC8CGGDCw4WDD+qWvyB3mmrXVuMlXxQRIZRJy5KazaQXsBWuIsx4kgGqC5Uo+yzpiQ1VMuCyI=
113 113 21aa1c313b05b1a85f8ffa1120d51579ddf6bf24 0 iQIVAwUAVbuouCBXgaxoKi1yAQL2ng//eI1w51F4YkDiUAhrZuc8RE/chEd2o4F6Jyu9laA03vbim598ntqGjX3+UkOyTQ/zGVeZfW2cNG8zkJjSLk138DHCYl2YPPD/yxqMOJp/a7U34+HrA0aE5Y2pcfx+FofZHRvRtt40UCngicjKivko8au7Ezayidpa/vQbc6dNvGrwwk4KMgOP2HYIfHgCirR5UmaWtNpzlLhf9E7JSNL5ZXij3nt6AgEPyn0OvmmOLyUARO/JTJ6vVyLEtwiXg7B3sF5RpmyFDhrkZ+MbFHgL4k/3y9Lb97WaZl8nXJIaNPOTPJqkApFY/56S12PKYK4js2OgU+QsX1XWvouAhEx6CC6Jk9EHhr6+9qxYFhBJw7RjbswUG6LvJy/kBe+Ei5UbYg9dATf3VxQ6Gqs19lebtzltERH2yNwaHyVeqqakPSonOaUyxGMRRosvNHyrTTor38j8d27KksgpocXzBPZcc1MlS3vJg2nIwZlc9EKM9z5R0J1KAi1Z/+xzBjiGRYg5EZY6ElAw30eCjGta7tXlBssJiKeHut7QTLxCZHQuX1tKxDDs1qlXlGCMbrFqo0EiF9hTssptRG3ZyLwMdzEjnh4ki6gzONZKDI8uayAS3N+CEtWcGUtiA9OwuiFXTwodmles/Mh14LEhiVZoDK3L9TPcY22o2qRuku/6wq6QKsg=
114 114 1a45e49a6bed023deb229102a8903234d18054d3 0 iQIVAwUAVeYa2SBXgaxoKi1yAQLWVA//Q7vU0YzngbxIbrTPvfFiNTJcT4bx9u1xMHRZf6QBIE3KtRHKTooJwH9lGR0HHM+8DWWZup3Vzo6JuWHMGoW0v5fzDyk2czwM9BgQQPfEmoJ/ZuBMevTkTZngjgHVwhP3tHFym8Rk9vVxyiZd35EcxP+4F817GCzD+K7XliIBqVggmv9YeQDXfEtvo7UZrMPPec79t8tzt2UadI3KC1jWUriTS1Fg1KxgXW6srD80D10bYyCkkdo/KfF6BGZ9SkF+U3b95cuqSmOfoyyQwUA3JbMXXOnIefnC7lqRC2QTC6mYDx5hIkBiwymXJBe8rpq/S94VVvPGfW6A5upyeCZISLEEnAz0GlykdpIy/NogzhmWpbAMOus05Xnen6xPdNig6c/M5ZleRxVobNrZSd7c5qI3aUUyfMKXlY1j9oiUTjSKH1IizwaI3aL/MM70eErBxXiLs2tpQvZeaVLn3kwCB5YhywO3LK0x+FNx4Gl90deAXMYibGNiLTq9grpB8fuLg9M90JBjFkeYkrSJ2yGYumYyP/WBA3mYEYGDLNstOby4riTU3WCqVl+eah6ss3l+gNDjLxiMtJZ/g0gQACaAvxQ9tYp5eeRMuLRTp79QQPxv97s8IyVwE/TlPlcSFlEXAzsBvqvsolQXRVi9AxA6M2davYabBYAgRf6rRfgujoU=
115 115 9a466b9f9792e3ad7ae3fc6c43c3ff2e136b718d 0 iQIVAwUAVg1oMSBXgaxoKi1yAQLPag/+Pv0+pR9b9Y5RflEcERUzVu92q+l/JEiP7PHP9pAZuXoQ0ikYBFo1Ygw8tkIG00dgEaLk/2b7E3OxaU9pjU3thoX//XpTcbkJtVhe7Bkjh9/S3dRpm2FWNL9n0qnywebziB45Xs8XzUwBZTYOkVRInYr/NzSo8KNbQH1B4u2g56veb8u/7GtEvBSGnMGVYKhVUZ3jxyDf371QkdafMOJPpogkZcVhXusvMZPDBYtTIzswyxBJ2jxHzjt8+EKs+FI3FxzvQ9Ze3M5Daa7xfiHI3sOgECO8GMVaJi0F49lttKx08KONw8xLlEof+cJ+qxLxQ42X5XOQglJ2/bv5ES5JiZYAti2XSXbZK96p4wexqL4hnaLVU/2iEUfqB9Sj6itEuhGOknPD9fQo1rZXYIS8CT5nGTNG4rEpLFN6VwWn1btIMNkEHw998zU7N3HAOk6adD6zGcntUfMBvQC3V4VK3o7hp8PGeySrWrOLcC/xLKM+XRonz46woJK5D8w8lCVYAxBWEGKAFtj9hv9R8Ye9gCW0Q8BvJ7MwGpn+7fLQ1BVZdV1LZQTSBUr5u8mNeDsRo4H2hITQRhUeElIwlMsUbbN078a4JPOUgPz1+Fi8oHRccBchN6I40QohL934zhcKXQ+NXYN8BgpCicPztSg8O8Y/qvhFP12Zu4tOH8P/dFY=
116 116 b66e3ca0b90c3095ea28dfd39aa24247bebf5c20 0 iQIVAwUAViarTyBXgaxoKi1yAQLZgRAAh7c7ebn7kUWI5M/b/T6qHGjFrU5azkjamzy9IG+KIa2hZgSMxyEM7JJUFqKP4TiWa3sW03bjKGSM/SjjDSSyheX+JIVSPNyKrBwneYhPq45Ius8eiHziClkt0CSsl2d9xDRpI0JmHbN0Pf8nh7rnbL+231GDAOT6dP+2S8K1HGa/0BgEcL9gpYs4/2GyjL+hBSUjyrabzvwe48DCN5W0tEJbGFw5YEADxdfbVbNEuXL81tR4PFGiJxPW0QKRLDB74MWmiWC0gi2ZC/IhbNBZ2sLb6694d4Bx4PVwtiARh63HNXVMEaBrFu1S9NcMQyHvAOc6Zw4izF/PCeTcdEnPk8J1t5PTz09Lp0EAKxe7CWIViy350ke5eiaxO3ySrNMX6d83BOHLDqEFMSWm+ad+KEMT4CJrK4X/n/XMgEFAaU5nWlIRqrLRIeU2Ifc625T0Xh4BgTqXPpytQxhgV5b+Fi6duNk4cy+QnHT4ymxI6BPD9HvSQwc+O7h37qjvJVZmpQX6AP8O75Yza8ZbcYKRIIxZzOkwNpzE5A/vpvP5bCRn7AGcT3ORWmAYr/etr3vxUvt2fQz6U/R4S915V+AeWBdcp+uExu6VZ42M0vhhh0lyzx1VRJGVdV+LoxFKkaC42d0yT+O1QEhSB7WL1D3/a/iWubv6ieB/cvNMhFaK9DA=
117 117 47dd34f2e7272be9e3b2a5a83cd0d20be44293f4 0 iQIVAwUAVjZiKiBXgaxoKi1yAQKBWQ/+JcE37vprSOA5e0ezs/avC7leR6hTlXy9O5bpFnvMpbVMTUp+KfBE4HxTT0KKXKh9lGtNaQ+lAmHuy1OQE1hBKPIaCUd8/1gunGsXgRM3TJ9LwjFd4qFpOMxvOouc6kW5kmea7V9W2fg6aFNjjc/4/0J3HMOIjmf2fFz87xqR1xX8iezJ57A4pUPNViJlOWXRzfa56cI6VUe5qOMD0NRXcY+JyI5qW25Y/aL5D9loeKflpzd53Ue+Pu3qlhddJd3PVkaAiVDH+DYyRb8sKgwuiEsyaBO18IBgC8eDmTohEJt6707A+WNhwBJwp9aOUhHC7caaKRYhEKuDRQ3op++VqwuxbFRXx22XYR9bEzQIlpsv9GY2k8SShU5MZqUKIhk8vppFI6RaID5bmALnLLmjmXfSPYSJDzDuCP5UTQgI3PKPOATorVrqMdKzfb7FiwtcTvtHAXpOgLaY9P9XIePbnei6Rx9TfoHYDvzFWRqzSjl21xR+ZUrJtG2fx7XLbMjEAZJcnjP++GRvNbHBOi57aX0l2LO1peQqZVMULoIivaoLFP3i16RuXXQ/bvKyHmKjJzGrLc0QCa0yfrvV2m30RRMaYlOv7ToJfdfZLXvSAP0zbAuDaXdjGnq7gpfIlNE3xM+kQ75Akcf4V4fK1p061EGBQvQz6Ov3PkPiWL/bxrQ=
118 118 1aa5083cbebbe7575c88f3402ab377539b484897 0 iQIVAwUAVkEdCCBXgaxoKi1yAQKdWg//crTr5gsnHQppuD1p+PPn3/7SMsWJ7bgbuaXgERDLC0zWMfhM2oMmu/4jqXnpangdBVvb0SojejgzxoBo9FfRQiIoKt0vxmmn+S8CrEwb99rpP4M7lgyMAInKPMXQdYxkoDNwL70Afmog6eBtlxjYnu8nmUE/swu6JoVns+tF8UOvIKFYbuCcGujo2pUOQC0xBGiHeHSGRDJOlWmY2d7D/PkQtQE/u/d4QZt7enTHMiV44XVJ8+0U0f1ZQE7V+hNWf+IjwcZtL95dnQzUKs6tXMIln/OwO+eJ3d61BfLvmABvCwUC9IepPssNSFBUfGqBAP5wXOzFIPSYn00IWpmZtCnpUNL99X1IV3RP+p99gnEDTScQFPYt5B0q5I1nFdRh1p48BSF/kjPA7V++UfBwMXrrYLKhUR9BjmrRzYnyXJKwbH6iCNj5hsXUkVrBdBi/FnMczgsVILfFcIXUfnJD3E/dG+1lmuObg6dEynxiGChTuaR4KkLa5ZRkUcUl6fWlSRsqSNbGEEbdwcI+nTCZqJUlLSghumhs0Z89Hs1nltBd1ALX2VLJEHrKMrFQ8NfEBeCB6ENqMJi5qPlq354MCdGOZ9RvisX/HlxE4Q61BW0+EwnyXSch6LFSOS3axOocUazMoK1XiOTJSv/5bAsnwb0ztDWeUj9fZEJL+SWtgB8=
119 119 2d437a0f3355834a9485bbbeb30a52a052c98f19 0 iQIVAwUAVl5U9CBXgaxoKi1yAQLocg//a4YFz9UVSIEzVEJMUPJnN2dBvEXRpwpb5CdKPd428+18K6VWZd5Mc6xNNRV5AV/hCYylgqDplIvyOvwCj7uN8nEOrLUQQ0Pp37M5ZIX8ZVCK/wgchJ2ltabUG1NrZ7/JA84U79VGLAECMnD0Z9WvZDESpVXmdXfxrk1eCc3omRB0ofNghEx+xpYworfZsu8aap1GHQuBsjPv4VyUWGpMq/KA01PdxRTELmrJnfSyr0nPKwxlI5KsbA1GOe+Mk3tp5HJ42DZqLtKSGPirf6E+6lRJeB0H7EpotN4wD3yZDsw6AgRb2C/ay/3T3Oz7CN+45mwuujV9Cxx5zs1EeOgZcqgA/hXMcwlQyvQDMrWpO8ytSBm6MhOuFOTB3HnUxfsnfSocLJsbNwGWKceAzACcXSqapveVAz/7h+InFgl/8Qce28UJdnX5wro5gP6UWt+xrvc7vfmVGgI3oxbiOUrfglhkjmrxBjEiDQy4BWH7HWMZUVxnqPQRcxIE10+dv0KtM/PBkbUtnbGJ88opFBGkFweje5vQcZy/duuPEIufRkPr8EV47QjOxlvldEjlLq3+QUdJZEgCIFw1X0y7Pix4dsPFjwOmAyo4El1ePrdFzG3dXSVA3eHvMDRnYnNlue9wHvKhYbBle5xTOZBgGuMzhDVe+54JLql5JYr4WrI1pvA=
120 120 ea389970c08449440587712117f178d33bab3f1e 0 iQIVAwUAVociGyBXgaxoKi1yAQJx9Q//TzMypcls5CQW3DM9xY1Q+RFeIw1LcDIev6NDBjUYxULb2WIK2qPw4Th5czF622SMd+XO/kiQeWYp9IW90MZOUVT1YGgUPKlKWMjkf0lZEPzprHjHq0+z/no1kBCBQg2uUOLsb6Y7zom4hFCyPsxXOk5nnxcFEK0VDbODa9zoKb/flyQ7rtzs+Z6BljIQ0TJAJsXs+6XgrW1XJ/f6nbeqsQyPklIBJuGKiaU1Pg8wQe6QqFaO1NYgM3hBETku6r3OTpUhu/2FTUZ7yDWGGzBqmifxzdHoj7/B+2qzRpII77PlZqoe6XF+UOObSFnhKvXKLjlGY5cy3SXBMbHkPcYtHua8wYR8LqO2bYYnsDd9qD0DJ+LlqH0ZMUkB2Cdk9q/cp1PGJWGlYYecHP87DLuWKwS+a6LhVI9TGkIUosVtLaIMsUUEz83RJFb4sSGOXtjk5DDznn9QW8ltXXMTdGQwFq1vmuiXATYenhszbvagrnbAnDyNFths4IhS1jG8237SB36nGmO3zQm5V7AMHfSrISB/8VPyY4Si7uvAV2kMWxuMhYuQbBwVx/KxbKrYjowuvJvCKaV101rWxvSeU2wDih20v+dnQKPveRNnO8AAK/ICflVVsISkd7hXcfk+SnhfxcPQTr+HQIJEW9wt5Q8WbgHk9wuR8kgXQEX6tCGpT/w=
121 121 158bdc8965720ca4061f8f8d806563cfc7cdb62e 0 iQIVAwUAVqBhFyBXgaxoKi1yAQLJpQ//S8kdgmVlS+CI0d2hQVGYWB/eK+tcntG+bZKLto4bvVy5d0ymlDL0x7VrJMOkwzkU1u/GaYo3L6CVEiM/JGCgB32bllrpx+KwQ0AyHswMZruo/6xrjDIYymLMEJ9yonXBZsG7pf2saYTHm3C5/ZIPkrDZSlssJHJDdeWqd75hUnx3nX8dZ4jIIxYDhtdB5/EmuEGOVlbeBHVpwfDXidSJUHJRwJvDqezUlN003sQdUvOHHtRqBrhsYEhHqPMOxDidAgCvjSfWZQKOTKaPE/gQo/BP3GU++Fg55jBz+SBXpdfQJI2Gd8FZfjLkhFa9vTTTcd10YCd4CZbYLpj/4R2xWj1U4oTVEFa6d+AA5Yyu8xG53XSCCPyzfagyuyfLqsaq5r1qDZO/Mh5KZCTvc9xSF5KXj57mKvzMDpiNeQcamGmsV4yXxymKJKGMQvbnzqp+ItIdbnfk38Nuac8rqNnGmFYwMIPa50680vSZT/NhrlPJ8FVTJlfHtSUZbdjPpsqw7BgjFWaVUdwgCKIGERiK7zfR0innj9rF5oVwT8EbKiaR1uVxOKnTwZzPCbdO1euNg/HutZLVQmugiLAv5Z38L3YZf5bH7zJdUydhiTI4mGn/mgncsKXoSarnnduhoYu9OsQZc9pndhxjAEuAslEIyBsLy81fR2HOhUzw5FGNgdY=
122 122 2408645de650d8a29a6ce9e7dce601d8dd0d1474 0 iQIVAwUAVq/xFSBXgaxoKi1yAQLsxhAAg+E6uJCtZZOugrrFi9S6C20SRPBwHwmw22PC5z3Ufp9Vf3vqSL/+zmWI9d/yezIVcTXgM9rKCvq58sZvo4FuO2ngPx7bL9LMJ3qx0IyHUKjwa3AwrzjSzvVhNIrRoimD+lVBI/GLmoszpMICM+Nyg3D41fNJKs6YpnwwsHNJkjMwz0n2SHAShWAgIilyANNVnwnzHE68AIkB/gBkUGtrjf6xB9mXQxAv4GPco/234FAkX9xSWsM0Rx+JLLrSBXoHmIlmu9LPjC0AKn8/DDke+fj7bFaF7hdJBUYOtlYH6f7NIvyZSpw0FHl7jPxoRCtXzIV+1dZEbbIMIXzNtzPFVDYDfMhLqpTgthkZ9x0UaMaHecCUWYYBp8G/IyVS40GJodl8xnRiXUkFejbK/NDdR1f9iZS0dtiFu66cATMdb6d+MG+zW0nDKiQmBt6bwynysqn4g3SIGQFEPyEoRy0bXiefHrlkeHbdfc4zgoejx3ywcRDMGvUbpWs5C43EPu44irKXcqC695vAny3A7nZpt/XP5meDdOF67DNQPvhFdjPPbJBpSsUi2hUlZ+599wUfr3lNVzeEzHT7XApTOf6ysuGtHH3qcVHpFqQSRL1MI0f2xL13UadgTVWYrnHEis7f+ncwlWiR0ucpJB3+dQQh3NVGVo89MfbIZPkA8iil03U=
123 123 b698abf971e7377d9b7ec7fc8c52df45255b0329 0 iQIVAwUAVrJ4YCBXgaxoKi1yAQJsKw/+JHSR0bIyarO4/VilFwsYxCprOnPxmUdS4qc4yjvpbf7Dqqr/OnOHJA29LrMoqWqsHgREepemjqiNindwNtlZec+KgmbF08ihSBBpls96UTTYTcytKRkkbrB+FhwB0iDl/o8RgGPniyG6M7gOp6p8pXQVRCOToIY1B/G0rtpkcU1N3GbiZntO5Fm/LPAVIE74VaDsamMopQ/wEB8qiERngX/M8SjO1ZSaVNW6KjRUsarLXQB9ziVJBolK/WnQsDwEeuWU2udpjBiOHnFC6h84uBpc8rLGhr419bKMJcjgl+0sl2zHGPY2edQYuJqVjVENzf4zzZA+xPgKw3GrSTpd37PEnGU/fufdJ0X+pp3kvmO1cV3TsvVMTCn7NvS6+w8SGdHdwKQQwelYI6vmJnjuOCATbafJiHMaOQ0GVYYk6PPoGrYcQ081x6dStCMaHIPOV1Wirwd2wq+SN9Ql8H6njftBf5Sa5tVWdW/zrhsltMsdZYZagZ/oFT3t83exL0rgZ96bZFs0j3HO3APELygIVuQ6ybPsFyToMDbURNDvr7ZqPKhQkkdHIUMqEez5ReuVgpbO9CWV/yWpB1/ZCpjNBZyDvw05kG2mOoC7AbHc8aLUS/8DetAmhwyb48LW4qjfUkO7RyxVSxqdnaBOMlsg1wsP2S+SlkZKsDHjcquZJ5U=
124 124 d493d64757eb45ada99fcb3693e479a51b7782da 0 iQIVAwUAVtYt4SBXgaxoKi1yAQL6TQ/9FzYE/xOSC2LYqPdPjCXNjGuZdN1WMf/8fUMYT83NNOoLEBGx37C0bAxgD4/P03FwYMuP37IjIcX8vN6fWvtG9Oo0o2n/oR3SKjpsheh2zxhAFX3vXhFD4U18wCz/DnM0O1qGJwJ49kk/99WNgDWeW4n9dMzTFpcaeZBCu1REbZQS40Z+ArXTDCr60g5TLN1XR1WKEzQJvF71rvaE6P8d3GLoGobTIJMLi5UnMwGsnsv2/EIPrWHQiAY9ZEnYq6deU/4RMh9c7afZie9I+ycIA/qVH6vXNt3/a2BP3Frmv8IvKPzqwnoWmIUamew9lLf1joD5joBy8Yu+qMW0/s6DYUGQ4Slk9qIfn6wh4ySgT/7FJUMcayx9ONDq7920RjRc+XFpD8B3Zhj2mM+0g9At1FgX2w2Gkf957oz2nlgTVh9sdPvP6UvWzhqszPMpdG5Vt0oc5vuyobW333qSkufCxi5gmH7do1DIzErMcy8b6IpZUDeQ/dakKwLQpZVVPF15IrNa/zsOW55SrGrL8/ErM/mXNQBBAqvRsOLq2njFqK2JaoG6biH21DMjHVZFw2wBRoLQxbOppfz2/e3mNkNy9HjgJTW3+0iHWvRzMSjwRbk9BlbkmH6kG5163ElHq3Ft3uuQyZBL9I5SQxlHi9s/CV0YSTYthpWR3ChKIMoqBQ0=
125 125 ae279d4a19e9683214cbd1fe8298cf0b50571432 0 iQIVAwUAVvqzViBXgaxoKi1yAQKUCxAAtctMD3ydbe+li3iYjhY5qT0wyHwPr9fcLqsQUJ4ZtD4sK3oxCRZFWFxNBk5bIIyiwusSEJPiPddoQ7NljSZlYDI0HR3R4vns55fmDwPG07Ykf7aSyqr+c2ppCGzn2/2ID476FNtzKqjF+LkVyadgI9vgZk5S4BgdSlfSRBL+1KtB1BlF5etIZnc5U9qs1uqzZJc06xyyF8HlrmMZkAvRUbsx/JzA5LgzZ2WzueaxZgYzYjDk0nPLgyPPBj0DVyWXnW/kdRNmKHNbaZ9aZlWmdPCEoq5iBm71d7Xoa61shmeuVZWvxHNqXdjVMHVeT61cRxjdfxTIkJwvlRGwpy7V17vTgzWFxw6QJpmr7kupRo3idsDydLDPHGUsxP3uMZFsp6+4rEe6qbafjNajkRyiw7kVGCxboOFN0rLVJPZwZGksEIkw58IHcPhZNT1bHHocWOA/uHJTAynfKsAdv/LDdGKcZWUCFOzlokw54xbPvdrBtEOnYNp15OY01IAJd2FCUki5WHvhELUggTjfank1Tc3/Rt1KrGOFhg80CWq6eMiuiWkHGvYq3fjNLbgjl3JJatUFoB+cX1ulDOGsLJEXQ4v5DNHgel0o2H395owNlStksSeW1UBVk0hUK/ADtVUYKAPEIFiboh1iDpEOl40JVnYdsGz3w5FLj2w+16/1vWs=
126 126 740156eedf2c450aee58b1a90b0e826f47c5da64 0 iQIVAwUAVxLGMCBXgaxoKi1yAQLhIg/8DDX+sCz7LmqO47/FfTo+OqGR+bTTqpfK3WebitL0Z6hbXPj7s45jijqIFGqKgMPqS5oom1xeuGTPHdYA0NNoc/mxSCuNLfuXYolpNWPN71HeSDRV9SnhMThG5HSxI+P0Ye4rbsCHrVV+ib1rV81QE2kZ9aZsJd0HnGd512xJ+2ML7AXweM/4lcLmMthN+oi/dv1OGLzfckrcr/fEATCLZt55eO7idx11J1Fk4ptQ6dQ/bKznlD4hneyy1HMPsGxw+bCXrMF2C/nUiRLHdKgGqZ+cDq6loQRfFlQoIhfoEnWC424qbjH4rvHgkZHqC59Oi/ti9Hi75oq9Tb79yzlCY/fGsdrlJpEzrTQdHFMHUoO9CC+JYObXHRo3ALnC5350ZBKxlkdpmucrHTgcDabfhRlx9vDxP4RDopm2hAjk2LJH7bdxnGEyZYkTOZ3hXKnVpt2hUQb4jyzzC9Kl47TFpPKNVKI+NLqRRZAIdXXiy24KD7WzzE6L0NNK0/IeqKBENLL8I1PmDQ6XmYTQVhTuad1jjm2PZDyGiXmJFZO1O/NGecVTvVynKsDT6XhEvzyEtjXqD98rrhbeMHTcmNSwwJMDvm9ws0075sLQyq2EYFG6ECWFypdA/jfumTmxOTkMtuy/V1Gyq7YJ8YaksZ7fXNY9VuJFP72grmlXc6Dvpr4=
127 127 f85de28eae32e7d3064b1a1321309071bbaaa069 0 iQIVAwUAVyZQaiBXgaxoKi1yAQJhCQ//WrRZ55k3VI/OgY+I/HvgFHOC0sbhe207Kedxvy00a3AtXM6wa5E95GNX04QxUfTWUf5ZHDfEgj0/mQywNrH1oJG47iPZSs+qXNLqtgAaXtrih6r4/ruUwFCRFxqK9mkhjG61SKicw3Q7uGva950g6ZUE5BsZ7XJWgoDcJzWKR+AH992G6H//Fhi4zFQAmB34++sm80wV6wMxVKA/qhQzetooTR2x9qrHpvCKMzKllleJe48yzPLJjQoaaVgXCDav0eIePFNw0WvVSldOEp/ADDdTGa65qsC1rO2BB1Cu5+frJ/vUoo0PwIgqgD6p2i41hfIKvkp6130TxmRVxUx+ma8gBYEpPIabV0flLU72gq8lMlGBBSnQ+fcZsfs/Ug0xRN0tzkEScmZFiDxRGk0y7IalXzv6irwOyC2fZCajXGJDzkROQXWMgy9eKkwuFhZBmPVYtrATSq3jHLVmJg5vfdeiVzA6NKxAgGm2z8AsRrijKK8WRqFYiH6xcWKG5u+FroPQdKa0nGCkPSTH3tvC6fAHTVm7JeXch5QE/LiS9Y575pM2PeIP+k+Fr1ugK0AEvYJAXa5UIIcdszPyI+TwPTtWaQ83X99qGAdmRWLvSYjqevOVr7F/fhO3XKFXRCcHA3EzVYnG7nWiVACYF3H2UgN4PWjStbx/Qhhdi9xAuks=
128 128 a56296f55a5e1038ea5016dace2076b693c28a56 0 iQIVAwUAVyZarCBXgaxoKi1yAQL87g/8D7whM3e08HVGDHHEkVUgqLIfueVy1mx0AkRvelmZmwaocFNGpZTd3AjSwy6qXbRNZFXrWU85JJvQCi3PSo/8bK43kwqLJ4lv+Hv2zVTvz30vbLWTSndH3oVRu38lIA7b5K9J4y50pMCwjKLG9iyp+aQG4RBz76fJMlhXy0gu38A8JZVKEeAnQCbtzxKXBzsC8k0/ku/bEQEoo9D4AAGlVTbl5AsHMp3Z6NWu7kEHAX/52/VKU2I0LxYqRxoL1tjTVGkAQfkOHz1gOhLXUgGSYmA9Fb265AYj9cnGWCfyNonlE0Rrk2kAsrjBTGiLyb8WvK/TZmRo4ZpNukzenS9UuAOKxA22Kf9+oN9kKBu1HnwqusYDH9pto1WInCZKV1al7DMBXbGFcnyTXk2xuiTGhVRG5LzCO2QMByBLXiYl77WqqJnzxK3v5lAc/immJl5qa3ATUlTnVBjAs+6cbsbCoY6sjXCT0ClndA9+iZZ1TjPnmLrSeFh5AoE8WHmnFV6oqGN4caX6wiIW5vO+x5Q2ruSsDrwXosXIYzm+0KYKRq9O+MaTwR44Dvq3/RyeIu/cif/Nc7B8bR5Kf7OiRf2T5u97MYAomwGcQfXqgUfm6y7D3Yg+IdAdAJKitxhRPsqqdxIuteXMvOvwukXNDiWP1zsKoYLI37EcwzvbGLUlZvg=
129 129 aaabed77791a75968a12b8c43ad263631a23ee81 0 iQIVAwUAVzpH4CBXgaxoKi1yAQLm5A/9GUYv9CeIepjcdWSBAtNhCBJcqgk2cBcV0XaeQomfxqYWfbW2fze6eE+TrXPKTX1ajycgqquMyo3asQolhHXwasv8+5CQxowjGfyVg7N/kyyjgmJljI+rCi74VfnsEhvG/J4GNr8JLVQmSICfALqQjw7XN8doKthYhwOfIY2vY419613v4oeBQXSsItKC/tfKw9lYvlk4qJKDffJQFyAekgv43ovWqHNkl4LaR6ubtjOsxCnxHfr7OtpX3muM9MLT/obBax5I3EsmiDTQBOjbvI6TcLczs5tVCnTa1opQsPUcEmdA4WpUEiTnLl9lk9le/BIImfYfEP33oVYmubRlKhJYnUiu89ao9L+48FBoqCY88HqbjQI1GO6icfRJN/+NLVeE9wubltbWFETH6e2Q+Ex4+lkul1tQMLPcPt10suMHnEo3/FcOTPt6/DKeMpsYgckHSJq5KzTg632xifyySmb9qkpdGGpY9lRal6FHw3rAhRBqucMgxso4BwC51h04RImtCUQPoA3wpb4BvCHba/thpsUFnHefOvsu3ei4JyHXZK84LPwOj31PcucNFdGDTW6jvKrF1vVUIVS9uMJkJXPu0V4i/oEQSUKifJZivROlpvj1eHy3KeMtjq2kjGyXY2KdzxpT8wX/oYJhCtm1XWMui5f24XBjE6xOcjjm8k4=
130 130 a9764ab80e11bcf6a37255db7dd079011f767c6c 0 iQIVAwUAV09KHyBXgaxoKi1yAQJBWg/+OywRrqU+zvnL1tHJ95PgatsF7S4ZAHZFR098+oCjUDtKpvnm71o2TKiY4D5cckyD2KNwLWg/qW6V+5+2EYU0Y/ViwPVcngib/ZeJP+Nr44TK3YZMRmfFuUEEzA7sZ2r2Gm8eswv//W79I0hXJeFd/o6FgLnn7AbOjcOn3IhWdGAP6jUHv9zyJigQv6K9wgyvAnK1RQE+2CgMcoyeqao/zs23IPXI6XUHOwfrQ7XrQ83+ciMqN7XNRx+TKsUQoYeUew4AanoDSMPAQ4kIudsP5tOgKeLRPmHX9zg6Y5S1nTpLRNdyAxuNuyZtkQxDYcG5Hft/SIx27tZUo3gywHL2U+9RYD2nvXqaWzT3sYB2sPBOiq7kjHRgvothkXemAFsbq2nKFrN0PRua9WG4l3ny0xYmDFPlJ/s0E9XhmQaqy+uXtVbA2XdLEvE6pQ0YWbHEKMniW26w6LJkx4IV6RX/7Kpq7byw/bW65tu/BzgISKau5FYLY4CqZJH7f8QBg3XWpzB91AR494tdsD+ugM45wrY/6awGQx9CY5SAzGqTyFuSFQxgB2rBurb01seZPf8nqG8V13UYXfX/O3/WMOBMr7U/RVqmAA0ZMYOyEwfVUmHqrFjkxpXX+JdNKRiA1GJp5sdRpCxSeXdQ/Ni6AAGZV2IyRb4G4Y++1vP4yPBalas=
131 131 26a5d605b8683a292bb89aea11f37a81b06ac016 0 iQIVAwUAV3bOsSBXgaxoKi1yAQLiDg//fxmcNpTUedsXqEwNdGFJsJ2E25OANgyv1saZHNfbYFWXIR8g4nyjNaj2SjtXF0wzOq5aHlMWXjMZPOT6pQBdTnOYDdgv+O8DGpgHs5x/f+uuxtpVkdxR6uRP0/ImlTEtDix8VQiN3nTu5A0N3C7E2y+D1JIIyTp6vyjzxvGQTY0MD/qgB55Dn6khx8c3phDtMkzmVEwL4ItJxVRVNw1m+2FOXHu++hJEruJdeMV0CKOV6LVbXHho+yt3jQDKhlIgJ65EPLKrf+yRalQtSWpu7y/vUMcEUde9XeQ5x05ebCiI4MkJ0ULQro/Bdx9vBHkAstUC7D+L5y45ZnhHjOwxz9c3GQMZQt1HuyORqbBhf9hvOkUQ2GhlDHc5U04nBe0VhEoCw9ra54n+AgUyqWr4CWimSW6pMTdquCzAAbcJWgdNMwDHrMalCYHhJksKFARKq3uSTR1Noz7sOCSIEQvOozawKSQfOwGxn/5bNepKh4uIRelC1uEDoqculqCLgAruzcMNIMndNVYaJ09IohJzA9jVApa+SZVPAeREg71lnS3d8jaWh1Lu5JFlAAKQeKGVJmNm40Y3HBjtHQDrI67TT59oDAhjo420Wf9VFCaj2k0weYBLWSeJhfUZ5x3PVpAHUvP/rnHPwNYyY0wVoQEvM/bnQdcpICmKhqcK+vKjDrM=
132 132 519bb4f9d3a47a6e83c2b414d58811ed38f503c2 0 iQIVAwUAV42tNyBXgaxoKi1yAQI/Iw//V0NtxpVD4sClotAwffBVW42Uv+SG+07CJoOuFYnmHZv/plOzXuuJlmm95L00/qyRCCTUyAGxK/eP5cAKP2V99ln6rNhh8gpgvmZlnYjU3gqFv8tCQ+fkwgRiWmgKjRL6/bK9FY5cO7ATLVu3kCkFd8CEgzlAaUqBfkNFxZxLDLvKqRlhXxVXhKjvkKg5DZ6eJqRQY7w3UqqR+sF1rMLtVyt490Wqv7YQKwcvY7MEKTyH4twGLx/RhBpBi+GccVKvWC011ffjSjxqAfQqrrSVt0Ld1Khj2/p1bDDYpTgtdDgCzclSXWEQpmSdFRBF5wYs/pDMUreI/E6mlWkB4hfZZk1NBRPRWYikXwnhU3ziubCGesZDyBYLrK1vT+tf6giseo22YQmDnOftbS999Pcn04cyCafeFuOjkubYaINB25T20GS5Wb4a0nHPRAOOVxzk/m/arwYgF0ZZZDDvJ48TRMDf3XOc1jc5qZ7AN/OQKbvh2B08vObnnPm3lmBY1qOnhwzJxpNiq+Z/ypokGXQkGBfKUo7rWHJy5iXLb3Biv9AhxY9d5pSTjBmTAYJEic3q03ztzlnfMyi+C13+YxFAbSSNGBP8Hejkkz0NvmB1TBuCKpnZA8spxY5rhZ/zMx+cCw8hQvWHHDUURps7SQvZEfrJSCGJFPDHL3vbfK+LNwI=
133 133 299546f84e68dbb9bd026f0f3a974ce4bdb93686 0 iQIcBAABCAAGBQJXn3rFAAoJELnJ3IJKpb3VmZoQAK0cdOfi/OURglnN0vYYGwdvSXTPpZauPEYEpwML3dW1j6HRnl5L+H8D8vlYzahK95X4+NNBhqtyyB6wmIVI0NkYfXfd6ACntJE/EnTdLIHIP2NAAoVsggIjiNr26ubRegaD5ya63Ofxz+Yq5iRsUUfHet7o+CyFhExyzdu+Vcz1/E9GztxNfTDVpC/mf+RMLwQTfHOhoTVbaamLCmGAIjw39w72X+vRMJoYNF44te6PvsfI67+6uuC0+9DjMnp5eL/hquSQ1qfks71rnWwxuiPcUDZloIueowVmt0z0sO4loSP1nZ5IP/6ZOoAzSjspqsxeay9sKP0kzSYLGsmCi29otyVSnXiKtyMCW5z5iM6k8XQcMi5mWy9RcpqlNYD7RUTn3g0+a8u7F6UEtske3/qoweJLPhtTmBNOfDNw4JXwOBSZea0QnIIjCeCc4ZGqfojPpbvcA4rkRpxI23YoMrT2v/kp4wgwrqK9fi8ctt8WbXpmGoAQDXWj2bWcuzj94HsAhLduFKv6sxoDz871hqjmjjnjQSU7TSNNnVzdzwqYkMB+BvhcNYxk6lcx3Aif3AayGdrWDubtU/ZRNoLzBwe6gm0udRMXBj4D/60GD6TIkYeL7HjJwfBb6Bf7qvQ6y7g0zbYG9uwBmMeduU7XchErGqQGSEyyJH3DG9OLaFOj
134 134 ccd436f7db6d5d7b9af89715179b911d031d44f1 0 iQIVAwUAV8h7F0emf/qjRqrOAQjmdhAAgYhom8fzL/YHeVLddm71ZB+pKDviKASKGSrBHY4D5Szrh/pYTedmG9IptYue5vzXpspHAaGvZN5xkwrz1/5nmnCsLA8DFaYT9qCkize6EYzxSBtA/W1S9Mv5tObinr1EX9rCSyI4HEJYE8i1IQM5h07SqUsMKDoasd4e29t6gRWg5pfOYq1kc2MTck35W9ff1Fii8S28dqbO3cLU6g5K0pT0JLCZIq7hyTNQdxHAYfebxkVl7PZrZR383IrnyotXVKFFc44qinv94T50uR4yUNYPQ8Gu0TgoGQQjBjk1Lrxot2xpgPQAy8vx+EOJgpg/yNZnYkmJZMxjDkTGVrwvXtOXZzmy2jti7PniET9hUBCU7aNHnoJJLzIf+Vb1CIRP0ypJl8GYCZx6HIYwOQH6EtcaeUqq3r+WXWv74ijIE7OApotmutM9buTvdOLdZddBzFPIjykc6cXO+W4E0kl6u9/OHtaZ3Nynh0ejBRafRWAVw2yU3T9SgQyICsmYWJCThkj14WqCJr2b7jfGlg9MkQOUG6/3f4xz2R3SgyUD8KiGsq/vdBE53zh0YA9gppLoum6AY+z61G1NhVGlrtps90txZBehuARUUz2dJC0pBMRy8XFwXMewDSIe6ATg25pHZsxHfhcalBpJncBl8pORs7oQl+GKBVxlnV4jm1pCzLU=
135 135 149433e68974eb5c63ccb03f794d8b57339a80c4 0 iQIcBAABAgAGBQJX8AfCAAoJELnJ3IJKpb3VnNAP/3umS8tohcZTr4m6DJm9u4XGr2m3FWQmjTEfimGpsOuBC8oCgsq0eAlORYcV68zDax+vQHQu3pqfPXaX+y4ZFDuz0ForNRiPJn+Q+tj1+NrOT1e8h4gH0nSK4rDxEGaa6x01fyC/xQMqN6iNfzbLLB7+WadZlyBRbHaUeZFDlPxPDf1rjDpu1vqwtOrVzSxMasRGEceiUegwsFdFMAefCq0ya/pKe9oV+GgGfR4qNrP7BfpOBcN/Po/ctkFCbLOhHbu6M7HpBSiD57BUy5lfhQQtSjzCKEVTyrWEH0ApjjXKuJzLSyq7xsHKQSOPMgGQprGehyzdCETlZOdauGrC0t9vBCr7kXEhXtycqxBC03vknA2eNeV610VX+HgO9VpCVZWHtENiArhALCcpoEsJvT29xCBYpSii/wnTpYJFT9yW8tjQCxH0zrmEZJvO1/nMINEBQFScB/nzUELn9asnghNf6vMpSGy0fSM27j87VAXCzJ5lqa6WCL/RrKgvYflow/m5AzUfMQhpqpH1vmh4ba1zZ4123lgnW4pNZDV9kmwXrEagGbWe1rnmsMzHugsECiYQyIngjWzHfpHgyEr49Uc5bMM1MlTypeHYYL4kV1jJ8Ou0SC4aV+49p8Onmb2NlVY7JKV7hqDCuZPI164YXMxhPNst4XK0/ENhoOE+8iB6
136 136 438173c415874f6ac653efc1099dec9c9150e90f 0 iQIVAwUAWAZ3okemf/qjRqrOAQj89xAAw/6QZ07yqvH+aZHeGQfgJ/X1Nze/hSMzkqbwGkuUOWD5ztN8+c39EXCn8JlqyLUPD7uGzhTV0299k5fGRihLIseXr0hy/cvVW16uqfeKJ/4/qL9zLS3rwSAgWbaHd1s6UQZVfGCb8V6oC1dkJxfrE9h6kugBqV97wStIRxmCpMDjsFv/zdNwsv6eEdxbiMilLn2/IbWXFOVKJzzv9iEY5Pu5McFR+nnrMyUZQhyGtVPLSkoEPsOysorfCZaVLJ6MnVaJunp9XEv94Pqx9+k+shsQvJHWkc0Nnb6uDHZYkLR5v2AbFsbJ9jDHsdr9A7qeQTiZay7PGI0uPoIrkmLya3cYbU1ADhwloAeQ/3gZLaJaKEjrXcFSsz7AZ9yq74rTwiPulF8uqZxJUodk2m/zy83HBrxxp/vgxWJ5JP2WXPtB8qKY+05umAt4rQS+fd2H/xOu2V2d5Mq1WmgknLBLC0ItaNaf91sSHtgEy22GtcvWQE7S6VWU1PoSYmOLITdJKAsmb7Eq+yKDW9nt0lOpUu2wUhBGctlgXgcWOmJP6gL6edIg66czAkVBp/fpKNl8Z/A0hhpuH7nW7GW/mzLVQnc+JW4wqUVkwlur3NRfvSt5ZyTY/SaR++nRf62h7PHIjU+f0kWQRdCcEQ0X38b8iAjeXcsOW8NCOPpm0zcz3i8=
137 137 eab27446995210c334c3d06f1a659e3b9b5da769 0 iQIcBAABCAAGBQJYGNsXAAoJELnJ3IJKpb3Vf30QAK/dq5vEHEkufLGiYxxkvIyiRaswS+8jamXeHMQrdK8CuokcQYhEv9xiUI6FMIoX4Zc0xfoFCBc+X4qE+Ed9SFYWgQkDs/roJq1C1mTYA+KANMqJkDt00QZq536snFQvjCXAA5fwR/DpgGOOuGMRfvbjh7x8mPyVoPr4HDQCGFXnTYdn193HpTOqUsipzIV5OJqQ9p0sfJjwKP4ZfD0tqqdjTkNwMyJuwuRaReXFvGGCjH2PqkZE/FwQG0NJJjt0xaMUmv5U5tXHC9tEVobVV/qEslqfbH2v1YPF5d8Jmdn7F76FU5J0nTd+3rIVjYGYSt01cR6wtGnzvr/7kw9kbChw4wYhXxnmIALSd48FpA1qWjlPcAdHfUUwObxOxfqmlnBGtAQFK+p5VXCsxDZEIT9MSxscfCjyDQZpkY5S5B3PFIRg6V9bdl5a4rEt27aucuKTHj1Ok2vip4WfaIKk28YMjjzuOQRbr6Pp7mJcCC1/ERHUJdLsaQP+dy18z6XbDjX3O2JDRNYbCBexQyV/Kfrt5EOS5fXiByQUHv+PyR+9Ju6QWkkcFBfgsxq25kFl+eos4V9lxPOY5jDpw2BWu9TyHtTWkjL/YxDUGwUO9WA/WzrcT4skr9FYrFV/oEgi8MkwydC0cFICDfd6tr9upqkkr1W025Im1UBXXJ89bTVj
138 138 b3b1ae98f6a0e14c1e1ba806a6c18e193b6dae5c 0 iQIVAwUAWECEaEemf/qjRqrOAQjuZw/+IWJKnKOsaUMcB9ly3Fo/eskqDL6A0j69IXTJDeBDGMoyGbQU/gZyX2yc6Sw3EhwTSCXu5vKpzg3a6e8MNrC1iHqli4wJ/jPY7XtmiqTYDixdsBLNk46VfOi73ooFe08wVDSNB65xpZsrtPDSioNmQ2kSJwSHb71UlauS4xGkM74vuDpWvX5OZRSfBqMh6NjG5RwBBnS8mzA0SW2dCI2jSc5SCGIzIZpzM0xUN21xzq0YQbrk9qEsmi7ks0eowdhUjeET2wSWwhOK4jS4IfMyRO7KueUB05yHs4mChj9kNFNWtSzXKwKBQbZzwO/1Y7IJjU+AsbWkiUu+6ipqBPQWzS28gCwGOrv5BcIJS+tzsvLUKWgcixyfy5UAqJ32gCdzKC54FUpT2zL6Ad0vXGM6WkpZA7yworN4RCFPexXbi0x2GSTLG8PyIoZ4Iwgtj5NtsEDHrz0380FxgnKUIC3ny2SVuPlyD+9wepD3QYcxdRk1BIzcFT9ZxNlgil3IXRVPwVejvQ/zr6/ILdhBnZ8ojjvVCy3b86B1OhZj/ZByYo5QaykVqWl0V9vJOZlZfvOpm2HiDhm/2uNrVWxG4O6EwhnekAdaJYmeLq1YbhIfGA6KVOaB9Yi5A5BxK9QGXBZ6sLj+dIUD3QR47r9yAqVQE8Gr/Oh6oQXBQqOQv7WzBBs=
139 139 e69874dc1f4e142746ff3df91e678a09c6fc208c 0 iQIVAwUAWG0oGUemf/qjRqrOAQh3uhAAu4TN7jkkgH7Hxn8S1cB6Ru0x8MQutzzzpjShhsE/G7nzCxsZ5eWdJ5ItwXmKhunb7T0og54CGcTxfmdPtCI7AhhHh9/TM2Hv1EBcsXCiwjG8E+P6X1UJkijgTGjNWuCvEDOsQAvgywslECBNnXp2QA5I5UdCMeqDdTAb8ujvbD8I4pxUx1xXKY18DgQGJh13mRlfkEVnPxUi2n8emnwPLjbVVkVISkMFUkaOl8a4fOeZC1xzDpoQocoH2Q8DYa9RCPPSHHSYPNMWGCdNGN2CoAurcHWWvc7jNU28/tBhTazfFv8LYh63lLQ8SIIPZHJAOxo45ufMspzUfNgoD6y3vlF5aW7DpdxwYHnueh7S1Fxgtd9cOnxmxQsgiF4LK0a+VXOi/Tli/fivZHDRCGHJvJgsMQm7pzkay9sGohes6jAnsOv2E8DwFC71FO/btrAp07IRFxH9WhUeMsXLMS9oBlubMxMM58M+xzSKApK6bz2MkLsx9cewmfmfbJnRIK1xDv+J+77pWWNGlxCCjl1WU+aA3M7G8HzwAqjL75ASOWtBrJlFXvlLgzobwwetg6cm44Rv1P39i3rDySZvi4BDlOQHWFupgMKiXnZ1PeL7eBDs/aawrE0V2ysNkf9An+XJZkos2JSLPWcoNigfXNUu5c1AqsERvHA246XJzqvCEK8=
140 140 a1dd2c0c479e0550040542e392e87bc91262517e 0 iQIcBAABCAAGBQJYgBBEAAoJELnJ3IJKpb3VJosP/10rr3onsVbL8E+ri1Q0TJc8uhqIsBVyD/vS1MJtbxRaAdIV92o13YOent0o5ASFF/0yzVKlOWPQRjsYYbYY967k1TruDaWxJAnpeFgMni2Afl/qyWrW4AY2xegZNZCfMmwJA+uSJDdAn+jPV40XbuCZ+OgyZo5S05dfclHFxdc8rPKeUsJtvs5PMmCL3iQl1sulp1ASjuhRtFWZgSFsC6rb2Y7evD66ikL93+0/BPEB4SVX17vB/XEzdmh4ntyt4+d1XAznLHS33IU8UHbTkUmLy+82WnNH7HBB2V7gO47m/HhvaYjEfeW0bqMzN3aOUf30Vy/wB4HHsvkBGDgL5PYVHRRovGcAuCmnYbOkawqbRewW5oDs7UT3HbShNpxCxfsYpo7deHr11zWA3ooWCSlIRRREU4BfwVmn+Ds1hT5HM28Q6zr6GQZegDUbiT9i1zU0EpyfTpH7gc6NTVQrO1z1p70NBnQMqXcHjWJwjSwLER2Qify9MjrGXTL6ofD5zVZKobeRmq94mf3lDq26H7coraM9X5h9xa49VgAcRHzn/WQ6wcFCKDQr6FT67hTUOlF7Jriv8/5h/ziSZr10fCObKeKWN8Skur29VIAHHY4NuUqbM55WohD+jZ2O3d4tze1eWm5MDgWD8RlrfYhQ+cLOwH65AOtts0LNZwlvJuC7
141 141 e1526da1e6d84e03146151c9b6e6950fe9a83d7d 0 iQIVAwUAWJIKpUemf/qjRqrOAQjjThAAvl1K/GZBrkanwEPXomewHkWKTEy1s5d5oWmPPGrSb9G4LM/3/abSbQ7fnzkS6IWi4Ao0za68w/MohaVGKoMAslRbelaTqlus0wE3zxb2yQ/j2NeZzFnFEuR/vbUug7uzH+onko2jXrt7VcPNXLOa1/g5CWwaf/YPfJO4zv+atlzBHvuFcQCkdbcOJkccCnBUoR7y0PJoBJX6K7wJQ+hWLdcY4nVaxkGPRmsZJo9qogXZMw1CwJVjofxRI0S/5vMtEqh8srYsg7qlTNv8eYnwdpfuunn2mI7Khx10Tz85PZDnr3SGRiFvdfmT30pI7jL3bhOHALkaoy2VevteJjIyMxANTvjIUBNQUi+7Kj3VIKmkL9NAMAQBbshiQL1wTrXdqOeC8Nm1BfCQEox2yiC6pDFbXVbguwJZ5VKFizTTK6f6BdNYKTVx8lNEdjAsWH8ojgGWwGXBbTkClULHezJ/sODaZzK/+M/IzbGmlF27jJYpdJX8fUoybZNw9lXwIfQQWHmQHEOJYCljD9G1tvYY70+xAFexgBX5Ib48UK4DRITVNecyQZL7bLTzGcM0TAE0EtD4M42wawsYP3Cva9UxShFLICQdPoa4Wmfs6uLbXG1DDLol/j7b6bL+6W8E3AlW+aAPc8GZm51/w3VlYqqciWTc12OJpu8FiD0pZ/iBw+E=
142 142 25703b624d27e3917d978af56d6ad59331e0464a 0 iQIcBAABCAAGBQJYuMSwAAoJELnJ3IJKpb3VL3YP/iKWY3+K3cLUBD3Ne5MhfS7N3t6rlk9YD4kmU8JnVeV1oAfg36VCylpbJLBnmQdvC8AfBJOkXi6DHp9RKXXmlsOeoppdWYGX5RMOzuwuGPBii6cA6KFd+WBpBJlRtklz61qGCAtv4q8V1mga0yucihghzt4lD/PPz7mk6yUBL8s3rK+bIHGdEhnK2dfnn/U2G0K/vGgsYZESORISuBclCrrc7M3/v1D+FBMCEYX9FXYU4PhYkKXK1mSqzCB7oENu/WP4ijl1nRnEIyzBV9pKO4ylnXTpbZAr/e4PofzjzPXb0zume1191C3wvgJ4eDautGide/Pxls5s6fJRaIowf5XVYQ5srX/NC9N3K77Hy01t5u8nwcyAhjmajZYuB9j37nmiwFawqS/y2eHovrUjkGdelV8OM7/iAexPRC8i2NcGk0m6XuzWy1Dxr8453VD8Hh3tTeafd6v5uHXSLjwogpu/th5rk/i9/5GBzc1MyJgRTwBhVHi/yFxfyakrSU7HT2cwX/Lb5KgWccogqfvrFYQABIBanxLIeZxTv8OIjC75EYknbxYtvvgb35ZdJytwrTHSZN0S7Ua2dHx2KUnHB6thbLu/v9fYrCgFF76DK4Ogd22Cbvv6NqRoglG26d0bqdwz/l1n3o416YjupteW8LMxHzuwiJy69WP1yi10eNDq
143 143 ed5b25874d998ababb181a939dd37a16ea644435 0 iQIcBAABCAAGBQJY4r/gAAoJELnJ3IJKpb3VtwYP/RuTmo252ExXQk/n5zGJZvZQnI86vO1+yGuyOlGFFBwf1v3sOLW1HD7fxF6/GdT8CSQrRqtC17Ya3qtayfY/0AEiSuH2bklBXSB1H5wPyguS5iLqyilCJY0SkHYBIDhJ0xftuIjsa805wdMm3OdclnTOkYT+K1WL8Ylbx/Ni2Lsx1rPpYdcQ/HlTkr5ca1ZbNOOSxSNI4+ilGlKbdSYeEsmqB2sDEiSaDEoxGGoSgzAE9+5Q2FfCGXV0bq4vfmEPoT9lhB4kANE+gcFUvsJTu8Z7EdF8y3CJLiy8+KHO/VLKTGJ1pMperbig9nAXl1AOt+izBFGJGTolbR/ShkkDWB/QVcqIF5CysAWMgnHAx7HjnMDBOANcKzhMMfOi3GUvOCNNIqIIoJHKRHaRk0YbMdt7z2mKpTrRQ9Zadz764jXOqqrPgQFM3jkBHzAvZz9yShrHGh42Y+iReAF9pAN0xPjyZ5Y2qp+DSl0bIQqrAet6Zd3QuoJtXczAeRrAvgn7O9MyLnMyE5s7xxI7o8M7zfWtChLF8ytJUzmRo3iVJNOJH+Zls9N30PGw6vubQAnB5ieaVTv8lnNpcAnEQD/i0tmRSxzyyqoOQbnItIPKFOsaYW+eX9sgJmObU3yDc5k3cs+yAFD2CM/uiUsLcTKyxPNcP1JHBYpwhOjIGczSHVS1
144 144 77eaf9539499a1b8be259ffe7ada787d07857f80 0 iQIcBAABCAAGBQJY9iz9AAoJELnJ3IJKpb3VYqEQAJNkB09sXgYRLA4kGQv3p4v02q9WZ1lHkAhOlNwIh7Zp+pGvT33nHZffByA0v+xtJNV9TNMIFFjkCg3jl5Z42CCe33ZlezGBAzXU+70QPvOR0ojlYk+FdMfeSyCBzWYokIpImwNmwNGKVrUAfywdikCsUC2aRjKg4Mn7GnqWl9WrBG6JEOOUamdx8qV2f6g/utRiqj4YQ86P0y4K3yakwc1LMM+vRfrwvsf1+DZ9t7QRENNKQ6gRnUdfryqSFIWn1VkBVMwIN5W3yIrTMfgH1wAZxbnYHrN5qDK7mcbP7bOA3XWJuEC+3QRnheRFd/21O1dMFuYjaKApXPHRlTGRMOaz2eydbfBopUS1BtfYEh4/B/1yJb9/HDw6LiAjea7ACHiaNec83z643005AvtUuWhjX3QTPkYlQzWaosanGy1IOGtXCPp1L0A+9gUpqyqycfPjQCbST5KRzYSZn3Ngmed5Bb6jsgvg5e5y0En/SQgK/pTKnxemAmFFVvIIrrWGRKj0AD0IFEHEepmwprPRs97EZPoBPFAGmVRuASBeIhFQxSDIXV0ebHJoUmz5w1rTy7U3Eq0ff6nW14kjWOUplatXz5LpWJ3VkZKrI+4gelto5xpTI6gJl2nmezhXQIlInk17cPuxmiHjeMdlOHZRh/zICLhQNL5fGne0ZL+qlrXY
145 145 616e788321cc4ae9975b7f0c54c849f36d82182b 0 iQIVAwUAWPZuQkemf/qjRqrOAQjFlg/9HXEegJMv8FP+uILPoaiA2UCiqWUL2MVJ0K1cvafkwUq+Iwir8sTe4VJ1v6V+ZRiOuzs4HMnoGJrIks4vHRbAxJ3J6xCfvrsbHdl59grv54vuoL5FlZvkdIe8L7/ovKrUmNwPWZX2v+ffFPrsEBeVlVrXpp4wOPhDxCKTmjYVOp87YqXfJsud7EQFPqpV4jX8DEDtJWT95OE9x0srBg0HpSE95d/BM4TuXTVNI8fV41YEqearKeFIhLxu37HxUmGmkAALCi8RJmm4hVpUHgk3tAVzImI8DglUqnC6VEfaYb+PKzIqHelhb66JO/48qN2S/JXihpNHAVUBysBT0b1xEnc6eNsF2fQEB+bEcf8IGj7/ILee1cmwPtoK2OXR2+xWWWjlu2keVcKeI0yAajJw/dP21yvVzVq0ypst7iD+EGHLJWJSmZscbyH5ICr+TJ5yQvIGZJtfsAdAUUTM2xpqSDW4mT5kYyg75URbQ3AKI7lOhJBmkkGQErE4zIQMkaAqcWziVF20xiRWfJoFxT2fK5weaRGIjELH49NLlyvZxYc4LlRo9lIdC7l/6lYDdTx15VuEj1zx/91y/d7OtPm+KCA2Bbdqth8m/fMD8trfQ6jSG/wgsvjZ+S0eoXa92qIR/igsCI+6EwP7duuzL2iyKOPXupQVNN10PKI7EuKv4Lk=
146 146 bb96d4a497432722623ae60d9bc734a1e360179e 0 iQIVAwUAWQkDfEemf/qjRqrOAQierQ/7BuQ0IW0T0cglgqIgkLuYLx2VXJCTEtRNCWmrH2UMK7fAdpAhN0xf+xedv56zYHrlyHpbskDbWvsKIHJdw/4bQitXaIFTyuMMtSR5vXy4Nly34O/Xs2uGb3Y5qwdubeK2nZr4lSPgiRHb/zI/B1Oy8GX830ljmIOY7B0nUWy4DrXcy/M41SnAMLFyD1K6T/8tkv7M4Fai7dQoF9EmIIkShVPktI3lqp3m7infZ4XnJqcqUB0NSfQZwZaUaoalOdCvEIe3ab5ewgl/CuvlDI4oqMQGjXCtNLbtiZSwo6hvudO6ewT+Zn/VdabkZyRtXUxu56ajjd6h22nU1+vknqDzo5tzw6oh1Ubzf8tzyv3Gmmr+tlOjzfK7tXXnT3vR9aEGli0qri0DzOpsDSY0pDC7EsS4LINPoNdsGQrGQdoX++AISROlNjvyuo4Vrp26tPHCSupkKOXuZaiozycAa2Q+aI1EvkPZSXe8SAXKDVtFn05ZB58YVkFzZKAYAxkE/ven59zb4aIbOgR12tZbJoZZsVHrlf/TcDtiXVfIMEMsCtJ1tPgD1rAsEURWRxK3mJ0Ev6KTHgNz4PeBhq1gIP/Y665aX2+cCjc4+vApPUienh5aOr1bQFpIDyYZsafHGMUFNCwRh8bX98oTGa0hjqz4ypwXE4Wztjdc+48UiHARp/Y=
147 147 c850f0ed54c1d42f9aa079ad528f8127e5775217 0 iQIVAwUAWTQINUemf/qjRqrOAQjZDw//b4pEgHYfWRVDEmLZtevysfhlJzbSyLAnWgNnRUVdSwl4WRF1r6ds/q7N4Ege5wQHjOpRtx4jC3y/riMbrLUlaeUXzCdqKgm4JcINS1nXy3IfkeDdUKyOR9upjaVhIEzCMRpyzabdYuflh5CoxayO7GFk2iZ8c1oAl4QzuLSspn9w+znqDg0HrMDbRNijStSulNjkqutih9UqT/PYizhE1UjL0NSnpYyD1vDljsHModJc2dhSzuZ1c4VFZHkienk+CNyeLtVKg8aC+Ej/Ppwq6FlE461T/RxOEzf+WFAc9F4iJibSN2kAFB4ySJ43y+OKkvzAwc5XbUx0y6OlWn2Ph+5T54sIwqasG3DjXyVrwVtAvCrcWUmOyS0RfkKoDVepMPIhFXyrhGqUYSq25Gt6tHVtIrlcWARIGGWlsE+PSHi87qcnSjs4xUzZwVvJWz4fuM1AUG/GTpyt4w3kB85XQikIINkmSTmsM/2/ar75T6jBL3kqOCGOL3n7bVZsGXllhkkQ7e/jqPPWnNXm8scDYdT3WENNu34zZp5ZmqdTXPAIIaqGswnU04KfUSEoYtOMri3E2VvrgMkiINm9BOKpgeTsMb3dkYRw2ZY3UAH9QfdX9BZywk6v3kkE5ghLWMUoQ4sqRlTo7mJKA8+EodjmIGRV/kAv1f7pigg6pIWWEyo=
148 148 26c49ed51a698ec016d2b4c6b44ca3c3f73cc788 0 iQIcBAABCAAGBQJZXQSmAAoJELnJ3IJKpb3VmTwP/jsxFTlKzWU8EnEhEViiP2YREOD3AXU7685DIMnoyVAsZgxrt0CG6Y92b5sINCeh5B0ORPQ7+xi2Xmz6tX8EeAR+/Dpdx6K623yExf8kq91zgfMvYkatNMu6ZVfywibYZAASq02oKoX7WqSPcQG/OwgtdFiGacCrG5iMH7wRv0N9hPc6D5vAV8/H/Inq8twpSG5SGDpCdKj7KPZiY8DFu/3OXatJtl+byg8zWT4FCYKkBPvmZp8/sRhDKBgwr3RvF1p84uuw/QxXjt+DmGxgtjvObjHr+shCMcKBAuZ4RtZmyEo/0L81uaTElHu1ejsEzsEKxs+8YifnH070PTFoV4VXQyXfTc8AyaqHE6rzX96a/HjQiJnL4dFeTZIrUhGK3AkObFLWJxVTo4J8+oliBQQldIh1H2yb1ZMfwapLnUGIqSieHDGZ6K2ccNJK8Q7IRhTCvYc0cjsnbwTpV4cebGqf3WXZhX0cZN+TNfhh/HGRzR1EeAAavjJqpDam1OBA5TmtJd/lHLIRVR5jyG+r4SK0XDlJ8uSfah7MpVH6aQ6UrycPyFusGXQlIqJ1DYQaBrI/SRJfIvRUmvVz9WgKLe83oC3Ui3aWR9rNjMb2InuQuXjeZaeaYfBAUYACcGfCZpZZvoEkMHCqtTng1rbbFnKMFk5kVy9YWuVgK9Iuh0O5
149 149 857876ebaed4e315f63157bd157d6ce553c7ab73 0 iQIVAwUAWW9XW0emf/qjRqrOAQhI7A//cKXIM4l8vrWWsc1Os4knXm/2UaexmAwV70TpviKL9RxCy5zBP/EapCaGRCH8uNPOQTkWGR9Aucm3CtxhggCMzULQxxeH86mEpWf1xILWLySPXW/t2f+2zxrwLSAxxqFJtuYv83Pe8CnS3y4BlgHnBKYXH8XXuW8uvfc0lHKblhrspGBIAinx7vPLoGQcpYrn9USWUKq5d9FaCLQCDT9501FHKf5dlYQajevCUDnewtn5ohelOXjTJQClW3aygv/z+98Kq7ZhayeIiZu+SeP+Ay7lZPklXcy6eyRiQtGCa1yesb9v53jKtgxWewV4o6zyuUesdknZ/IBeNUgw8LepqTIJo6/ckyvBOsSQcda81DuYNUChZLYTSXYPHEUmYiz6CvNoLEgHF/oO5p6CZXOPWbmLWrAFd+0+1Tuq8BSh+PSdEREM3ZLOikkXoVzTKBgu4zpMvmBnjliBg7WhixkcG0v5WunlV9/oHAIpsKdL7AatU+oCPulp+xDpTKzRazEemYiWG9zYKzwSMk9Nc17e2tk+EtFSPsPo4iVCXMgdIZSTNBvynKEFXZQVPWVa+bYRdAmbSY8awiX7exxYL10UcpnN2q/AH/F7rQzAmo8eZ3OtD0+3Nk3JRx0/CMyzKLPYDpdUgwmaPb+s2Bsy7f7TfmA7jTa69YqB1/zVwlWULr0=
150 150 5544af8622863796a0027566f6b646e10d522c4c 0 iQIcBAABCAAGBQJZjJflAAoJELnJ3IJKpb3V19kQALCvTdPrpce5+rBNbFtLGNFxTMDol1dUy87EUAWiArnfOzW3rKBdYxvxDL23BpgUfjRm1fAXdayVvlj6VC6Dyb195OLmc/I9z7SjFxsfmxWilF6U0GIa3W0x37i05EjfcccrBIuSLrvR6AWyJhjLOBCcyAqD/HcEom00/L+o2ry9CDQNLEeVuNewJiupcUqsTIG2yS26lWbtLZuoqS2T4Nlg8wjJhiSXlsZSuAF55iUJKlTQP6KyWReiaYuEVfm/Bybp0A2bFcZCYpWPwnwKBdSCHhIalH8PO57gh9J7xJVnyyBg5PU6n4l6PrGOmKhNiU/xyNe36tEAdMW6svcVvt8hiY0dnwWqR6wgnFFDu0lnTMUcjsy5M5FBY6wSw9Fph8zcNRzYyaeUbasNonPvrIrk21nT3ET3RzVR3ri2nJDVF+0GlpogGfk9k7wY3808091BMsyV3448ZPKQeWiK4Yy4UOUwbKV7YAsS5MdDnC1uKjl4GwLn9UCY/+Q2/2R0CBZ13Tox+Nbo6hBRuRGtFIbLK9j7IIUhhZrIZFSh8cDNkC+UMaS52L5z7ECvoYIUpw+MJ7NkMLHIVGZ2Nxn0C7IbGO6uHyR7D6bdNpxilU+WZStHk0ppZItRTm/htar4jifnaCI8F8OQNYmZ3cQhxx6qV2Tyow8arvWb1NYXrocG
151 151 943c91326b23954e6e1c6960d0239511f9530258 0 iQIcBAABCAAGBQJZjKKZAAoJELnJ3IJKpb3VGQkP/0iF6Khef0lBaRhbSAPwa7RUBb3iaBeuwmeic/hUjMoU1E5NR36bDDaF3u2di5mIYPBONFIeCPf9/DKyFkidueX1UnlAQa3mjh/QfKTb4/yO2Nrk7eH+QtrYxVUUYYjwgp4rS0Nd/++I1IUOor54vqJzJ7ZnM5O1RsE7VI1esAC/BTlUuO354bbm08B0owsZBwVvcVvpV4zeTvq5qyPxBJ3M0kw83Pgwh3JZB9IYhOabhSUBcA2fIPHgYGYnJVC+bLOeMWI1HJkJeoYfClNUiQUjAmi0cdTC733eQnHkDw7xyyFi+zkKu6JmU1opxkHSuj4Hrjul7Gtw3vVWWUPufz3AK7oymNp2Xr5y1HQLDtNJP3jicTTG1ae2TdX5Az3ze0I8VGbpR81/6ShAvY2cSKttV3I+2k4epxTTTf0xaZS1eUdnFOox6acElG2reNzx7EYYxpHj17K8N2qNzyY78iPgbJ+L39PBFoiGXMZJqWCxxIHoK1MxlXa8WwSnsXAU768dJvEn2N1x3fl+aeaWzeM4/5Qd83YjFuCeycuRnIo3rejSX3rWFAwZE0qQHKI5YWdKDLxIfdHTjdfMP7np+zLcHt0DV/dHmj2hKQgU0OK04fx7BrmdS1tw67Y9bL3H3TDohn7khU1FrqrKVuqSLbLsxnNyWRbZQF+DCoYrHlIW
152 152 3fee7f7d2da04226914c2258cc2884dc27384fd7 0 iQIcBAABCAAGBQJZjOJfAAoJELnJ3IJKpb3VvikP/iGjfahwkl2BDZYGq6Ia64a0bhEh0iltoWTCCDKMbHuuO+7h07fHpBl/XX5XPnS7imBUVWLOARhVL7aDPb0tu5NZzMKN57XUC/0FWFyf7lXXAVaOapR4kP8RtQvnoxfNSLRgiZQL88KIRBgFc8pbl8hLA6UbcHPsOk4dXKvmfPfHBHnzdUEDcSXDdyOBhuyOSzRs8egXVi3WeX6OaXG3twkw/uCF3pgOMOSyWVDwD+KvK+IBmSxCTKXzsb+pqpc7pPOFWhSXjpbuYUcI5Qy7mpd0bFL3qNqgvUNq2gX5mT6zH/TsVD10oSUjYYqKMO+gi34OgTVWRRoQfWBwrQwxsC/MxH6ZeOetl2YkS13OxdmYpNAFNQ8ye0vZigJRA+wHoC9dn0h8c5X4VJt/dufHeXc887EGJpLg6GDXi5Emr2ydAUhBJKlpi2yss22AmiQ4G9NE1hAjxqhPvkgBK/hpbr3FurV4hjTG6XKsF8I0WdbYz2CW/FEbp1+4T49ChhrwW0orZdEQX7IEjXr45Hs5sTInT90Hy2XG3Kovi0uVMt15cKsSEYDoFHkR4NgCZX2Y+qS5ryH8yqor3xtel3KsBIy6Ywn8pAo2f8flW3nro/O6x+0NKGV+ZZ0uo/FctuQLBrQVs025T1ai/6MbscQXvFVZVPKrUzlQaNPf/IwNOaRa
153 153 920977f72c7b70acfdaf56ab35360584d7845827 0 iQIcBAABCAAGBQJZv+wSAAoJELnJ3IJKpb3VH3kQAJp3OkV6qOPXBnlOSSodbVZveEQ5dGJfG9hk+VokcK6MFnieAFouROoGNlQXQtzj6cMqK+LGCP/NeJEG323gAxpxMzc32g7TqbVEhKNqNK8HvQSt04aCVZXtBmP0cPzc348UPP1X1iPTkyZxaJ0kHulaHVptwGbFZZyhwGefauU4eMafJsYqwgiGmvDpjUFu6P8YJXliYeTo1HX2lNChS1xmvJbop1YHfBYACsi8Eron0vMuhaQ+TKYq8Zd762u2roRYnaQ23ubEaVsjGDUYxXXVmit2gdaEKk+6Rq2I+EgcI5XvFzK8gvoP7siz6FL1jVf715k9/UYoWj9KDNUm8cweiyiUpjHQt0S+Ro9ryKvQy6tQVunRZqBN/kZWVth/FlMbUENbxVyXZcXv+m7OLvk+vyK7UZ7yT+OBzgRr0PyUuafzSVW3e+RZJtGxYGM5ew2bWQ8L6wuBucRYZOSnXXtCw7cKEMlK3BTjfAfpHUdIZIG492R9d6aOECUK/MpNvCiXXaZoh5Kj4a0dARiuWFCZxWwt3bmOg13oQ841zLdzOi/YZe15vCm8OB4Ffg6CkmPKhZhnMwVbFmlaBcoaeMzzpMuog91J1M2zgEUBTYwe/HKiNr/0iilJMPFRpZ+zEb2GvVoc8FMttXi8aomlXf/6LHCC9ndexGC29jIzl41+
154 154 2f427b57bf9019c6dc3750baa539dc22c1be50f6 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAlnQtVIQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91TTkD/409sWTM9vUH2qkqNTb1IXyGpqzb9UGOSVDioz6rvgZEBgh9D1oBTWnfBXW8sOWR0A7iCL6qZh2Yi7g7p0mKGXh9LZViLtSwwMSXpNiGBO7RVPW+NQ6DOY5Rhr0i08UBiVEkZXHeIVCd2Bd6mhAiUsm5iUh9Jne10wO8cIxeAUnsx4DBdHBMWLg6AZKWllSgN+r9H+7wnOhDbkvj1Cu6+ugKpEs+xvbTh47OTyM+w9tC1aoZD4HhfR5w5O16FC+TIoE6wmWut6e2pxIMHDB3H08Dky6gNjucY/ntJXvOZW5kYrQA3LHKks8ebpjsIXesOAvReOAsDz0drwzbWZan9Cbj8yWoYz/HCgHCnX3WqKKORSP5pvdrsqYua9DXtJwBeSWY4vbIM2kECAiyw1SrOGudxlyWBlW1f1jhGR2DsBlwoieeAvUVoaNwO7pYirwxR4nFPdLDRCQ4hLK/GFiuyr+lGoc1WUzVRNBYD3udcOZAbqq4JhWLf0Gvd5xP0rn1cJNhHMvrPH4Ki4a5KeeK6gQI7GT9/+PPQzTdpxXj6KwofktJtVNqm5sJmJ+wMIddnobFlNNLZ/F7OMONWajuVhh+vSOV34YLdhqzAR5XItkeJL6qyAJjNH5PjsnhT7nMqjgwriPz6xxYOLJWgtK5ZqcSCx4gWy9KJVVja8wJ7rRUg==
155 155 1e2454b60e5936f5e77498cab2648db469504487 0 iQJVBAABCAA/FiEEOoFVFj0OIKUw/LeGR6Z/+qNGqs4FAlnqRBUhHGtidWxsb2NrK21lcmN1cmlhbEByaW5nd29ybGQub3JnAAoJEEemf/qjRqrOAQQP/28EzmTKFL/RxmNYePdzqrmcdJ2tn+s7OYmGdtneN2sESZ4MK0xb5Q8Mkm+41aXS52zzJdz9ynwdun8DG4wZ3sE5MOG+GgK6K0ecOv1XTKS3a2DkUM0fl5hlcXN7Zz7m7m5M6sy6vSxHP7kTyzQWt//z175ZLSQEu1a0nm/BLH+HP9e8DfnJ2Nfcnwp32kV0Nj1xTqjRV1Yo/oCnXfVvsxEJU+CDUGBiLc29ZcoWVbTw9c1VcxihJ6k0pK711KZ+bedSk7yc1OudiJF7idjB0bLQY6ESHNNNjK8uLppok0RsyuhvvDTAoTsl1rMKGmXMM0Ela3/5oxZ/5lUZB73vEJhzEi48ULvstpq82EO39KylkEfQxwMBPhnBIHQaGRkl7QPLXGOYUDMY6gT08Sm3e8/NqEJc/AgckXehpH3gSS2Ji2xg7/E8H5plGsswFidw//oYTTwm0j0halWpB521TD2wmjkjRHXzk1mj0EoFQUMfwHTIZU3E8flUBasD3mZ9XqZJPr66RV7QCrXayH75B/i0CyNqd/Hv5Tkf2TlC3EkEBZwZyAjqw7EyL1LuS936sc7fWuMFsH5k/fwjVwzIc1LmP+nmk2Dd9hIC66vec4w1QZeeAXuDKgOJjvQzj2n+uYRuObl4kKcxvoXqgQN0glGuB1IW7lPllGHR1kplhoub
156 156 0ccb43d4cf01d013ae05917ec4f305509f851b2d 0 iQJVBAABCAA/FiEEOoFVFj0OIKUw/LeGR6Z/+qNGqs4FAln6Qp8hHGtidWxsb2NrK21lcmN1cmlhbEByaW5nd29ybGQub3JnAAoJEEemf/qjRqrOJ8MP/2ufm/dbrFoE0F8hewhztG1vS4stus13lZ9lmM9kza8OKeOgY/MDH8GaV3O8GnRiCNUFsVD8JEIexE31c84H2Ie7VQO0GQSUHSyMCRrbED6IvfrWp6EZ6RDNPk4LHBfxCuPmuVHGRoGZtsLKJBPIxIHJKWMlEJlj9BZuUxZp/8kurQ6CXwblVbFzXdOaZQlioOBH27Bk3S0+gXfJ+wA2ed5XOQvT9jwjqC8y/1t8obaoPTpzyAvb9NArG+9RT9vfNN42aWISZNwg6RW5oLJISqoGrAes6EoG7dZfOC0UoKMVYXoNvZzJvVlMHyjugIoid+WI+V8y9bPrRTfbPCmocCzEzCOLEHQta8roNijB0bKcq8hmQPHcMyXlj1Srnqlco49jbhftgJoPTwzb10wQyU0VFvaZDPW/EQUT3M/k4j3sVESjANdyG1iu6EDV080LK1LgAdhjpKMBbf6mcgAe06/07XFMbKNrZMEislOcVFp98BSKjdioUNpy91rCeSmkEsASJ3yMArRnSkuVgpyrtJaGWl79VUcmOwKhUOA/8MXMz/Oqu7hvve/sgv71xlnim460nnLw6YHPyeeCsz6KSoUK3knFXAbTk/0jvU1ixUZbI122aMzX04UgPGeTukCOUw49XfaOdN+x0YXlkl4PsrnRQhIoixY2gosPpK4YO73G
157 157 cabc840ffdee8a72f3689fb77dd74d04fdc2bc04 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAloB+EYQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91TfwEAC/pYW7TC8mQnqSJzde4yiv2+zgflfJzRlg5rbvlUQl1gSBla3sFADZcic0ebAc+8XUu8eIzyPX+oa4wjsHvL13silUCkUzTEEQLqfKPX1bhA4mwfSDb5A7v2VZ5q8qhRGnlhTsB79ML8uBOhR/Bigdm2ixURPEZ37pWljiMp9XWBMtxPxXn/m0n5CDViibX6QqQCR4k3orcsIGd72YXU6B8NGbBN8qlqMSd0pGvSF4vM2cgVhz7D71+zU4XL/HVP97aU9GsOwN9QWW029DOJu6KG6x51WWtfD/tzyNDu7+lZ5/IKyqHX4tyqCIXEGAsQ3XypeHgCq5hV3E6LJLRqPcLpUNDiQlCg6tNPRaOuMC878MRIlffKqMH+sWo8Z7zHrut+LfRh5/k1aCh4J+FIlE6Hgbvbvv2Z8JxDpUKl0Tr+i0oHNTapbGXIecq1ZFR4kcdchodUHXBC2E6HWR50/ek5YKPddzw8WPGsBtzXMfkhFr3WkvyP2Gbe2XJnkuYptTJA+u2CfhrvgmWsYlvt/myTaMZQEzZ+uir4Xoo5NvzqTL30SFqPrP4Nh0n9G6vpVJl/eZxoYK9jL3VC0vDhnZXitkvDpjXZuJqw/HgExXWKZFfiQ3X2HY48v1gvJiSegZ5rX+uGGJtW2/Mp5FidePEgnFIqZW/yhBfs2Hzj1D2A==
158 158 a92b9f8e11ba330614cdfd6af0e03b15c1ff3797 0 iQJVBAABCAA/FiEEOoFVFj0OIKUw/LeGR6Z/+qNGqs4FAlohslshHGtidWxsb2NrK21lcmN1cmlhbEByaW5nd29ybGQub3JnAAoJEEemf/qjRqrO7P8P/1qGts96acEdB9BZbK/Eesalb1wUByLXZoP8j+1wWwqh/Kq/q7V4Qe0z1jw/92oZbmnLy2C8sDhWv/XKxACKv69oPrcqQix1E8M+07u88ZXqHJMSxkOmvA2Vimp9EG1qgje+qchgOVgvhEhysA96bRpEnc6V0RnBqI5UdfbKtlfBmX5mUE/qsoBZhly1FTmzV1bhYlGgNLyqtJQpcbA34wyPoywsp8DRBiHWrIzz5XNR+DJFTOe4Kqio1i5r8R4QSIM5vtTbj5pbsmtGcP2CsFC9S3xTSAU6AEJKxGpubPk3ckNj3P9zolvR7krU5Jt8LIgXSVaKLt9rPhmxCbPrLtORgXkUupJcrwzQl+oYz5bkl9kowFa959waIPYoCuuW402mOTDq/L3xwDH9AKK5rELPl3fNo+5OIDKAKRIu6zRSAzBtyGT6kkfb1NSghumP4scR7cgUmLaNibZBa8eJj92gwf+ucSGoB/dF/YHWNe0jY09LFK3nyCoftmyLzxcRk1JLGNngw8MCIuisHTskhxSm/qlX7qjunoZnA3yy9behhy/YaFt4YzYZbMTivt2gszX5ktToaDqfxWDYdIa79kp8G68rYPeybelTS74LwbK3blXPI3I1nddkW52znHYLvW6BYyi+QQ5jPZLkiOC+AF0q+c4gYmPaLVN/mpMZjjmB
159 159 27b6df1b5adbdf647cf5c6675b40575e1b197c60 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAlpmbwIQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91W4BD/4h+y7QH7FkNcueOBrmdci7w1apkPX7KuknKxf8+FmA1QDGWYATnqD6IcAk3+f4reO4n9qc0y2BGrIz/pyTSIHvJW+ORrbPCKVrXlfUgkUK3TumtRObt8B75BVBBNaJ93r1yOALpo/K8wSwRrBF+Yl6aCoFiibUEbfcfaOAHVqZXKC1ZPtLRwq5NHIw0wWB0qNoAXj+FJV1EHO7SEjj2lXqw/r0HriQMdObWLgAb6QVUq7oVMpAumUeuQtZ169qHdqYfF1OLdCnsVBcwYEz/cBLC43bvYiwFxSkbAFyl656caWiwA3PISFSzP9Co0zWU/Qf8f7dTdAdT/orzCfUq8YoXqryfRSxi+8L8/EMxankzdW73Rx5X+0539pSq+gDDtTOyNuW6+CZwa5D84b31rsd+jTx8zVm3SRHRKsoGF2EEMQkWmDbhIFjX5W1fE84Ul3umypv+lPSvCPlQpIqv2hZmcTR12sgjdBjU8z+Zcq22SHFybqiYNmWpkVUtiMvTlHMoJfi5PI6xF8D2dxV4ErG+NflqdjaXydgnbO6D3/A1FCASig0wL4jMxSeRqnRRqLihN3VaGG2QH6MLJ+Ty6YuoonKtopw9JNOZydr/XN7K5LcjX1T3+31qmnHZyBXRSejWl9XN93IDbQcnMBWHkz/cJLN0kKu4pvnV8UGUcyXfA==
160 160 d334afc585e29577f271c5eda03378736a16ca6b 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAlpzZuUQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91TiDEADDD6Tn04UjgrZ36nAqOcHaG1ZT2Cm1/sbTw+6duAhf3+uKWFqi2bgcdCBkdfRH7KfEU0GNsPpiC6mzWw3PDWmGhnLJAkR+9FTBU0edK01hkNW8RelDTL5J9IzIGwrP4KFfcUue6yrxU8GnSxnf5Vy/N5ZZzLV/P3hdBte5We9PD5KHPAwTzzcZ9Wiog700rFDDChyFq7hNQ3H0GpknF6+Ck5XmJ3DOqt1MFHk9V4Z/ASU59cQXKOeaMChlBpTb1gIIWjOE99v5aY06dc1WlwttuHtCZvZgtAduRAB6XYWyniS/7nXBv0MXD3EWbpH1pkOaWUxw217HpNP4g9Yo3u/i8UW+NkSJOeXtC1CFjWmUNj138IhS1pogaiPPnIs+H6eOJsmnGhN2KbOMjA5Dn9vSTi6s/98TarfUSiwxA4L7fJy5qowFETftuBO0fJpbB8+ZtpnjNp0MMKed27OUSv69i6BmLrP+eqk+MVO6PovvIySlWAP9/REM/I5/mFkqoI+ruT4a9osNGDZ4Jqb382b7EmpEMDdgb7+ezsybgDfizuaTs/LBae7h79o1m30DxZ/EZ5C+2LY8twbGSORvZN4ViMVhIhWBTlOE/iVBOj807Y2OaUURcuLfHRmaCcfF1uIzg0uNB/aM/WSE0+AXh2IX+mipoTS3eh/V2EKldBHcOQ==
161 161 369aadf7a3264b03c8b09efce715bc41e6ab4a9b 0 iQJVBAABCAA/FiEEOoFVFj0OIKUw/LeGR6Z/+qNGqs4FAlqe5w8hHGtidWxsb2NrK21lcmN1cmlhbEByaW5nd29ybGQub3JnAAoJEEemf/qjRqrO1lUQAK6+S26rE3AMt6667ClT+ubPl+nNMRkWJXa8EyPplBUGTPdMheViOe+28dCsveJxqUF7A4TMLMA/eIj4cRIwmVbBaivfQKnG5GMZ+9N6j6oqE/OAJujdHzzZ3+o9KJGtRgJP2tzdY/6qkXwL3WN6KULz7pSkrKZLOiNfj4k2bf3bXeB7d3N5erxJYlhddlPBlHXImRkWiPR/bdaAaYJq+EEWCbia6MWXlSAqEjIgQi+ytuh/9Z+QSsJCsECDRqEExZClqHGkCLYhST99NqqdYCGJzAFMgh+xWxZxI0LO08pJxYctHGoHm+vvRVMfmdbxEydEy01H6jX+1e7Yq44bovIiIOkaXCTSuEBol+R5aPKJhgvqgZ5IlcTLoIYQBE3MZMKZ89NWy3TvgcNkQiOPCCkKs1+DukXKqTt62zOTxfa6mIZDCXdGai6vZBJ5b0yeEd3HV96yHb9dFlS5w1cG7prIBRv5BkqEaFbRMGZGV31Ri7BuVu0O68Pfdq+R+4A1YLdJ0H5DySe2dGlwE2DMKhdtVu1bie4UWHK10TphmqhBk6B9Ew2+tASCU7iczAqRzyzMLBTHIfCYO2R+5Yuh0CApt47KV23OcLje9nORyE2yaDTbVUPiXzdOnbRaCQf7eW5/1y/LLjG6OwtuETTcHKh7ruko+u7rFL96a4DNlNdk
162 162 8bba684efde7f45add05f737952093bb2aa07155 0 iQJVBAABCAA/FiEEOoFVFj0OIKUw/LeGR6Z/+qNGqs4FAlqe6dkhHGtidWxsb2NrK21lcmN1cmlhbEByaW5nd29ybGQub3JnAAoJEEemf/qjRqrOJmIQALUVCoWUFYYaRxGH4OpmIQ2o1JrMefvarFhaPY1r3+G87sjXgw15uobEQDtoybTUYbcdSxJQT1KE1FOm3wU0VyN6PY9c1PMEAVgJlve0eDiXNNlBsoYMXnpq1HidZknkjpXgUPdE/LElxpJJRlJQZlS29bkGmEDZQBoOvlcZoBRDSYcbM07wn7d+1gmJkcHViDBMAbSrudfO0OYzDC1BjtGyKm7Mes2WB1yFYw+ySa8hF/xPKEDvoZINOE5n3PBJiCvPuTw3PqsHvWgKOA1Obx9fATlxj7EHBLfKBTNfpUwPMRSH1cmA+qUS9mRDrdLvrThwalr6D3r2RJ2ntOipcZpKMmxARRV+VUAI1K6H0/Ws3XAxENqhF7RgRruJFVq8G8EcHJLZEoVHsR+VOnd/pzgkFKS+tIsYYRcMpL0DdMF8pV3xrEFahgRhaEZOh4jsG3Z+sGLVFFl7DdMqeGs6m/TwDrvfuYtGczfGRB0wqu8KOwhR1BjNJKcr4lk35GKwSXmI1vk6Z1gAm0e13995lqbCJwkuOKynQlHWVOR6hu3ypvAgV/zXLF5t8HHtL48sOJ8a33THuJT4whbXSIb9BQXu/NQnNhK8G3Kly5UN88vL4a3sZi/Y86h4R2fKOSib/txJ3ydLbMeS8LlJMqeF/hrBanVF0r15NZ2CdmL1Qxim
163 163 7de7bd407251af2bc98e5b809c8598ee95830daf 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAlrE4p0QHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91c4UD/4tC+mBWxBw/JYm4vlFTKWLHopLEa1/uhFRK/uGsdgcCyexbCDbisjJpl3JTQb+wQDlZnUorm8zB206y418YqhJ7lCauRgcoqKka0e3kvKnwmklwmuGkwOIoruWxxhCcgRCT4C+jZ/ZE3Kre0CKnUvlASsHtbkqrCqFClEcIlPVohlccmjbpQXN+akB40tkMF5Xf0AMBPYG7UievmeHhz3pO/yex/Uc6RhgWAqD4zjA1bh+3REGs3CaoYgKUTXZw/XYI9cqAI0FobRuXSVbq2dqkXCFLfD+WizxUz55rZA+CP4pqLndwxGm4fLy4gk2iLHxKfrHsAul7n5e4tHmxDcOOa1K0fIJDBijuXoNfXN7nF4NQUlfpmtOxUxfniVohvXJeYV8ecepsDMSFqDtEtbdhsep5QDx85lGLNLQAA1f36swJzLBSqGw688Hjql2c9txK2eVrVxNp+M8tqn9qU/h2/firgu9a2DxQB45M7ISfkutmpizN5TNlEyElH0htHnKG7+AIbRAm4novCXfSzP8eepk0kVwj9QMIx/rw4aeicRdPWBTcDIG0gWELb0skunTQqeZwPPESwimntdmwCxfFksgT0t79ZEDAWWfxNLhJP/HWO2mYG5GUJOzNQ4rj/YXLcye6A4KkhvuZlVCaKAbnm60ivoG082HYuozV4qPOQ==
164 164 ed5448edcbfa747b9154099e18630e49024fd47b 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAlrXnuoQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91fSHEACBVg4FsCE2nN5aEKAQb7l7rG4XTQ9FbvoTYB3tkvmsLQSRfh2GB2ZDBOI7Vswo2UxXupr4qSkUQbeHrwrk9A1s5b/T5e4wSKZuFJOrkwLVZDFfUHumKomqdoVj/D8+LDt7Rz+Wm7OClO/4dTAsl2E4rkl7XPtqjC3jESGad8IBANlPVBhNUMER4eFcPZzq1qi2MrlJKEKpdeZEWJ/ow7gka/aTLqHMfRwhA3kS5X34Yai17kLQZGQdWISWYiM9Zd2b/FSTHZGy8rf9cvjXs3EXfEB5nePveDrFOfmuubVRDplO+/naJjNBqwxeB99jb7Fk3sekPZNW/NqR/w1jvQFA3OP9fS2g1OwfXMWyx6DvBJNfQwppNH3JUvA5PEiorul4GJ2nuubXk+Or1yzoRJtwOGz/GQi2BcsPKaL6niewrInFw18jMVhx/4Jbpu+glaim4EvT/PfJ5KdSwF7pJxsoiqvw7A2C2/DsZRbCeal9GrTulkNf/hgpCJOBK1DqVVq1O5MI/oYQ69HxgMq9Ip1OGJJhse3qjevBJbpNCosCpjb3htlo4go29H8yyGJb09i05WtNW2EQchrTHrlruFr7mKJ5h1mAYket74QQyaGzqwgD5kwSVnIcwHpfb8oiJTwA5R+LtbAQXWC/fFu1g1KEp/4hGOQoRU04+mYuPsrzaA==
165 165 1ec874717d8a93b19e0d50628443e0ee5efab3a9 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAlraM3wQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91RAJEACSnf/HWwS0/OZaqz4Hfh0UBgkXDmH1IC90Pc/kczf//WuXu5AVnnRHDziOlCYYZAnZ2iKu0EQI6GT2K2garaWkaEhukOnjz4WADVys6DAzJyw5iOXeEpIOlZH6hbYbsW3zVcPjiMPo8cY5tIYEy4E/8RcVly1SDtWxvt/nWYQd2MxObLrpU7bPP6a2Db4Vy8WpGRbZRJmOvDNworld5rB5M/OGgHyMa9hg2Hjn+cLtQSEJY4O92A6h2hix9xpDC7zzfoluD2piDslocTm/gyeln2BJJBAtr+aRoHO9hI0baq5yFRQLO8aqQRJJP8dXgYZIWgSU/9oVGPZoGotJyw24iiB37R/YCisKE+cEUjfVclHTDFCkzmYP2ZMbGaktohJeF7EMau0ZJ8II5F0ja3bj6GrwfpGGY5OOcQrzIYW7nB0msFWTljb34qN3nd7m+hQ5hji3Hp9CFXEbCboVmm46LqwukSDWTmnfcP8knxWbBlJ4xDxySwTtcHAJhnUmKxu7oe3D/0Ttdv7HscI40eeMdr01pLQ0Ee3a4OumQ1hn+oL+o+tlqg8PKT20q528CMHgSJp6aIlU7pEK81b+Zj6B57us4P97qSL6XLNUIfubADCaf/KUDwh1HvKhHXV2aRli1GX1REFsy0ItGZn0yhQxIDJKc/FKsEMBKvlVIHGQFw==
166 166 6614cac550aea66d19c601e45efd1b7bd08d7c40 0 iQJVBAABCAA/FiEEOoFVFj0OIKUw/LeGR6Z/+qNGqs4FAlruOCQhHGtidWxsb2NrK21lcmN1cmlhbEByaW5nd29ybGQub3JnAAoJEEemf/qjRqrOENQQAI1ttaffqYucUEyBARP1GDlZMIGDJgNG7smPMU4Sw7YEzB9mcmxnBFlPx/9n973ucEnLJVONBSZq0VWIKJwPp1RMBpAHuGrMlhkMvYIAukg5EBN3YpA1UogHYycwLj2Ye7fNgiN5FIkaodt9++c4d1Lfu658A2pAeg8qUn5uJ77vVcZRp988u9eVDQfubS8P6bB4KZc87VDAUUeXy+AcS9KHGBmdRAabwU4m09VPZ4h8NEj3+YUPnKXBaNK9pXK5pnkmB8uFePayimnw6St6093oylQTVw/tfxGLBImnHw+6KCu2ut9r5PxXEVxVYpranGbS4jYqpzRtpQBxyo/Igu7fqrioR2rGLQL5NcHsoUEdOC7VW+0HgHjXKtRy7agmcFcgjFco47D3hor7Y16lwgm+RV2EWQ/u2M4Bbo1EWj1oxQ/0j5DOM5UeAJ3Jh64gb4sCDqJfADR8NQaxh7QiqYhn69IcjsEfzU/11VuqWXlQgghJhEEP/bojRyM0qee87CKLiTescafIfnRsNQhyhsKqdHU1QAp29cCqh3mzNxJH3PDYg4fjRaGW4PM7K5gmSXFn/Ifeza0cuZ4XLdYZ76Z1BG80pqBpKZy1unGob+RpItlSmO5jQw7OoRuf0q3Id92gawUDDLuQ7Xg3zOVqV8/wJBlHM7ZUz162bnNsO5Hn
167 167 9c5ced5276d6e7d54f7c3dadf5247b7ee98ec79c 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAlsYGdAQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91S3fEACmrG3S5eAUhnKqkXFe+HZUwmUvLKRhyWDLlWQzEHaJZQCFWxqSM1ag7JtAx3WkWwmWrOZ0+T/w/xMv81h9JAv9RsoszUT/RH4RsnWoc2ddcK93Q/PrNJ29kFjvC8j3LF42WfHEIeNqAki5c3GbprUL86KG7XVYuMvpPI/SeNSz8siPaKjXo6sg6bAupPCyapisTmeRHcCUc5UfeTTq4YQdS9UI0p9Fo8/vcqmnWY6XnQCRYs2U8Y2I2QCJBHBE5p4KrxrFsAdPWMCg0dJT0goSbzpfDjukPHQaAnUKjCtXCwrzA/KY8fDH9hm5tt1FnC6nl6BRpEHRoHqTfE1ag2QktJZTn5+JWpzz85qFDl5ktmxj1gS80jkOUJ2699RykBy7NACu+TtLJdBk+E1TN0pAU+zsrTSGiteuikEBjQP/8i4whUZCFIHLPgVlxrHWwn0/oszj1Q/u86sCxnYTflR2GLZs3fbSGBEKDDrjqwetxMlwi/3Qhf0PN9aAI7S13YnA89tGLGRLTsVsOoKiQoTExQaCUpE5jFYBLVjsTPh2AjPhG3Zaf7R5ZIvW4CbVYORNTMaYhFNnFyczILJLRid+INHLVifNiJuaLiAFD5Izq9Me4H+GpwB5AI7aG1r+01Si2KbqqpdfoK430UeDV+U/MvEU7v0RoeF30M7uVYv+kg==
168 168 0b63a6743010dfdbf8a8154186e119949bdaa1cc 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAls7n+0QHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91XVGEAC1aPuUmW9R0QjWUmyY4vMO7AOT4F1sHKrkgNaoG/RCvczuZOCz/fGliEKQ52pkvThrOgOvNfJlIGOu91noLKsYUybO8eeTksCzc7agUjk6/Xsed35D8gNEPuiVTNu379sTQRnOA2T/plQnVCY2PjMzBe6nQ2DJYnggJelCUxuqUsLM76OvMEeNlXvyxZmyAcFT5dfSBYbjAt0kklRRQWgaug3GwLJY/+0tmXhq0tCpAF6myXoVQm/ynSxjR+5+2/+F5nudOQmDnL0zGayOAQU97RLAAxf1L+3DTRfbtxams9ZrGfRzQGcI1d4I4ernfnFYI19kSzMPcW4qI7gQQlTfOzs8X5d2fKiqUFjlgOO42hgM6cQv2Hx3u+bxF00sAvrW8sWRjfMQACuNH3FJoeIubpohN5o1Madv4ayGAZkcyskYRCs9X40gn+Q9gv34uknjaF/mep7BBl08JC9zFqwGaLyCssSsHV7ncekkUZfcWfq4TNNEUZFIu7UtsnZYz0aYrueAKMp+4udTjfKKnSZL2o0n1g11iH9KTQO/dWP7rVbu/OIbLeE+D87oXOWGfDNBRyHLItrM70Vum0HxtFuWc1clj8qzF61Mx0umFfUmdGQcl9DGivmc7TLNzBKG11ElDuDIey6Yxc6nwWiAJ6v1H5bO3WBi/klbT2fWguOo5w==
169 169 e90130af47ce8dd53a3109aed9d15876b3e7dee8 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAltQ1bUQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91RQVD/9NA5t2mlt7pFc0Sswktc5dI8GaSYxgeknacLkEdkYx9L+mzg77G7TGueeu5duovjdI/vDIzdadGtJJ+zJE5icCqeUFDfNZNZLQ+7StuC8/f+4i/DaCzjHJ4tDYd0x6R5efisLWRKkWoodI1Iit7gCL493gj1HZaIzRLaqYkbOk3PhOEkTcov2cnhb4h54OKm07qlg6PYH507WGmmTDDnhL9SwdfBXHA2ps9dCe52NzPMyebXoZYA9T5Yz67eQ8D+YCh9bLauA59dW0Iyx59yGJ0tmLwVKBgbUkynAknwk/hdNlF7r6wLqbR00NLKmAZl8crdVSqFUU/vAsPQLn3BkbtpzqjmisIq2BWEt/YWYZOHUvJoK81cRcsVpPuAOIQM/rTm9pprTq7RFtuVnCj+QnmWwEPZJcS/7pnnIXte3gQt76ovLuFxr7dq99anEA7gnTbSdADIzgZhJMM8hJcrcgvbI4xz0H1qKn3webTNl/jPgTsNjAPYcmRZcoU2wUIR+OPhZvfwhvreRX0dGUV6gqxWnx3u3dsWE9jcBIGlNfYnIkLXyqBdOL6f4yQoxaVjRg/ScEt3hU17TknuPIDOXE/iMgWnYpnTqKBolt/Vbx7qB1OiK7AmQvXY1bnhtkIfOoIwZ9X1Zi2vmV1Wz4G0a5Vxq5eNKpQgACA2HE0MS2HQ==
170 170 33ac6a72308a215e6086fbced347ec10aa963b0a 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAlthwaIQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91atOD/0de4nA55WJpiQzAqTg4xWIRZB6y0pkQ8D4cKNQkNiwPQAdDEPf85RuYmoPusNxhM40qfJlmHOw8sbRaqqabhVBPEzL1DpKe4GBucagLZqoL3pycyMzhkhzMka2RJT6nekCchTKJTIs2gx4FOA/QwaFYNkXFfguAEvi01isVdMo0GFLQ7pf7wU8UO1PPdkYphH0xPUvsreQ3pR3+6WwMLovk4JYW4cSaM4YkLlqJQPSO2YAlyXAwiQRvu2A227ydVqHOgLeV5zMQPy2v2zTgl2AoMdWp8+g2lJrYwclkNR+LAk5OlGYamyZwlmsTO7OX3n7xJYtfjbqdoqEKhO1igMi3ZSjqwkaBxxkXxArrteD19bpUyInTjbwTRO3mSe5aNkEDGoOYWn8UOn5ZkeEo7NyhP4OTXqyxQs9rwjD79xZk+6fGB777vuZDUdLZYRQFOPEximpmCGJDrZWj5PeIALWkrRGWBl2eFJ5sl6/pFlUJDjDEstnrsfosp6NJ3VFiD9EunFWsTlV2qXaueh9+TfaSRmGHVuwFCDt7nATVEzTt8l74xsL3xUPS4u9EcNPuEhCRu1zLojCGjemEA29R9tJS8oWd6SwXKryzjo8SyN7yQVSM/yl212IOiOHTQF8vVZuJnailtcWc3D4NoOxntnnv8fnd1nr8M5QSjYQVzSkHw==
171 171 ede3bf31fe63677fdf5bd8db687977d4e3d792ed 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAluOq84QHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91ao3D/oC9zKNbk+MMUP0cSfl+ESRbP/sAI466IYDkr9f1klooIFMsdqCd16eS36DVwIwrBYapRaNszC6Pg0KCFKCdeAWJLcgeIawwOkZPrLKQmS3I9GTl9gxtExeFvRryaAdP1DAPEU6JkyHo3xmURkJB58VjuBquZz4cYnL2aE1ag04CWAoRFiLu6bt1hEZ8pONU6cbDpHaJVyUZmJRB+llpybgdLnlBTrhfWjNofTh8MM6+vz67lIienYoSbepY+029J98phBTV+UEfWSBWw1hcNT/+QmOBGWWTLfBARsNDZFeYgQQOo3gRghKO7qUA/hqzDTmMG4/a2obs0LGsBlcMZ1Ky//zhdAJ/EN7uH9svM1t1fkw1RgvftmybptK5KiusZ9AWhnggHSwZtj1I6i/sojqsj9MrtdrD+1LfiKuAv/FtcMHSeff8IfItrd2B67JIj4wCzU8vDrAbAAqODHx7AnssvNbYrH2iOigSINFMNJoLU/xLxBhTxitU2Zf8puHA4CQ3+BybgOH9HPqCtGcVAB7bcp4hiezGrachM+2oec2YwcGCpIobMPl43cmWkLhtGF5qfl7APVfbo18UXk8ZGmBY8YAYwEyksk2SBMJV6+XHw9J7uaaugc3uN8PuMVLqvSMpWN1ZdRsSkxrOJK+UNW7kbUi0wHnsV1rN0U0BIfVOQ==
172 172 5405cb1a79010ac50c58cd84e6f50c4556bf2a4c 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAluyfokQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91eWpD/0eu/JfD6SfaT4Ozd2767ojNIW4M9BgcRH/FehFBd/3iQ/YQmaMVd6GmdaagM5YUpD9U+rDK95l8rUstuTglXeKD2SVcDM4Oq9ToyZyp5aizWjkxRxHT60W95G5FQO/tBbs63jfNrVDWDElbkpcn/gUG6JbX+q/S/mKd6WsuwNQC1N4VOWp0OWCmFGBWN7t/DqxGLGEajJM0NB97/r/IV6TzrGtaPf1CXaepDVvZwIIeas/eQgGInyqry7WBSn5sCUq4opIh1UigMABUAgzIZbgTg8NLGSmEgRgk0Vb4K+pLejLLDb5YD7ZwuUCkbd8oJImKQfU6++Ajd70TbNQRvVhMtd15iCtOOjLR+VNkUiDXm0g1U53sREMLdj/+SMJZB6Z18DotdgpaeCmwA/wWijXOdt76xwUKjByioxyQilPrzrWGaoSG4ynjiD2Y+eSRS1DxbpDgt4YEuiVA6U3ay99oW7KkhFjQsUtKl4SJ5SQWiEofvgtb2maNrXkPtKOtNRHhc61v73zYnsxtl2qduC99YOTin90FykD80XvgJZfyow/LICb77MNGwYBsJJMDQ3jG1YyUC2CQsb8wyrWM4TO3tspKAQPyMegUaVtBqw7ZhgiC3OXEes+z+AL5YRSZXALfurXPYbja8M8uGL2TYB3/5bKYvBXxvfmSGIeY6VieQ==
173 173 956ec6f1320df26f3133ec40f3de866ea0695fd7 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAlvOG20QHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91eZ+EACb/XfPWaMkwIX54JaFWtL/nVkDcaL8xLVzlI+PxL0ZtHdQTGVQNp5f1BnZU9RKPZ9QOuz+QKNvb4hOOXBwmCi2AAjmTYUqtKThHmOT50ZRICkllY+YlZ3tI6JXRDhh7pSXaus8jBFG/VwuUlVmK5sA2TP+lIJijOgV9rThszfS4Q2I8sBTIaeZS1hyujFxGRO++tjYR+jPuo/98FhqJ5EylVYvKmnflWkOYLFNFqgDI6DQs7Dl+u2nrNAzZJQlgk+1ekd66T3WyK8U3tcFLZGRQ+gpzINH0Syn6USaaE+0nGi4we1hJS8JK0txWyHXJGNZYaWQAC2l1hIBfA38azwVLSe2w9JatXhS3HWByILy8JkEQ2kSo1xTD4mBkszZo/kWZpZRsAWydxCnzhNgKmTJYxASFTTX1mpdX4EzJBOs/++52y1OjVc0Ko0+6vSwxsC6zgIGJx1Os7vVgWHql0XbDmJ1NDdNmz7q5HjFcbNOWScKf6UGcBKV4dpW1w+7CvdoMFHUsVTa2zn6YOki3NEt0GWLXq+0aXbHSw8XETcyunQKjDi9ddKOw0rYGip6EKUKhOILZimQ0lgYRE23RDdT5Tl2D8s66SUuipgP9vGjbMaE/FhO3OAb7406jyCrOVfDis7sK0Hvw074GhIfZUjA4W4Ey2TeExCZHHhBdoPTrg==
174 174 a91a2837150bdcb27ae76b3646e6c93cd6a15904 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAlvclPMQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91fc0EADF/62jqCARFaQRRcKpobPNBZupwSbnQ7E296ZRwHdZvT8CVGfkWBUIStyh+r8bfmBzzea6d9/SUoRqCoV9rwCXuRbeCZZRMMkqx9IblV3foaIOxyQi0KE2lpzGJAHxPiNxD3czZV4B+P6X2wNmG9OLjmHyQ7o64GvPAJ+Ko/EsND1tkx4qB16mEuEHVxtfaG6hbjgpLekIA3+3xur3E8cWBsNO28HtQBK83r2qURwv6eG3TfkbmiE+Ie5TNC15LPVhAOHVSD7miZdI82uk2063puCKZxIJXsy7EMjHfChTM9c7B4+TdEBjms3y+Byz2EV7kRfjplGOnBbYvfY7qiteTn/22+rLrTTQNkndDN/Sqr1DjwsvxKDeIfsqgXzGQPupLOrGdGf4ILAtA0Reme7VKNN5Px6dNxnjKKwsnSrKTQ7ZcmD+W1LKlL63lBEQvEy+TLmmFLfM2xvvBxL5177AKZrj/8gMUzEi1K2MelDGrasA7OSjTlABoleDvZzVOf1nC0Bv83tFc8FeMHLwNOxkFSsjORvZuIH/G9BYUTAd96iLwQRBxXLOVNitxAOQT+s3hs7JEaUzTHlAY+lNeFAxUujb4H0V40Xgr20O1u7PJ53tzApIrg9JQPgvUXntmRs8fpNo6f3P6Sg8XtaCCHIUAB6qTHiose56llf6bzl66A==
175 175 1c8c54cf97256f4468da2eb4dbee24f7f3888e71 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAlwG+eIQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91YqSD/9IAwdaPrOeiT+DVBW2x33oFeY1X1f5CBG/vCJptalOd2QDIsD0ANEzQHmzV25RKD851v155Txt/BPlkuBfO/kg0BbOoqTpGZk+5CcoFWeyhJct2CxtCLdEpyZ/98/htMR4VfWprCX2GHXPjS813l9pebsN3WgBUOc2VaUdHNRoAGsMVgWC5BWwNP4XSA9oixFL/O4aGLQ6pPfP3vmMFySWXWnIN8gUZ4sm53eKaT0QCICAgzFh+GzRd81uACDfoJn1d8RS9GK+h6j8x0crLY5CpQQy8lRVkokvc0h6XK44ofc57p9GHAOfprHY3DbBhD9H6fLAf5raUsqPkLRYVGqhg8bOsBr3vJ56hiXJYOYPZSYXGjnHRcUrgfPVrY+6mPTeCIQMPmWBHwYH5Tc5TLrPuxxCL4wVywqGbfmIVP+WFUikkykAAwuPOZAswxJJOB0gsnnxcApmTeXRznBXyvzscMlWVZiMjzflKRRJ9V5RI4Fdc6n1wQ4vuLSO4AUnIypIsV6ZFAOBuFKH7x6nPG0tP3FYzcICaMOPbxEx3LStnuU+UuEs6TIxM6IiR3LPiiDGZ2BA2gjJhDxQFV8hAl8KDO3LsYuyUQCv3RTAP+YejH21bIXdnwDlNqy8Hrd53rq7jZsdb2pMVvOZZ3VmIu64f+jVkD/r5msDUkQL3M9jwg==
176 176 197f092b2cd9691e2a55d198f717b231af9be6f9 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAlwz6DUQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91SbtD/47TJkSFuDJrvrpLuZROeR48opM8kPtMdbFKZxmeUtap/1q1ahBcA8cnkf5t5iEna57OkPfx0FVw7zupFZSD970q8KeQa1C1oRf+DV83rkOqMEzTLmDYZ5YWWILyDb2NrSkBzArhLNhEtWrFFo9uoigwJWiyNGXUkjVd7XUaYvxVYvnHJcmr98l9sW+RxgV2Cm/6ImeW6BkSUjfrJpZlHUecxcHIaDVniSCVzVF7T+tgG0+CxpehmRrPE/qlPTY2DVHuG6ogwjmu7pWr4kW3M6pTmOYICKjkojIhPTAfNDZGNYruJMukEeB2JyxSz+J9jhjPe//9x4JznpCzm/JzCHFO9CfONjHIcUqLa9qxqhmBFpr1U5J7vRir4ch7v8TGtGbcR3833HTUA7EEMu/Ca48XVfGNDmySQs8zgGpj1yzf/lBGbiAzTSp7Zp+ANLu+R3NjeiDUYQbgf3vcpoHL44duk4dzhD+ofFD75PF1SMTluWbeLCSENH9io2pxVDj3I5VhlNxHdbqY1WXb+sDBVr4niIGzQiKqVOV33ghyRpzVJFZ7SaQG7VR/mLL3UnvJuapLYtUV9+/7Si/CHl7m8NntPMvx1nM/Z4t/BN8Z5cdhPn2PLxp9f5VCmCqLlCQDSv94cCTLlatiCTfF7axgE0u7+CWiOUNyyqg/vu0pjTwIA==
177 177 593718ff5844cad7a27ee3eb5adad89ac8550949 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAlxCG6EQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91YptD/9DG76IvubjzVsfX1UiQcV1mqWuSgz/idpeFCrc6Z1dyFB5UmbHKfAaZnrPBR7ly6bGD9+NZupB9A8QRxX92koiq0Hw2ywbwR5oWVrBaDiinIDLiTQTUCPnNMH0FSNrt4Kf9Gj4RqMufZvL+dR0pDYV0n6HP3aGOeTnowNhv0lUbw/Gx20YrcCU9uf3GbgRvMQiFNv9cTJAdQlH++98C8MVLfRU4ZxP11hI7sR8mp1q6ruJoozd0Cta67E6MyC/L2Rp3W89psvvY7DSTg9RwQwoS8I6U9iyQJ16Bb6UgZVV6jqQqOSxWUaPfKUhJLl2ENHH5f3rzoi3NH6jHuy5rq2v9XuvOpQ7LqSi1Ev0oq1xllZiyD4Zm69Z/Is0mxwqPskZGWR5Lh6Uq3Dh0zJW7O5M2m1IHdAYqffHpUr2NgEQVST4VDvO4fR2d7n6+ZNXYbZrpmQ1j4bpOZCEMqWXPfl4HY7a60hWa884mWxtVLGvhYycxnN8r1o5ouS0pAMAI6qEFFW1XFFN4eNDDWl83BkuDa32DTEthoyi15JM5jS7VPDYACdHE3IVqsTsZq7nn60uoFCGpdMcSqrD2mlUd9Z12x8NnCIrxKhlHLkq89OrQAcz8/0bbluGuzm3FHKb+8VQWr0MgkvOLTqqvOqn97oBdKqo0eyT0IPz8QeVYPbZfQ==
178 178 83377b4b4ae0e9a6b8e579f7b0a693b8cf5c3b10 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAlxUk3gQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91aT7EACaycWeal53ShxaNyTNOa5IPZ71+iyWA9xEh7hK6cDDirpItarWLRVWoWqBlWRBBs6uU4BxnpPSCLFkJLu6ts/5p4R6/0Z04Pasd6sFi14bCGslmPJFlwrpfFDpQvFR6xZAtv1xGb8n+rjpK+wfstjRgyf84zn4//0dOdylY5EUXOk4/3zcXKAzPgZHBRper+PlQ0ICgYHiKQUlyDWrFrdSEis6OqBa+PbxdmgzLYbhXi0bvS5XRWM9EVJZa+5ITEVOEGPClRcoA7SJE5DiapMYlwNnB3U6TEazJoj5yuvGhrJzj9lx7/jx9tzZ/mhdOVsSRiSCBu46B/E63fnUDqaMw8KKlFKBRuzKnqnByZD8fuD34YJ6A82hta56W4SJ4pusa/X2nAJn1QbRjESY4wN4FEaNdYiMbpgbG2uBDhmEowAyhXtiuQAPCUra5o42a+E+tAgV5uNUAal8vk0DcPRmzc4UntQiQGwxL0fsTEpMQtG5ryxWRmOIBq6aKGuLVELllPCwOh8UIGLlpAoEynlNi9qJNT6kHpSmwquiU6TG6R1dA/ckBK2H90hewtb/jwLlenGugpylLQ2U/NsDdoWRyHNrdB4eUJiWD/BBPXktZQJVja97Js+Vn44ctCkNjui/53xcBQfIYdHGLttIEq56v/yZiSviCcTUhBPRSEdoUg==
179 179 4ea21df312ec7159c5b3633096b6ecf68750b0dd 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAlyQ7VYQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91aziD/4uI/Nr+UJgOri1zfa6ObXuMVO2FeadAolKemMDE/c4ddPUN2AwysZyJaOHmqj5VR0nf4a9CpTBc8Ciq9tfaFSWN6XFIJ2s3GPHhsnyhsPbF56c2bpl2W/csxor9eDGpv9TrQOK0qgI4wGxSQVFW0uUgHtZ5Yd6JWupHuyDfWopJf3oonissKI9ykRLeZEQ3sPIP6vTWMM3pdavAmDii3qKVEaCEGWmXgnM/vfBJ/tA1U5LSXpxwkJB7Pi/6Xc6OnGHWmCpsA4L6TSRkoyho4a6tLUA1Qlqm6sMxJjXAer8dmDLpmXL7gF3JhZgkiX74i2zDZnM4i42E6EhO52l3uorF5gtsw85dY20MSoBOmn5bM7k40TCA+vriNZJgmDrTYgY3B00mNysioEuSpDkILPJIV4U9LTazsxR49h3/mH2D1Sdxu6YtCIPE8ggThmveW/dZQy6W1xLfS66pFmDvq8ND0WjDa/Fi9dmjMcQtzA9CZL8AMlSc2aLJs++KjCuN+t6tn/tLhLz1nHaSitqgsIoJmBWb00QjOilnAQq7H8gUpUqMdLyEeL2B9HfJobQx6A8Op2xohjI7qD5gLGAxh+QMmuUmf7wx1h2UuQvrNW5di7S3k3nxfhm87Gkth3j0M/aMy0P6irPOKcKns55r6eOzItC+ezQayXc4A10F+x6Ew==
180 180 4a8d9ed864754837a185a642170cde24392f9abf 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAly3aLkQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91bpXD/0Qdx3lNv6230rl369PnGM7o56BFywJtGtQ0FjBj81/Q6IKNJkAus/FXA02MevAxnKhyCMPHbiWQn4cn+Fpt9Y7FOFl3MTdoY5v4rGDAbAaJsjyK3BNqSwWD1uFaOnFDzA/112MJ6nDciVaOzeD7qakMj8zdVhvyEfFszN7f7xT1JyGc+cOWfbvcIv/IXWZNrSZC0EzcZspfwxYQwFscgDL3AHeKeYqihJ6vgWxgEg4V8ZnJ6roJeERTp2wwvIj/pKSEpgzfLQfHiEwvH9MKMaJHGx4huzWJxYX2DB83LaK7cgkKqzyQ+z8rsb27oFPMVgb1Kg78+6sRujFdkahFWYYGPT6sFBDWkRQ/J7DRnBzHH2wbBoyNkApmLEfaRGJpxX8wojPFGJkNr6GF12uF7E+djsuE8ZL7l4p2YD33NBSzcEjNTlgruRauj/7SoSC3BgDlrqCypCkNgn5nDDjvf6oJx16qGqZsglHJOl0S2LRiGaMQTpBhpDWAyVIAQBRW/vF1IRnNJaQ+dX7M9VqlVsXnfh8WD+FPKDgpiSLO8hIuvlYlcrtU9rXyWu1njKvCs744G836k4SNBoi+y6bi6XbmU0Uv0GSCLyj1BIsqglfXuac0QHlz5RNmS6LVf7z13ZIn/ePXehYoKHu+PNDmbVGGwAVoZP4HLEqonD3SVpVcQ==
181 181 07e479ef7c9639be0029f00e6a722b96dcc05fee 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAlzJ5QYQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91U0QD/4xQ00Suo+XNM/2v01NEALJA8pFxSaUcz1fBVQDwIQbApAHbjVDgIShuFlAXu7Jf582+C5wJu0J8L5Rb+Q9WJuM9sM+6cxUWclT3D3gB326LuQg86y5MYbzmwsSCOnBdRn/MY18on2XTa8t4Mxf0jAaHPUXEadmuwkOw4ds62eUD81lkakGoxgXrD1GUhAlGItNPOb0rp2XFj7i+LvazMX2mWOEXMXA5KPQrOvLsKnoESiPfONXumBfZNVSxVA7fJ3Vl1+PldBax+w9LQMgVGo+BkqPt7i+lPTcnlh2Nbf8y3zERTcItFBzrBxmuG6pINfNpZY/fi+9VL7mpMYlzlxs7VcLF8bVnpYpxpHfDR4hPjP0sq6+/nSSGUfzQXmfGHq0ZdoVGSzrDEv8UzYE9ehWUhHNE+sIU3MpwjC+WiW2YhYzPYN2KOlfSog3LuWLAcn3ZghWg1S4crsPt9CeE0vKxkNWNz9dzvhbniW7VGorXJKFCJzMu6pGaP/UjwpHxR+C6J1MGUW2TQwdIUyhPA8HfHJSVbifFJV+1CYEDcqRcFETpxm4YNrLJNL/Ns7zoWmdmEUXT1NEnK1r3Pe2Xi1o56FHGPffOWASmqFnF/coZCq6b4vmBWK/n8mI/JF1yxltfwacaY+1pEor92ztK34Lme1A+R7zyObGYNDcWiGZgA==
182 182 c3484ddbdb9621256d597ed86b90d229c59c2af9 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAlz3zjsQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91XWVEACnlQCHCF7dMrvTHwE4nA+i/I1l8UfRwR3ufXhBxjVUqxS75mHMcCsOwClAa2HaqNP97IGbk2fi9y53SOKH67imNVm8NY8yIook1C8T7nKsFmyM3l63FdVQDgUF6AJ0krDt6iJo4vjk8CyRHowAcmL942jcfBU9U5/Jli11Sx33MKF/eMXnuXYRBNESh97f1bDgwydp7QT8dj/T23YvuIVtfq9h8D46qXWkpwbgtnXMnaz21kqcN6A5aKbadG4ELf9175cBlfe+ZpOqpy+OSuQBByOP5eBNl5d0vq/i4WQyJZs8GoVd5Bh559+HjKIKv11Y+gXoaQMf4VSp2JZwwPlTR5Me5N6AJNViXW1Bm108ZWeXR81Hu2+t2eQv6EelcQxnW0e/mTCUot8TaewYFJ+4VWwAAca81FP0X8J0YcdIkvvNmrU9V62B3WYK3iYgbwm7IlR3+7ilQUz3NZCZOqJpo+c7k/yhuoj4ZMDq8JzaqBnBnARbvUF61B4iVhto4xpruUQw8FwFLUuZLohsESCNCCgqdoiyJHnVQVitoNJlCeEPl+W+UUeFfwf9fzrS6nj9xWkNm9lBOahaH+fV69msi5Ex/gy8y4H+4T8z0f3gFO7kp9eKr5C7hoGyKQWv5D61H1qEZOFUZjXHBhMxbe+og40G0apMm3qmsj2KsCNDdQ==
183 183 97ada9b8d51bef24c5cb4cdca4243f0db694ab6e 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAl0kn6UQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91RwND/9uZ3Avf0jXYzGT5t+HhlAeWeqA3wrQOmk0if7ttUholoHYmCbc7V9ufgiQ1jTX/58EhOXHt4L1zlLDf2OMJ7YQz9pfiGjW3vLvVKU7eeQ5epG8J8Hp4BcbEU5gfQBwzZmRMqVfZ9QbNgENysfQxhVT0ONPC5TBUsamAysRQVVPeEQFlW1mSf03LYF1UDjXgquHoIFnnPCZyNUGVRSajW9mDe0OQI95lXE6lISlBkeoTmVs9mR+OeLO3+Dgn2ai8d4gHxdCSU5iDnifSp4aaThfNxueSRFzNI1Q6R6MQrIplqFYZGhAOOXQzZWqThQld6/58IvaBP4aCGs1VxE/qBKNp8txm1QeL/ukOWPgVS9z7Iw5uRuET95aEn/Khisv78lrVGOD5wigt2bb4UiysIgk8+du7HNMqPmS31fCS1vsoJ+y2XoJP2q8bNDiwuVihDWJDlF091HH2+ItmopHGUGeHaxNyRoiSvE7fCBi/u3rleiMsMai8r1QDgBpalUPbaLzBelEKhn2JcDhU5NrG8a+SKRCzpmXkkFPhxrzT1dvEAnoNI0LbmekTDWilp0sZbwdsn2rO51IJ4PU8CgbYROP8Z4DuNMfVyVIpxAEb2zbnIA4YqJ3qcQ3e+qEIw8h9m/ot9YYJ/wCQjIIXN6CUHXLYO30HubNOEDVS4Gem93Gcw==
184 184 e386b5f4f8360dbb43a576dd9b1368e386fefa5b 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAl01+7cQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91ZM6D/9iWw0AyhcDFI7nEVcSlqDNABQvCnHoNB79UYrTf3GOjuUiyVUTwZ4CIOS+o2wchZXBRWx+T3aHJ1x6qTpXvA3oa9bgerNWFfmVmTuWWMlbQszXS5Lpv5u1lwCoLPDi4sa/gKBSIzt/CMu7zuPzO2yLEnWvR6ljOzjY9LfUx80u1zc899MEEsNuVStkfw9f37lAu+udMRgvQDZeLh+j3Qg5uh3GV3/8Q/I/YFNRHeKSLBkdp5CD3CkUtteBuZfIje/BwttxHG6MdbXMjOe0QmGMNzcSstnVqsENhEa0ZKLxM6NxfwcsxbeKA1uFoTvzT1sFyXXS3NV0noMQBwMrxipzKv4WrjuctmUms6n+VW/w4GMg8gzeUvu7rzqVIehWIBTxV8yWwkWiS9ge6Upiki5vCG+aeMLrwsNqsptOh4BEcsvcpd2ZZtUDRHYFVUK4z/RRlpKb6CdzkGeMWwP6oWAv4N0veD73Y7wPz76ZFNU2yvqViRPxrU2A2P44R8dLFvEOmcO5MHVNwHP0kpaj9dpGwBI0t2A32vDF8LEsnd86LQBm6X5ZWWJ5hGmtZotp4blkH1oFKt+ZeccHcwueIMU3v9e02ElhM4Mo2nD3yyQvMkzDqp5lZEfNqEK8rlj2TNfc8XyjAsp1hKpnjDa1olKKfdq8OniUpsaYDTku4+vuGw==
185 185 e91930d712e8507d1bc1b2dffd96c83edc4cbed3 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAl1DD/sQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91bvmD/4/QDZZGVe+WiMUxbT+grfFjwjX4nkg7Vt+6vQbjN68NC5XpSiCzW8uu0LRemX0KJKoOfQxqHk3YKkZZHIk10Fe6RSLWt8dqlfa2J9B2U8DwMEBykCOuxcLlDe7DGaaMXlXXRhNXebRheNPLeNe+r7beMAAjwchTIIJD5xcFnPRFR0nN7Vj7eRUdWIQ9H/s7TolPz1Mf7IWqapLjPtofiwSgtRoXfIAkuuabnE4eMVJ8rsLwcuMhxWP2zjEfEg68YkiGBAFmlnRk+3lJpiB9kVapB3cWcsWv2OBhz0D3NgGp82eWkjJCZZhZ+zHHrQ6L9zbiArzW9NVvPEAKLbl3XUhFUzFTUD+S38wsYLYL5RkzhlCI2/K1LJLOtj7r0Seen0v8X842p0cXmxTg/o1Vg3JOm04l9AwzCsnqwIqV7Ru//KPqH91MFFH6T6tbfjtLHRmjxRjMZmVt7ZQjS84opVCZwgUTZZJB2kd1goROjdowQVK6qsEonlzGjWb9zc3el5L9uzDeim3e5t2GNRVt8veQaLc+U2hHWniVsDJMvqp2Hr9IWUKp+bu/35B1nElvooS40gj2WhkfkCbbXSg9qnVLwGxxcGdF28Z0nhQcfKiJAc+8l9l19GNhdKxOi4zUXlp90opPWfT7wGQmysvTjQeFL2zX9ziuHUZZwlW1YbeMQ==
186 186 a4e32fd539ab41489a51b2aa88bda9a73b839562 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAl1xTxUQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91ZQgD/96mViQ6fEh84l4XyAlY6Dq3SgMqEXttsUpk/GPoW4ykDFKN6VoiOaPoyNODO/46V3yeAjYjy3vX7Ua4/MY1NlnNoliQcTYtRV3SlDdoueTPOLfO6YSV27LG+dX/HYvPc/htCVmIVItU1JL+KEpXnv+bT50Bk+m6OgzfJMDzdHQ5ICImT8gW7UXlH/mlNtWMOrJDk3cArGhGs/pTFVrfgRTfDfDGSA9xW0/QvsNI5iwZHgMYaqoPFDnw6d/NXWRlk77KNiXkBEOKHf6UEWecMKmiSCm8RePSiX9ezqdcBAHygOg4KUeiR2kPNl4QJtskyG4CwWxlmGlfgKx07s7rGafE+DWLEYC9Wa8qK6/LPiowm17m/UlAYxdFXaBCiN0wgEw7oNmjcx/791ez+CL1+h6pd0+iSVI4bO9/YZ8LPROYef18MFm+IFIDIOgZU4eUbpBrzBb3IM1a519xgnmWXAjtRtGWEZMuHaSoLJf2pDXvaUPX6YpJeqCBFO3q/swbiJsQsy6xRW0Dwtn7umU1PGdmMoTnskTRKy9Kgzv7lf/nsUuRbzzM4ut9m1TOo27AulObMrmQB4YvLi/LEnYaRNx18yaqOceMxb/mS0tHLgcZToy9rTV+vtC21vgwfzGia2neLLe50tnIsBPP/AdTOw9ZDMRfXMCajWM22hPxvnGcw==
187 187 181e52f2b62f4768aa0d988936c929dc7c4a41a0 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAl2UzlMQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91SDzD/0YZqtN+LK5AusJjWaTa61DRIPhJQoZD+HKg4kAzjL8zw8SxBGLxMZkGmve9QFMNzqIr5kkPk6yEKrEWYqyPtpwrv5Xh5D4d8AKfphdzwSr+BvMk4fBEvwnBhrUJtKDEiuYQdbh4+OQfQs1c3xhtinjXn30160uzFvLQY6/h4hxai2XWj4trgoNXqPHDHlQKc6kRfPpmNO2UZhG+2Xfsava2JpcP4xA2R0XkI10be5MDoGU4AFCMUcXZzIto0DYT+HOezowoNpdC1EWVHfa+bdrlzHHO7WPaTLzEPy44/IhXmNhbwFKOk5RZ/qBADQvs9BDfmIDczOoZKTC5+ESZM0PR2np5t7+JFMUeeRcINqBdSc4Aszw3iHjgNbJJ3viU72JZvGGGd9MglP590tA0proVGxQgvXDq3mtq3Se5yOLAjmRnktW5Tnt8/Z3ycuZz+QsTEMXR5uIZvgz63ibfsCGTXFYUz9h7McGgmhfKWvQw9+MH6kRbE9U8qaUumgf4zi4HNzmf8AyaMJo07DIMwWVgjlVUdWUlN/Eg61fU3wC79mV8mLVsi5/TZ986obz4csoYSYXyyez5ScRji+znSw8vUx0YhoiOQbDms/y2QZR/toyon554tHkDZsya2lhpwXs8T0IFZhERXsmz/XmT3fWnhSzyrUe6VjBMep1zn6lvQ==
188 188 59338f9561099de77c684c00f76507f11e46ebe8 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAl2ty1MQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91XBUD/wJqwW0cuMCUvuUODLIfWa7ZxNl1mV9eW3tFQEuLGry97s12KDwBe0Erdjj7DASl4/6Xpc4PYxelZwSw4xT1UQg7wd/C3daCq/cDXrAkl7ZNTAHu6iAnHh25mOpIBfhMbh4j3YD0A2OoI17QGScU6S7Uv0Gz1CY20lJmEqsMzuuDPm2zrdPnTWffRUuPgskAg3czaw45Na7nUBeaxN1On0O5WqMYZsCGyi14g5S0Z0LHMKRJzc/s48JUTDjTbbzJ6HBxrxWTW2v8gN2J6QDYykcLBB9kV6laal9jhWs9n/w0yWwHfBfJ+E4EiMXeRdZgGA55OCOuDxnmmONs1/Z0WwPo+vQlowEnjDMT0jPrPePZ5P4BDXZD3tGsmdXDHM7j+VfDyPh1FBFpcaej44t84X1OWtAnLZ3VMPLwobz9MOzz4wr9UuHq23hus0Fen+FJYOAlTx9qPAqBrCTpGl+h1DMKD62D7lF8Z1CxTlqg9PPBB7IZNCXoN7FZ4Wfhv1AarMVNNUgBx6m0r6OScCXrluuFklYDSIZrfgiwosXxsHW27RjxktrV4O+J1GT/chLBJFViTZg/gX/9UC3eLkzp1t6gC6T9SQ+lq0/I+1/rHQkxNaywLycBPOG1yb/59mibEwB9+Mu9anRYKFNHEktNoEmyw5G9UoZhD+1tHt4tkJCwA==
189 189 ca3dca416f8d5863ca6f5a4a6a6bb835dcd5feeb 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAl3BrQ4QHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91ZXjEACfBdZczf0a4bmeaaxRwxXAniSS4rVkF790g22fsvSZFvQEpmwqNtsvbTt3N1V2QSDSZyhBa+/qfpuZ689VXMlR3rcJOVjo/7193QLXHOPfRn7sDeeCxjsbtXXLbLa8UT56gtT5gUa4i0LC2kHBEi+UhV9EGgSaDTBxWUFJ9RY2sosy1XFiOUlkUoHUbqUF28J3/CxEXzULWkqTOPwh94JYsgXSSS69WNZEfsuEBSPCzn8Gd7z7lWudZ/VTZBTpTji7HQxpFtSZxNzpwmcmVOH9HlEKoA1K4JoR+1TMHqSytQXlz3FMF6c6Z1G+OPpwTGCjGTkB9ZAusP3gU8KIZTTEXthiEluRtnRq1yu4K2LTyY172JPJvANAWpVEvBvn4k5c9tDOEt9RCAPqCrgNGzDTrw02+gZyyNkjcS6hPn+cDJ6OQ1j2eCQtHlqfHLSc7FsRjUSTiKSEUTdWvHbNfOYe6Yth/tnQ7TnpnS9S0eiugFzZs2f8P85Gfa3uTFQIDm67Ud+8Yu1uOxa6bhECLaXEACnLofzz8sioLsJMiOoG2HmwhyPyfZUHXlb2zdsSP3LC+gKN39VvzSxhhjrIUJoM4ulP0GP1/lkMVzOady66iLaEwDvEn4FLmu395SubHwbre1Jx83hiCQpZfPkI0PhKnh4yVm+BRGUpX97rMTGjzw==
190 190 a50fecefa691c9b72a99e49aa6fe9dd13943c2bf 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAl3pEYIQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91duiD/9fwJbyrXXdpoBCeW3pgiz/xKZRQq0N3UqC/5m3PGl2qPfDqTi1GA6J+O24Cpy/FXYLEKlrEG2jy/iBZnGgTpb2sgycHFlWCT7VbuS8SDE3FFloTE8ZOGy5eJRo1UXYu4vsvNtmarN1xJQPrVK4l/Co5XWXFx15H/oMXLaHzS0kzQ/rHsMr7UXM0QwtmLC0S9IMetg5EUQx9GtHHaRnh1PIyP5NxP9VQ9RK4hmT6F2g60bcsMfpgF0I/RgL3tcdUn1RNIZ2OXHBhKYL+xOUe+wadDPIyPDqLXNEqPH7xqi0MQm/jOG++AvUPM7AdVc9Y2eRFOIIBIY0nkU5LL4yVVdqoc8kgwz14xhJXGTpMDRD54F6WrQtxhbHcb+JF7QDe3i9wI1LvurW4IIA5e4DC1q9yKKxNx9cDUOMF5q9ehiW9V120LTXJnYOUwfB7D4bIhe2mpOw8yYABU3gZ0Q6iVBTH+9rZYZ9TETX6vkf/DnJXteo39OhKrZ1Z4Gj6MSAjPJLARnYGnRMgvsyHSbV0TsGA4tdEaBs3dZmUV7maxLbs70sO6r9WwUY37TcYYHGdRplD9AreDLcxvjXA73Iluoy9WBGxRWF8wftQjaE9XR4KkDFrAoqqYZwN2AwHiTjVD1lQx+xvxZeEQ3ZBDprH3Uy6TwqUo5jbvHgR2+HqaZlTg==
191 191 b4c82b70418022e67cc0e69b1aa3c3aa43aa1d29 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAl4TkWgQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91aV6D/4xzlluOwsBhLXWUi7bDp4HtYnyDhq4XuDORAMO5mCZ7I7J6uqGoViqH4AhXoo3yPp1cDiRzzl172xpec38uTL8C5zHhARKuAl5Pn1A8rYORvYzT9nsDh4MAtfTokhg81awRzhun9xtPUT2nETAOgampW0g7r241MSR1j0myAkC7zqO3yf+1rYo7kiv7fh+74MkrSn4HEmEaLsI5gW05tFR+ip6vpm6eikFinqeVJegDCuyTPMvH0D9ZeBNlyoOfdEd6DDYsWvWAmLSO9FGbb03R5aOFRp7RmQRFH/qcueeePa/9Z1zO+YyCeBy0wvWCkjfLMY99HhNhdNfy/qC/69V5RGQYvaapy6BEAi4eCH73hsxzCQpKopUl9VrpwhNasJ41KWc90RsPO91bkTdDddF7e2qjq762aNgm7ysEzIHMgSsMgsE9w8hz70RE7bk/gYn26ak3XP4nCOY0OJQ8mgaElN/FP1kxqqT7MM7WeMiNMFTD1gvWwEAu9Y47AwUedkTrykQsAFzc+CyaIaW+/Kuyv0j5E7v8zAcVTTX4xIyqR4yL2Nwe1rYE4MZgs0L9gQ3rcdyft6899gAiiq96MPR3gLJUPbBz2azH/e0CzNXvDJa39jIm2ez0qC7c88NhTKhFjHE9EW5GI3g8mhS5dJXCnUSq4spgtrJdfGenL3vLw==
192 192 84a0102c05c7852c8215ef6cf21d809927586b69 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAl4nP/4QHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91VaHD/93dVKKFMJtclNMIG2AK3yZjfQ3HaqIuK1CqOuZyVQmk5fbnLydbi5RjIQMkaYPSKjDz0OKlfzDYo6kQrZrZUzIxzPBOz8/NMRSHGAWqvzQMbQGjYILsqDQ+wbol9wk8IDoyFzIcB4gPED1U5kWVCBTEqRrYiGP4siiycXVO5334Q5zOrvcjze0ksufbKQhL6SEUovfLtpX+DW6Z841LmR53aquEH8iBGswHKRt4ukyvmXTQAgea4lWXZXj3DH6oZqe0yzg5ogF4vFaoIgZDpBh2LZKuh6gwJtvA9jsFj5HVOzYDcllkgpaOTV1g/xKPo1EkLpt0W0vd/4vnjSKNo0fmOTvZzI9vCCXLlRSUhoboY6AFHN7XtL9gYWI0rj81p/WrnnQQ7Iv2YHS1KCLr765HW6mjREwFMLD9RrLLDQ0DWIyNuGq8/yrqoruAhidEE9ifITnNh38wVISdiPxORj3onZkAn7VbOWQnlJtYkynlk2t3HnHWfduLGc2G0BkLvg4YfEDsZBA+ssr+TspkZ1dVAq8kf4JKNR01sfjBF6Fj1zRPkoexV40/pPiW55ikfOI9LRHxRiOUyndLviIBv1Mbm90PZ89lT4OTMejD8hhb4omlVxH3HFv4j7TozuPFOuouH7ARRwbPFl/0ldPlESoGvFiyOrqNzlql+JvyLUSbg==
193 193 e4344e463c0c888a2f437b78b5982ecdf3f6650a 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAl4rFTIQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91eStD/wNSk7/07dvzItYmxg9LuUInYH17pZrXm8+jGEejoYZw74R1BHusFBcnmB1URldbq4IdzlxXNKrcnmJH/lgYCdbZ8OG0MaQrEIyLz0WmY27ARb/AwDuiy/dn0X3NgvQjqPffLHrYHmdqvqBsb0+qG3v7b0xt+BGDkebt1TXCy9wjIa1iqCOQ0EJi2dcuD2dWlhPM2kuslMjKlqe57D5bwaHBDS6K9Sd4VABRdv7mExrMBSr1SnkasrBsvb47UVXYUJRI3GGyA/wYYAi3fW9ZxG25x2SA0rjF5U68c5rmQMD94FLmaSoaqSvigkSBDOF/DIwlRO5vB4NlP7/+TjNOo92r4GbTZyMTnrsORqQJKcMrpfVbM8gRngPTJz2FxBSoz86HQ3wVXnS0gVUJNM+ctWdvzvtrv1Np3wF0/zWHddrtfYdNgnuyKjQL3chpJs7y5aQxdgU1vHdf4X2NwhA77Cf/U6bSemhR+MfZlp4it7pZiu96b8jKsEbKrCi998tKCKVv70WhGXce3gebKPY3Gn/qUL6X3rx4Uj5CPrIjWZNhwRJJ3BXSTnKog2eUIWJC0rXXrGRV6Sf6514zbi0MCOexnAjZM1xs5NUd/wrugDnMp4+P+ZPZyseeVB51NSnGhxlYLwD9EN+4ocjyBzMINOcQw1GPkB5Rrqwh+19q5SnvA==
194 194 7f5410dfc8a64bb587d19637deb95d378fd1eb5c 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAl44RUUQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91WcUD/9em14ckTP9APTrSpe6y4FLS6cIUZabNN6wDXjTrHmS26hoNvWrT+RpWQ5XSOOJhZdhjkR1k87EOw9+m6+36ZaL+RXYnjrbku9fxbbFBraGTFy0JZHAT6v57uQ8P7XwqN4dGvXXpgE5UuY5sp1uDRbtIPNts3iWJKAnIazxUnyotHNtJQNESHySomzR1s93z1oOMpHapAqUmPbcZywg4otWjrOnkhOok3Sa3TgGthpHbM0qmh6J9ZaRBXsKEpLkjCRNggdvqww1w4omcAJzY4V5tG8WfhW+Xl8zBBe0K5m/ug3e25sWR5Dqm4+qUO0HZWQ3m3/M7CCuQrWFXTkr7nKac50vtFzsqHlHNoaiKnvQKoruQs3266TGsrzCCOSy8BqmpysD6sB79owLKoh0LfFOcSwG9kZ8sovEvTfrRn8g3YAp7XbXkDxbcLMijr7P4gWq8sC1NZJn1yhLXitcCfAAuVrVQfPVdt2pp8Ry2NdGnHjikQjOn/wAKlYJ5F8JMdn6eEI/Gveg2g8uR9kp/9zaXRx6rU3ccuZQ7cBQbBlBsmmpd7gJRp2v0NKsV8hXtCPnBvcfCqgYHLg7FQVq1wKe5glvtmx9uPZNsl/S++fSxGoXfp9wVi048J42KyEH6yvoySCvbYeSFQvMfAoD1xJ4xWtT8ZEj6oiHvzHw1u/zgw==
195 195 6d121acbb82e65fe4dd3c2318a1b61981b958492 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAl5f3IEQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91WoeD/9qhywGg/TI/FJEeJN5bJjcpB/YQeYDWCHh69yUmMPenf+6CaV/3QPc3R8JyQSKWwGUwc0IgZiJBb/HoUvBzpQyTvmGqddWsIGBpdGAkbLmRrE5BakR7Shs987a3Oq4hB03DJD4sQ1VitWg2OvGNd8rl1kSIF8aIErVI6ZiSw5eYemc/1VyBJXHWSFmcfnQqdsyPppH9e9/TAhio+YP4EmLmoxUcyRSb3UbtO2NT9+DEADaex+H2l9evg7AkTieVd6N163uqsLJIxSfCh5ZVmzaGW6uEoyC4U+9bkAyVE3Cy5z2giYblBzUkO9xqEZoA4tOM+b+gHokY8Sq3iGVw046CIW5+FjU9B5+7hCqWThYjnpnt+RomtHxrkqQ9SSHYnEWb4YTHqs+J7lWbm3ErjF08hYOyMA9/VT47UAKw4XL4Ss/1Pr7YezdmwB4jn7dqvslNvTqRAUOzB/15YeCfbd23SL4YzGaKBs9ajkxFFeCNNpLQ8CRm3a7/K6qkYyfSUpgUX7xBmRQTvUgr3nVk1epH/kOKwryy94Z+nlHF0qEMEq+1QOa5yvt3Kkr4H03pOFbLhdpjID5IYP4rRQTKB9yOS3XWBCE63AQVc7uuaBGPMCSLaKRAFDUXWY7GzCqda88WeN5BFC5iHrQTYE1IQ5YaWu38QMsJt2HHVc27+BuLA==
196 196 8fca7e8449a847e3cf1054f2c07b51237699fad3 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAl6GDVQQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91egzEACNEyQwLWCQEeNyxXKuTsnXhYU/au7nSGOti/9+zg/22SSceMsVcIyNr2ZnkMf3hnzBjL7Efsthif0QXyfB0LZDXwNuDmNlDtUV2veyVGSDE2UqiSbDBRu6MYTvtfYX87RmSWla3HHO09pwpcrhxyHs3mliQsXyB2+D+ovTOIjYukQLnh34jQnwiWEYLDXkHEHHTpdXqAnA7tVen3ardLyTWgky6DUwlfcnoVsAPXnDkqQ9aE2w7SoAsNtEAddmkjKoYYdBkV5aUInU/DyFVF7qnlCcvWm+EkN1708xZUQ1KzdAyeeoIrMkBgpSoyeNQ9pcU3T7B100UxLo/FP/A7y96b2kHnKJU6fVyD3OeHvP9SeucurC6jn2YoG3e1wSOQcbEuCsdGjqgAHnKt2SMPsEBu2qJJcUdco9tANN5BdntBo7bLc/zcpXZH3TkRfRSndWXPaXDJaQNvbH7aLIUTCP9oQaqTN+9BQ+Egt7YsB4C58JZmC87FAuekDULc4LWK2gDPFf7F/PvBnMh7+YylPl/8LLrEnz2Q/GM0S1HLhBrDf6vzxV5wVzCu9Q2N0PCkg6lDAJFVWLTEbxcRukKxbyK88Yzrb4GuUY4F5V21fN4vuxkOay7eoiXUcHMN2IN+DwhNWQSm5pUnpqGTfCYj/ZBbAykP2UnVOClL6O2JQA2A==
197 197 26ce8e7515036d3431a03aaeb7bc72dd96cb1112 0 iQJJBAABCgAzFiEE64UTlbQiPuL3ugso2lR0C/CHMroFAl6YlRUVHDc4OTVwdWxraXRAZ21haWwuY29tAAoJENpUdAvwhzK6Z3YP/iOqphn99v0z2OupCl0q8CepbcdZMJWW3j00OAHYSO43M0FULpMpzC2o+kZDeqeLyzN7DsjoGts2cUnAOe9WX73sPkX1n1dbiDcUSsRqNND+tCkEZMtTn4DaGNIq1zSkkm8Q7O/1uwZPnX6FaIRMBs9qGbdfmMPNEvzny2tgrKc3ra1+AA8RCdtsbpqhjy+xf+EKVB/SMsQVVSJEgPkUkW6PwpaspdrxQKgZrb7C7Jx/gRVzMTUmCQe1sVCSnZNO3I/woAqDY2UNg7/hBubeRh/EjoH1o4ONTXgBQdYCl7QdcwDHpDc2HstonrFq51qxBecHDVw+ZKQds63Ixtxuab3SK0o/SWabZ1v8bGaWnyWnRWXL/1qkyFWly+fjEGGlv1kHl3n0UmwlUY8FQJCYDZgR0FqQGXAF3vMJOEp82ysk6jWN/7NRzcnoUC7HpNo1jPMiPRjskgVf3bhErfUQnhlF1YsVu/jPTixyfftbiaZmwILMkaPF8Kg3Cyf63p2cdcnTHdbP1U6ncR+BucthlbFei4WL0J2iERb8TBeCxOyCHlEUq8kampjbmPXN7VxnK4oX3xeBTf8mMbvrD5Fv3svRD+SkCCKu/MwQvB1VT6q425TSKHbCWeNqGjVLvetpx+skVH7eaXLEQ3wlCfo/0OQTRimx2O73EnOF5r8Q2POm
198 198 cf3e07d7648a4371ce584d15dd692e7a6845792f 0 iQJJBAABCgAzFiEE64UTlbQiPuL3ugso2lR0C/CHMroFAl6sS5sVHDc4OTVwdWxraXRAZ21haWwuY29tAAoJENpUdAvwhzK6FQcP/1usy9WxajBppBZ54ep+qesxufLoux5qkRU7j4XZ0Id4/IcKQZeik0C/0mFMjc+dYhQDGpDiuXCADKMv5h2DCIoaWUC0GueVtVkPhhMW3zMg/BmepV7dhUuipfQ4fck8gYuaBOclunLX1MFd+CS/6BQ6XIrsKasnx9WrbO2JpieBXv+8I5mslChaZf2AxeIvUVb2BkKqsCD0rqbIjTjtfHWJpaH6spFa7XX/BZWeEYz2Nc6LVJNZY0AmvJh8ebpoGOx85dokRIEAzTmBh04SbkChi+350ki6MvG3Ax+3yrUZVc1PJtBDreL7dMs7Y3ENafSMhKnBrRaPVMyUHEm2Ygn4cmJ1YiGw4OWha1n7dtRW/uI96lXKDt8iLAQ4WBRojPhYNl4L3b6/6voCgpZUOpd7PgTRc3/00siCmYIOQzAO0HkDsALoNpk8LcCxpPFYTr8dF3bSsAT9fuaLNV6tI2ofbRLXh0gFXYdaWu10eVRrSMUMiH7n3H6EpzLa4sNdyFrK0vU4aSTlBERcjj2rj86dY0XQQL181V7Yhg8m8nyj+BzraRh7et2UXNsVosOnbTa1XX0qFVu+qAVp2BeqC4k31jm0MJk+1pDzkuAPs07z3ITwkDmTHjzxm5qoZyZ1/n37BB6miD+8xJYNH7vBX/yrDW790HbloasQOcXcerNR
199 199 065704cbdbdbb05dcd6bb814eb9bbdd982211b28 0 iQJJBAABCgAzFiEE64UTlbQiPuL3ugso2lR0C/CHMroFAl7amzkVHDc4OTVwdWxraXRAZ21haWwuY29tAAoJENpUdAvwhzK6AKEP/26Hoe8VqkuGwU0ZDsK6YgErXEPs8xtgZ9A2iouDkIqw2dm1TDmWnB5X8XaWmhAWFMUdjcqd1ZZJrAyD0p13xUOm3D+hlDXYTd2INkLwS8cVu22czZ5eoxtPkjuGYlPvek9b3vrrejkZ4vpamdS3iSvIx+TzvEW+w5eZFh9s1a9gR77hcZZoir24vtM9MsNnnBuI/5/fdWkhBoe17HSU4II56ckNXDrGO0nuqrWDxPr64WAcz6EmlTGc+cUqOM45Uc0sCr3GNQGEm6VCAw5oXq2Vt9O6sjgExLxr8zdud6w5hl9b8h2MrxyisgcnVR7efbumaRuNb8QZZPzk5QqlRxbaEcStyIXzAdar4fArQUY2vrmv1WyLJR3S/G3p8QkyWYL3CZNKjCAVxSa5ytS5Dr/bM2sWaEnIHqq+W6DOagpWV4uRRnwaId9tB9b0KBoFElXZRlaq0FlNYG8RLg65ZlkF+lj6RACO23epxapadcJwibDQiNYX20mcSEFDkSEgECnLQBecA2WZvw134RRbL3vuvB49SKS0ZEJ95myXMZa9kyIJY/g+oAFBuyZeK9O8DwGii0zFDOi6VWDTZzc3/15RRS6ehqQyYrLQntYtVGwHpxnUrp2kBjk3hDIvaYOcFbTnhTGcQCzckFnIZN2oxr5YZOI+Fpfak6RQTVhnHh0/
200 200 0ea9c86fac8974cd74dc12ea681c8986eb6da6c4 0 iQJJBAABCgAzFiEE64UTlbQiPuL3ugso2lR0C/CHMroFAl78z0gVHDc4OTVwdWxraXRAZ21haWwuY29tAAoJENpUdAvwhzK6IrkP/2m/DJ93BR/SljCFe7KnExrDTzDI/i69x+ljomRZJmMRa86zRkclgd5L49woExDd1ZGebUY650V16adKNmVpz2rS6bQOgEr2NBD5fL+GiTX6UJ1VMgmQ8x1m8DYuI8pfBWbqQuZIl1vCEc0RmT3tHLZ7T8XgG9RXa4XielI2uhyimJPyZsE1K7c8Fa6UakH++DhYFBj+3QYbwS2fFDdA29L/4N5JLUzHkIbF7tPg7P1RBk+vhopKz9MMIu4S95LU+Gk7eQ3FfE8Jnv959hX2o/B2sdT2tEPIuDRSxZhSKLdlGbMy5IZvc/bZ+a5jlb2w23tlpfgzQxNarFqpX/weiJCtsxzeMXQHEVFG/+VuIOIYbfILWzySFcnSvcAtmNXExxH2F9j+XmQkLysnsgIfplNVEEIgZDBPGAkAQ+lH7UrEdw31ciSrCDsjXDaPQWcmk4zkfrXlwN7R9zJguJ+OuZ/Ga7NXWdZAC+YkPSKAfCesdUefcesyiresO8GEk9DyRNQsX/gl5BjEeuqYyUsve5541IMqscvdosg6HrU/RrmeR7sM7tZrDwCWdOWu/GdFatQ+k6zArSrMTKUBztzV93MIwUHDrnd+7OOYDfAuqGy7oM2KoW0Jp8sS2hotIJZ9a+VGwQcxCJ93I5sVT6ePBdmBoIAFW+rbncnD+E/RvVpl
201 201 28163c5de797e5416f9b588940f4608269b4d50a 0 iQJJBAABCgAzFiEE64UTlbQiPuL3ugso2lR0C/CHMroFAl8VylYVHDc4OTVwdWxraXRAZ21haWwuY29tAAoJENpUdAvwhzK6zUEQAJoLrpMmHvM4VYepsu2UTFI2VA1iL7cd+AOlcAokn/29JOqmAWD2ujUMv2FIdcNqAW/ayeEW9oLAi0dOfLqS6UAxfw8hYEiM6hV1R0W9DOUV5CRQ5T86cbaZFBrrJL9N87tHjro0eS3i8iwPpklnWrwf8fkcBq8SKFBZbubat8X/mejbbq6zYML9SEhtrKHyBPL5iQjzqDEGWyTqJYusHGVkAtFMZWxStDA3VSr3x9Iy0495XdegYRkUFytRsz1zB3vfawJsWRY7tQfff5CF6knZ+UIpetjgJIlm21/vQmcL1aTIxem0CFQt5bub1a+LYI1TWt59rFrnRj97K6Kq6xG6lPjnM3l/w2nehGfpL/Tfjih9gY8ToS1GRg2JJ4IiXAI57fv5fZcZv3R0xAGfWfRdwMsO2siaDrd4R/kraDlTPZZ1Qmpa+Y4XtFxSGIXtf9DWt/7pw81GWrUH0u/WYjfSpYvbdr7GvYpdzxMmtEULoxJ9ibyFDyDyqEkJfT6onFb1aaHQJ1mjho1x93uDeAEq0R5UCSNDxi31Hq/nWtA9IwCjYeQkv9D1rxFcSx3MetUpJofdBYvvFsvjNTM5GO2ETvsjyzXf2Qa3oobQoKBqbTuKR6yJlCsmWJuejbDbblBdx3mj4xpXxmX/YQHQ+2PYrfopel/8Am8j7sq0sNcV
202 202 7fc3c5fbc65f6fe85d70ea63923b8767dda4f2e0 0 iQJJBAABCgAzFiEE64UTlbQiPuL3ugso2lR0C/CHMroFAl8oTNkVHDc4OTVwdWxraXRAZ21haWwuY29tAAoJENpUdAvwhzK6YLIP/0ZRwrBhBrMsy4UDS6dBwJ2WS5MRFIGTx44TW5Km/QGahz8kU+IEnKcV3Q9K7qu6Navt4uFvwFxJxDebcl4TJMfLqXH8gp8cma3GHLcHEgdms+lWe7osVVfDsynnSpZbwzUgeHoiJz805BAPrpesfq8GUDzeONJJcVtbAanSg+E0tnFNUE3592Oz8VjvgBAlPMdaRiPiTs2FrEN6+h1zxgHRSY8q4ZC88y1x5dst2yjCef9SUQ5MW1OCMuy+ki3QSwxRZfa28Z+17sJ6Lfy2ZqE2J7dZquGXllF6wPYGHmUZ1NKu4gY9aIghJBUzk6gZgvoqlJ44jFSlw4+Q8k9UW8GgLrMOkKCGstTztHDXdqCU4FMpUP+SaMq/XN4XRiyw5FiYyhBaCF3K3QwGqYNP4jadZqYAe1/UnjLWoPN5ZiXZQW7yD5MwOtrZOJFmm4PuFaAAPy4cdSvHpVA8HVQWyLhE0BSA7r8spPVptP3w9GG+qEGR3pvs0mVjMOVI/nWNuD40PILtGqqhbBIUawKqxtfdA1Pf1qcxWTC2Uxgtw0YuMHztPWihW0xfDxxdZ13ewQ4ETdWj598CyaUs3nVRX4ru33pmWBfhLSlXRsNhqc7N7XJ0xE8eHIUs7F3WCwBjMMemV6K3HN0xT4b+7uDdw2RuUA2HGtKLzNAGN9gyMd6/
203 203 f62bb5d07848ca598aa860a517394130b61bf2ee 0 iQJJBAABCgAzFiEE64UTlbQiPuL3ugso2lR0C/CHMroFAl9OKQ8VHDc4OTVwdWxraXRAZ21haWwuY29tAAoJENpUdAvwhzK6fZ8QAJrThdhW9z05KenVuMDofakaCK0MGjSu4Tjg0D5vcVSOi8MGUU1XLky7T8HGhCZvGS2WWsqWenfj+BigXz1Ri4Iw5/j9WE2e7K1tu4if3ZTWrrcwtGgVL5ABnqJ7i9N3SxAIZ8+ws+UkZ4qdd33YsdJesY00Hzk2QJcPCI8VMINeDedh+EQZAcYYD0T5oWYBttHn+xzk7GROL3LJLoZK6YiPigd0ZpWnJJvZtjH8S9SenVNsa0FFGvjbe4tYQz1AcJxc9J7onBkzSPDONdeONWItyaLUF/luvtgfY84OigHpnR1W+h11HfwtPlXMNP21kV2vyN8aLR1Zplx2QNZXykwm2zpD/3MZROb+OjTq/FmKACdgtylCL7vm0fQwcGoydKryuFw08b0EKSS4YQ6qIakh8d1Cz5WKMlvzd/TudoW+MNOChFreN9db2mYSxjHrtqeDp7I8uV1JdtC+UXPtBNXIOddg1/C2V2X7palfscrLbIFAVGsUf6x4AeGjatuxUUxrp0flEjH4IvRIuhwv1QSdLTJQCq3zMoosPgRskETlgqrjZawxWspGNbXOX45YWb+vEib17c11OE0C5vQFtA6q6MDO/g/g95eVGijIxUiLM45Nh7O+e7ugHiFwWQiD5KlVz1w5QRsCfIdYPOXXUEMyVDE94WduEHB+2D1FZ8hi
204 204 07731064ac41dacdf0ec869ebd05c2e848c14fbf 0 iQJJBAABCgAzFiEE64UTlbQiPuL3ugso2lR0C/CHMroFAl93L8cVHDc4OTVwdWxraXRAZ21haWwuY29tAAoJENpUdAvwhzK6xZIP/R34y1j74tumvkIQhijDuMEar3mEOcA0Bjy2iLMjEJtIwQ7OqRbQRY4bn5c88+uQtP2W2KH7OY8tusy+zplkclP2YZUMfUfeClz0G9Ud+94+hs41TX60Htm2dM3UbDo6aCO/j8Ado0U8W7m6LDd1UR/4UfcM5q2YZAq4n6a4twJuDqlv6xx9nFRK8AbeKihIGzv+J46YrqWi9unmLc0kTb6qWT/7H2FeMeBNN+XfGZ+ry/zEyTdhyURTaWEvt6h4EnroPFRmb779aK7dFNDZvc30bh5CnBfGflvvl5sQLDOU7Dqjmhie+PdVK0XNr1PGxNbI2Y9RSKyKXKHRI4jgxHfsB1957cVD++rzSBs4nAockPlAqupK8wL/RWZ0ilB+un1zPizk67cwApnQcWIRro+6D4OuqhA98DAHLu9R7vsjArxCcmgHXdjMiOpLs2K5dqYG15bgeJ+csVDzgFs8vtiaXWYbDdHrhMMAx0V+tLb9Yh6CashwPmi8+7mroJgqtZTLPg4cRwj0TiuHXzLUQrAzjf2o48KiUCEx6pz7PdQtaePO/l2qJCBWuXhY7pSNLy3kHv1gFN+hqKHLdJVNMoF0aR0O4u87ry7SD1dvz90BshH9kHy8FR3q77ITNVNFghWzNp4faTdqiNMMtx4fw+j28G5yQS3hmCkApmti9zJi
205 205 0e06a7ab9e0d5c65af4e511aee1e0342998799df 0 iQJJBAABCgAzFiEE64UTlbQiPuL3ugso2lR0C/CHMroFAl+PEggVHDc4OTVwdWxraXRAZ21haWwuY29tAAoJENpUdAvwhzK6KGoP/3rNBknIuLpJ/+nWiTQNY3GsJwl1Z0QX97cpXevNYQDjNGFpOJveJwEKq5ouAfD+bLILuEjdgdMaB/87b1fuf4stsH3myG6PlvgXeP9cpEMGejh4UvLBO74l5qALYI5J5f7/M8tPN1VGSC0cAcSvRilh+zl8KXakCjz/zoVpdDwE9YsbdZHhYMe2aiGJw0tueao22kP7txuqmy6coHVHIHhxLhvZ/HGSjoUD+oCcBVw9dIReariUFWw+56MAhAf99JhiQ/In+w1qKcoLF64Y7m45Tl7MPsweCpVQ0wtoprOMFziYhmwZcPPTa4WnNbE2MbnJcKyCKF3t3dJqqEplp64KYjskckZlK6lbhLrAi/nGU6HNRCRjIyzcA4qPhaEYb8DnebBPCpuKMaZMyJCZd+N7ydDAujGa+q2U5O1t1nLBRMou7eXD86L3aH2mukbUkkGmZXUP6M1C4ErEPZU78QoqUr+A+74+y+2lgWdkXYv5QmApitGMIel1sh80XYcdZmNAeXzB3QL3KnYp+mDapSe6oKAcArHWzbrCm4zWng6B6JKV+rHfbb9dxdJ3cSJwY+tTZQHwHZkQFVxiJsw2ID5jZsFwKkfXhqLW3FY+u20WQriVF5EDahdy5VvhNbsEVTY42m7OAUK7FjVqyX+gvtNx/mhyoPOv+6P+oPMj1HWa
206 206 18c17d63fdabd009e70bf994e5efb7db422f4f7f 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAl+gXVsQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91SAmEADN4fJHjY+Gxu4voL7BHCW3iar3jqyziY+q681nGBK6Tr3APslQkENFahAyHPawkuyiznfWVzzQh/aSbvqDDYCUe+ROjsjSGOwmyd45CN4X01RF1gavuCD5iAn5nw/PML4owtHkM4MhSI0V3++GgczFiDrG09EfGt4XxPWJT5XZaeR4uLB+FJL1DjuJQx8KTZDdlPsLzUCh41l76wrYRqP47KNtm50co4MJOx7r6BQn8ZmfNxG+TBnNRasES1mWv8OtYTleHZPHjvxKXmXNwuCPg1u33vKGIM/00yBm9/KHnfPUnLDxVXIo7yycLtU7KVXLeY/cOG3+w3tAY58EBozr8MA8zIAY773MqFq+I5TRKTQAxzpTtWm6FeW6jw1VAN4oImaWKWuKqIs7FbTwtw6158Mr5xbm7Rd7al8o9h8l9Y0kYyTWdzNnGCRGsZJ9VRnK7+EJ7O7PxicY1tNzcqidP/CvS7zA6oCeOGhu5C79K0Ww0NkcHcIeMznM1NK+OihEcqG5vLzuxqRXB93xrOay+zXBk/DIr0AdRbXUJQ8jJR9FjVZMHFTH2azAvBURsGwmJcJWIP5EKg2xNl9L1XH2BjwArS7U7Z+MiuetKZZfSw9MT2EVFCTNFmC3RPmFe/BLt1Pqax1nXN/U2NVVr0hqoyolfdBEFJyPOEsz4OhmIQ==
207 207 1d5189a57405ceca5aa244052c9f948977f4699b 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAl/JMCcQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91d8VEADPmycxSrG/9WClJrXrZXVugf2Bp6SiKWarCWmZQ32sh/Xkl6Km8I6uVQL0k82lQO71jOin6APY2HJeOC57mBeX9HOPcN/l+I8g4HecdI6UO8+tQzPqzno92Nm+tj0XxSelmMZ1KwDYpiHBo8F9VMILTZSdFdC5zBBMQOHhJDAtIUJx5W8n2/mcDvFEpv5OHqS2kYzHHqn9/V+J6iOweP2ftd3N84EZZHb7e8hYbLHS1aNJRe7SsruCYJujHr8Ym5izl5YTpwvVCvudbK/OnrFd0MqT3oRS8WRPwwYcYJkj5AtDLA0VLbx47KeR0vLCC7hTkFoOtFtxc7WIJOZVb/DPi38UsSJLG2tFuSvnW8b1YBCUD5o39F/4FxUuug/JxEG3nvP0Hf6PbPiAn/ZPJqNOyyY51YfjAaAGZeP+UNM4OgOdsSq1gAcCQEMclb54YuRe/J/fuBkQVKbaPuVYPCypqdc/KppS9hZzD3R3OEiztNXqn8u2tl33qsvdEJBlZq9NCD/wJMIzKC/6I5YNkYtgdfAH+xhqHgPvohGyc5q7jS8UvfIl6Wro8e+nWEXkOv2yQSU8nq/5hcyQj5SctznUxArpAt7CbNmGze42t29EdrP4P5w2K6t1lELUw1SVjzt/j9Xc5k/sDj4MxqP8KNRgoDSPRtv7+1/ECC4SfwVj5w==
208 208 9da65e3cf3706ff41e08b311381c588440c27baf 0 iQJJBAABCgAzFiEEgY2HzRrBgMOUyG5jOjPeRg2ew58FAmAHEb4VHDc4OTVwdWxraXRAZ21haWwuY29tAAoJEDoz3kYNnsOfMJ0P/0A0L7tLfx03TWyz7VLPs9t3ojqGjFCaZAGPyS0Wtkpw0fhllYzf4WjFyGGsM1Re8fY7iakSoU3hzHID9svxH1CZ2qneaWHyXc166gFEhvOUmySQMRN26HnRG2Spc+gc/SMLUcAavzMiHukffD+IF0sDwQyTxwei40dc2T2whlqlIJ5r3VvV9KJVWotupKyH4XcWC5qr5tQvoc4jUnP+oyRtmv9sr9yqoC0nI6SALK61USfe6wl/g1vDDmwz3mE75LsVAJjPYVQzceMSAKqSnS2eB1xSdrs8AGB+VbG7aBAAlYo2kiQGYWnriXNJK5b6fwqbiyhMsyxShg/uFUnWeO52/0/tt7/2sHhXs7+IBM8nW/DSr1QbHaJ+p874zmJGsNT3FC370YioSuaqwTBFMvh37qi95bwqxGUYCoTr6nahfiXdUO3PC3OHCH/gXFmisKx2Lq7X1DIZZRqbKr0gPdksLJqk1zRrB++KGq5KEUsLFdQq4BePxleQy9thGzujBp1kqb9s/9eWlNfDVTVtL1n8jujoK66EwgknN9m66xMuLGRmCclMZ9NwVmfP9jumD0jz+YYrIZC2EoRGyftmNhlZahwDwgtQ70FSxNr/r+bSgMcUPdplkwh6c+UZGJpFyaKvJQfHcm6wuShKbrccSai4e6BU43J/yvbAVH0+1wus
209 209 0e2e7300f4302b02412b0b734717697049494c4c 0 iQJJBAABCgAzFiEEgY2HzRrBgMOUyG5jOjPeRg2ew58FAmAZlogVHDc4OTVwdWxraXRAZ21haWwuY29tAAoJEDoz3kYNnsOfalsQAJjgyWsRM1Dty8MYagJiC3lDqqeUkIkdMB569d0NKaiarwL/vxPS7nx+ELNw0stWKDhgTjZlgUvkjqZEZgR4C4mdAbZYO1gWVc03eOeHMJB46oEIXv27pZYkQZ1SwDfVDfoCKExGExRw/cfoALXX6PvB7B0Az35ZcStCIgHn0ltTeJDge1XUCs8+10x2pjYBZssQ8ZVRhP3WeVZovX5CglrHW+9Uo09dJIIW7lmIgK2LLT0nsgeRTfb0YX7BiDATVAJgUQxf6MD2Sxt/oaWejL3zICKV5Cs+MaNElhpCD1YoVOe2DpASk60IHPZCmaOyCZCyBL9Yn2xxO9oDTVXJidwyKcvjCOaz4X6c5jdkgm0TaKlqfbY8LiUsQet0zzbQT7g+8jHv31wkjnxOMkbvHZZGoQLZTjS9M5NeWkvW8FzO9QLpp/sFJRCsNzjEzJWZCiAPKv51/4j7tNWOZLsKbYmjjQn9MoYZOrsFz4zjHYxz7Wi46JHMNzsHwi5iVreKXp1UGTQYhRZnKKb7g6zS3w3nI1KrGPfEnMf/EqRycLJV9HEoQTGo4T36DBFO7Wvyp6xwsnPGBki78ib5kUWwwSJiBsyx956nblY4wZaC8TiCueVqu0OfHpR4TGNuIkzS7ODNNRpcH65KNulIMRfB4kMLkvBVA27lDhc+XnDevi5q
210 210 d5d9177c0045d206db575bae6daa98e2cb2fe5bc 0 iQJJBAABCgAzFiEEgY2HzRrBgMOUyG5jOjPeRg2ew58FAmBHDE4VHDc4OTVwdWxraXRAZ21haWwuY29tAAoJEDoz3kYNnsOfo20P/2eaVVY+VgaHktRHpJKJsC8tc8brHXfwPTijTzWl/2d4rZ1QwvyYFycl8LwtHeVdjvbDf61YIX2BiucX+rG11x21LyPPgD90pQ0VdRgoGXgVZX27exkvS5DUhqXnVnbey5dH3pFAPtYsC3jHsoo8NyNDrn2nXdvzzABArljIVyjnG5JokPiEH3dQSY78HlJR451HlrWEmRgL9PlzHGDRmpkdypKiV8o58386uqCz5zfugA9aC/JYheNA40xM3PV24GbJ/dtMqztzOh6MVxFWV5+krK2hXBXk/p8eE1SYDoO5tqZAmSgKmBJZ5zas4zRBoJb51BiLM0cBaxmBiqZ+sv9IHknoyEMisc4+0O6z7JKqLiZetVbvNVOkCP/CbKyik+evbZnQB6JhgOSCjfcLD5ZFl8GiRiz84ZT3ges5RTyVcE6jJNUV+nwmNdW2qLQP9JydInKNwTrEgZcrJDv6i+lu519p8+zcOgIF1J+CO8qQaq3+j5MA4Dttat3anWOQNIzbx4yuG75NezVN3jnRGmoSGwg1YLseqjQCBlpJrBWTD1SsuWpgbKx4EiELDN+PcDovxB2pYa+NzFfv0ZFcnWuLpr6KjCgzBkTK5KfmTqu7I+eM29g+2JvmCao+kk8MVyVmV9H2f5xRvuhrEBmDNlLb7uOhJW3a7EvZG6g9EfW9
211 211 f67b8946bb1b6cfa8328dbf8d6a9128b69ccdcb4 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAmB+71MQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91Vj+EADBa/tHfgyymKmXXl9DSlzwEhX1DkCE0aRcsbfXujnpOQrDi09pfHvtYEbgJfl6m8JEUOjuRRcxofnIWOC9UJCGC3ZfW5tTcHomCFlqjHhUxGKsvQ1Wcec1IH3mmzhqLnd0X57EgnNC6APwgxNVRmC0q7M7rSlNiE8BkHEUuyCau5FvpgdF31Aqa9IQP95pmmeDwL4ByPR1Nssu2/8N5vbcQm55gdjcggNjBvNEbaFHDS9NlGS8quvCMwRZkr3meDfTeCs9d2MveXXvV8GVOFq+WHMoURVijTjON+HuXB7HLegyhVOcigfbU5zxGY/IAJ/tAYEzBLWSYW6wjsN5uuZP267XhKpd2FT8Cfe9t3OnN1K21ndltlaMSdGyAynuepzVE0IELOCiKlgBZkdnft2XkUt2DDg/TqhOeXmUBzIFVze5KULSgrFvjkx71iV22LUGkIxzIuW5ieBMeZotKHzI+ZXO7xNSDIdoSfERKUqfYJKbksnBQLRxYUO77KetjocsMMYyB4Dpzu05+eWpYtZs2u5PsqP/Jv84Mz3QR0szAI1h3KlhmbkvKxnWnFYasAdFPMluX4G4X+9+MulODCwgw/RvQhh13M2QP0vGb1Xzu/JOuxRr3zuliTUfszd7YHVJoROzuT9PlcZ4criwZwv+fvbCN+F9LRbeI/BQBVZi6w==
212 212 8d2b62d716b095507effaa8d56f87cd27ba659ab 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAmCAO3gQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91YvWD/4kn4nLsu6W6hpSmB6qZB7y9adX8mqwzpSfnt0hwesk5FiBmGnDWHT5IvGHRTq0B3+peG9NH5R0h1WgtCdyh6YxGg0CZwNoarv64U8llS+PTXp8YZo/bVex7QGKQJr45Xik4ZH6htJ0muJUhzpHa6wkthTxK2OuaTTJvJ53lY8dR4lmefxSYPAwWs/jOzkmPwIeK8EnG0ZcBtmheJESOzKnmmOF6N4GnUGFFz/W5q8Gfeqj9xKKDt+zdPHXCEZUYivBcMPL7UNti2kvrp3R7VXBzbw/bPAJTrq68M4Z9mFb0qRZ88ubGXu+LEufsG2Dls/ZF0GnBPeReuFFrg9jimQqo6Rf/+4vV+GtFBY71aofFDDex9/s0q7skNEBxLP6r/KfsachYzvdciRS46zLelrL/NhpDvM6mHOLWmuycCeYShYctGbc2zDK7vD136Da6xlWU5Qci/+6zTtAjaKqdIpJuIzBfKdhaakri8vlpplpNLIDMfTTLyYKVAuHUtZcwHcHWmx54b2ulAmNXtc5yB/JqRIUined+Z6KlYc7c7MKEo2FB2/0okIbx7bIiXbV2of4j3ufv+NPIQel1qsnX58vbYL1spdfynNMTHQ+TYc9lUvuq31znu2LLJ9ZhTOiLEt1QZB28lTukzNuH2MEpGWtrOBIC9AcXjyyZ8HlIwEWMA==
213 213 067f2c53fb24506c9e9fb4639871b13b19a85f8a 0 iQJJBAABCgAzFiEEgY2HzRrBgMOUyG5jOjPeRg2ew58FAmCQMXEVHDc4OTVwdWxraXRAZ21haWwuY29tAAoJEDoz3kYNnsOfpJgP/isIDkbMuhot376RY2SwilSCkjJRoKRCDyLjJReBUF29t+DPWs8h971t2v5DIasfuQZthMv9A6DYcyEs1Q3NTKvT4TMKTTrqQfIe8UMmUa9PI1SIuTShiWbwonrN8rrVMVVcjPO/gookMV8/uoYW3wn/SThkBEYYauONBBVKbQ/Bt31/OPbEeAEdb/IEJ9X9PL1sfQkf+/DA/cwawS+xn01GAxWybx8eJkcJFdGdUcl/PYWgX76RSUhGvD6aHRJTZ1+sXy7+ligfpdPkNrQ248mVEEQkmZaCQ39dQPMX5zLa2hEX6eW9b1BEhNjHzbDfyqwc+F5czLw+R56vjPUyRCkxAZ6Q5Q3vkgLPBlZ2Ay0Lta/5+qGWcX+nDzfKfr2FhBLAnRZG/M+M2ckzR+8twyKg7/vdD8e/B3+Oxmu5QTS8xuj1628Brf9IehedQHoEPDe2M5ynhlEcybkbLz1R7zWKrh2h76OGQtspcjF997W1uZFx+DH6kHSznIm/8zEXy13R2nZk/0YtGX2UjZDv9bZ5X3B7T1673uscx3VpiT8YLJVKX7FyFLMgUbVY9ZGFlQ/pzUP3gTGa5rAB8b72U45jlXdKKvCn9B3hbS4j9OzJKpjsspWDmFHl2/a01ZOL/SZtMlm7FeYymUXKc10dndXlXTlGxHFUJQsii6t3dDyf
214 214 411dc27fd9fd076d6a031a08fcaace659afe2fe3 0 iQJJBAABCgAzFiEEgY2HzRrBgMOUyG5jOjPeRg2ew58FAmDnSgwVHDc4OTVwdWxraXRAZ21haWwuY29tAAoJEDoz3kYNnsOftvQP/j1mvheFHsv5TSJ2IEKgEK4G/cIxt+taoWpecEUVN5JAk7q4Y1xnzcoyqQdAyvZcTu7m4ESx865XW6Jvc0I2pG+uKcmO7ZfwrAOugoXXxrlXtopVfDDFZOLlk72x+Z5tQpL9QcBUgetkuOZLFhT+1ETjnFd2H4P4pwPjdTpn+YBmDmh1tWTMzllTDDzvZeE6iAjIpM9IQKL4jKxcEjPAX2XDa1xWhd/o9NZC9kYSTIBQvbFWAz3A0PSAudz0lu5YDXKJNtIHlzZtMFmcUlqJGM4MlD6v9tm8EQbCWTgOm0+wB5miDqv05aC6axD3LnSgrlPsmRDZCIRAws1JHEjKYFob7VRMxpivW7GDSd6QrmUbTHYN5eY0v1YB62dCa8W9qk2E7R5VdLRi4haFTv42u7jOZT0tSzRv/R0QppoVQ7/Fpqpps+aoZBM6EGj/pAxRgBTHeyI9WTFUAYDbhRuN9EoJAqRUCpXn39oR+TsaD9COENAJroX2WLIY8XFD3UzrpA9NPt7JE9mufWoNipNqLdLY7k3p3UxX0/SDboVlax6ORpQN+YzYhCesJaAOhlTAXMRMyXsfw/ScYttXxmIJ7BINYEMSXM55uiUPYFjE/GuZjbjgqk3dmJr7ceAyGa5v+m5Hr6efPSRHKUAxkEcDsXpcTHyEOVt3l7Qwfd+oUumK
215 215 d7515d29761d5ada7d9c765f517db67db75dea9a 0 iQJJBAABCgAzFiEEgY2HzRrBgMOUyG5jOjPeRg2ew58FAmD4lQMVHDc4OTVwdWxraXRAZ21haWwuY29tAAoJEDoz3kYNnsOfVsMP/19G6aZBokNRdErXcT86ahVy82IquR/CmLJcdj/4nehmBXToLCmdeqKe17ZKgZ7bnPnevhO07zPub7RUhDixnb7OxpbXiyP7x67FAqAfKvi8rZggmeWZT5kpiltoBIvHDlOlQhsgtfea0REULyn4zNB6dLED5zh2Ddr5LcWIjfOvIWo1F0eFMcRszL8f2u2ei2dERDuG8MSzMsiFHMAPRMHJjm+YukJBuz78CH4qT/Inkq52ao+3GCh4fFBhPG5+IABeCn1J4cAAK06mPcJqa7fbv7NfUCN9MeDNQUsUGGfIhKzGHJTb7PwXkKJ3qpLPs4FYGV1ZTucrIU1i65hXuf66QcYGlAQmKavS7xDOfZhzrZrAKe65dLpWdEH5mpTMcjaMBS+mhfMJT7DQg9T/9jISiKeqiFNkNOy1cobpJWes8iFwihEBtEhCtiVgnf7i7IzZY/spmSmP4ot/MEBi3jMjvAEaH1HyDGOPuBuqRSIRU+Mf5o1yB2kZmGL9vHWUzm/ySjQFYte061OyE9bZrbF9daOTdRip/CXPApOneVBIMwXc7fWDu45cKyVg7kYo8a0gcFfg39Ceja3Z8iJSFtJTuj1Sd9q8YU6pxqDrfPm1byJJlb7SvAoZfIGQPFk+DF6UVEcWRC0MYRm2bHXlaZwNVpgmFv6ZOVja3jxCJkw8
216 216 2813d406b03607cdb8c06cb04c44efcc9a79d9a2 0 iQJJBAABCgAzFiEEgY2HzRrBgMOUyG5jOjPeRg2ew58FAmESg/wVHDc4OTVwdWxraXRAZ21haWwuY29tAAoJEDoz3kYNnsOf6kAP/1w3elvhAYQcK9hkEVCg4sQgnvcatOafCNaK0dVW9OOFbt+8DNUcHbtUHZtR6ETmSAMlWilIr/1vRMjy0Zic6afJ30oq8i+4f6DgLyTsLQL/QdwJQIwi2fZmHebv1PSrhT9tJAwtH6oG3cNhSq8KMme4l7sVR7ekB34Cmzk3fa5udMOuQG9xWbGTmeEsx0kYb+1oag+NnnZJqVTi68gGGxRW8TYZ1APXJcrZVfkldtaIWx6U1UdkWSTqWHV4fnnctp/1M+IgXCLT0iupY5LnxqGKQcMte7WKRPPdfhGF1ta+LN+QPHbwXhDRDIWPBVbDeHxjKcjz3h+DOeF0b7c5vKDADgo9LtHui9QhBJiCDHwsM+8gA+kNEDbtvIYYQ6CLxX9m1TttxI4ASIzFGIQF6nBr3mjQCzmOoWtgVh7R4dsQ9YZgm4twjsIg3g0MDhmgs71jn6Gp4BficF25nY8J6Ct8YopkPs2sfiBYJmyh9NJLDjwqNnjq3MBervPX3B+7p1dfIsK4JoSuop5A4lc4OOEhrwm5BKIxm30R4NtB15RZ7nI0DcRFcwNQiTYPG+nOaPsFzeZD6lj8+YnuLyo2aCnf4K26/1YTlE1wOFkCb1reL99++i8FP94poHBKZ7+6HT6gk4Mmnfb52II4yWlh/CYLeKEzFFfAiOTvfhzpIvqg
217 217 53221078e0de65d1a821ce5311dec45a7a978301 0 iQJJBAABCgAzFiEEgY2HzRrBgMOUyG5jOjPeRg2ew58FAmEeqLUVHDc4OTVwdWxraXRAZ21haWwuY29tAAoJEDoz3kYNnsOfMb4P/R4oPBjSKrlGbuxYClNdP0lV4C1NUU1SPa+Il4QwGQteKD+RDfvp8z8+c45rVIEGiUNzaSJP/ZEyhBVW657rYzIhBnZgqnpwBzOViqe4Q3lHiq6wPKjEDIRJafcqMb6MaViPS6iRn6hhMlAcPcoabwhXrUgv8QyxVSTFlJm0RGbUVekQLIWKEAnwcWLHKt0d2DrB0/706xXtKxdJ8N/2WCVOOkr7UvpdLXo3quOz1S930/o1iF/csggsi9q4oZYj2XBdBGHayoqkhKAQMyBfXH19RqW3SWZafY8whrZDCz+9AAmJJk8hjQl6xrT/ZVweRfqvRoMJBgjQdFTi58wjC8995ZXKEC7jsJCEblyRJkc23opuAArPEkJXLDR+oK1vOfikaRjmQoMPAMDjbxTUyVOuHcX+PxMtq9NAO0MKcnSr+D2Xc28TGY9PkBhRkEnN3nlZH5z7DvF8GfOnUt5SGhFiQHhXnL6jDBCQVDKAoCJn0WKDG9+29I6st2eGEwKaIjZQ9NCtaLASiauopMOyWWbHeM58bCl80TBXuj+3W+mo+zDSLoGwWJc5oFdFpmnGGTQtkxPDiV4ksIgJAMb/KHkGY+RxnEsWgX1VcR2c1sYD4nzOjrt4RuvX1i+cfzRjLOchPiru7BbrBQRTXGhrvNzsS9laTCxCH2oDazIudia4
218 218 86a60679cf619e14cee9442f865fcf31b142cb9f 0 iQJJBAABCgAzFiEEgY2HzRrBgMOUyG5jOjPeRg2ew58FAmEtHx4VHDc4OTVwdWxraXRAZ21haWwuY29tAAoJEDoz3kYNnsOfALUP/331tj8MaD6Ld0Jq+yLK7dRlLa0iZ6Kbq2Nq2bYFrv1V99RMG/0xipxWnHfn+B0qdane15tgYIugiVl5pQCGRBeva5CJEg5hfiN53tDDXc2duwaj+kYAREPZJm3lEtv4Tp87E8XZxnJ5qDnNeLCmtpFEEs2bgOHHY/fwHUf/hu0jHJHvkxXh8zPHBf2le6UOMR65PS89bv0jKKmtYPVuYhs/sPRFp78FbYZPiJ0x5NxQsrkYd3ViaQaT2Hb47fpTEg/t1yD3nkZyxHzrGhkFwrLJDMTafuPaXtzVN0BPT9iztgONm+5cF4g6+4AvFWvi5ki87UmrYMCHoiBxKycKR6O+rxh5aay/69I5iIJlcrxyZ/YkzaTUbw4rAZdaTfODwaYOBeMPJp/MviNB5kEGeCV3yLpbftIzsO9BPJ4VtSadVA4HPN/OvAGcYvGO58rN22ojHnqyrnmmuhc4K2/i94+dkMbTyKHrROMXwkJFgH4i3nukyo5fYw5c5ggYAvtEsHLpihv9hXPafTQvmz17f+7/fNi6qJsjEhH8MPjfFpydkjptIyszZ9tx6HyE+2699vJGVHRVepw6RFVOuneXsyKzNeSaw/LmO7B+PfBxpBTvWLblD6DH09pzisTacoMrhvugvfGZsYEFxGt34NvN3Hqj0+ongzFM53UvzMy2fLm5
219 219 750920b18aaaddd654756be40dec59d90f2643be 0 iQJJBAABCgAzFiEEgY2HzRrBgMOUyG5jOjPeRg2ew58FAmFcc4wVHDc4OTVwdWxraXRAZ21haWwuY29tAAoJEDoz3kYNnsOfatIP+wXnpFitqScNjqnBK6+DaTj+rmBlKoZGB1IQJW5ziDN59gJmT/axemrc3O8BJ/OFO+gDFTX6mk1/L+1Ul4BAF8Yo8XrPd/V7+M02ZUgKTbHmOqTosa9sLeSEojdQQRfSPTHgtA3CLm6VB91fCCfpS9yfCWO3+T8owNelHl8beSqcSlmAzPjqeF1EmalBO4YjSeOCfSdNpVvUGYG8OL/LwYWJqbea7LpN/Sq0piNMqYbc9GYeB9tnf0338WlGEaLTTDk8V3iES+EZxTNeN8NnpGvU0RN50CUfFVyadtbdXUzRDjF4mpdEnsQBkje3hGotyrzDZs1IjKGCANiNBb6dyn/wgv4APOLFw/BLat1Y7z2ZJ6sqUkBbfOs6H2KfufwFZl1sggG1NNXYrwjdS8dHuwi7FRzWMgcYi8Rle8qX8xK/3+We1rwbHfYxhmlEvC8VEC9PZl/K13aIuKmCQ36Es8C/qAtnNfSKZNkYoi/ueAvGFvJo2win1/wIa/6GvBfCxS3ExR1dH+tAUHj2HgMuQXMI6p9OuEloI/mJbdLmU9vnn06EcIyiIPd3dn4H2k0h2WNzyIoVE6YjD5T86jumrUxIj6hp+C9XYYkoj4KR17Pk7U4i3GixDpupLc/KoxiQRGSQTogPjD5O5RCg41tFaGav/TcyW/pb9gTI+v3ALjbZ
220 220 6ee0244fc1cf889ae543d2ce0ec45201ae0be6e1 0 iQJJBAABCgAzFiEEgY2HzRrBgMOUyG5jOjPeRg2ew58FAmF4AWgVHDc4OTVwdWxraXRAZ21haWwuY29tAAoJEDoz3kYNnsOfxu8P/R8FftAoLkFGHnrzXA9Wa+ch+wunUNixCSimuXjG5sUtDSDlNT+xGj0deTVRVDylFd5HShR6a8NV+2P9edgJYDOKE70j4DJxHdeDyZ3l09YEBymrluE4FygXwpG0B3Ew9pUD85yFxa6UfIFWvNTGYi7XCHBl85buCkMACafN97802jXuE3JV53FvW6Fp917hM0saG48Cnp33WZxdUrZdxXU0Q8bZ9OBYCuGq8Wt2ZIqfEM6YXmvOzlkZf6oJb65rYOw2KgfLs/5nEGiDUNK2akuEhAZLi7uL0dt4WzYAbLyRhIpMpFPitk9P+Ges7iYINwSyZKZcsNPm0NiJupSjKqIYuuLte9HR59RkDFGgM9hbFnskElgHXMqLxi+RqjDVrj2efbuyWzDCn6eVZyn7vmxy9/oLM9vnVsvvdziN2uNUPL4CVmnOZciCdkEZQtWynyyEGzNyq7kPH593ct3tYMxpzs3wa3o+sSdph3lf7caXskij0d0woRZneuZFwp26Ha9tKMMRmXzgFvipzL+o2ANWV6X2udO0pXmKhzYJSBcUPlmVz8hyJaV2D3nmXeFHKVrPa/CqnSGNPWNQC39im1NyPKbfJAA9DZmw7FKg/b23tJq8w9WkBAghEUhC4e54Eb068awt/RDaD6oBYfpdCnQ1pbC/6PHnRSOm8PubGoOZ
221 221 a44bb185f6bdbecc754996d8386722e2f0123b0a 0 iQJJBAABCgAzFiEEgY2HzRrBgMOUyG5jOjPeRg2ew58FAmGKo4sVHDc4OTVwdWxraXRAZ21haWwuY29tAAoJEDoz3kYNnsOffmQP/jsOxxP0F9TliKYp7YjgMagtnebk+qdbq9pX8y8GdjGirRwCy/rMm3pXMNQDiWd3ZdYLICZIz8aSYbPL6HD78O6F68IWOVG5AwLM6knUNcEzmrPoFnSU1J7jaz8ERFmfNV6loes3oYj/VhRUDiFEmG1sflCc1iXvTEXaOi2PObo7iORR/2JtOlMQI7bASBTo0F7QTRzOuh+SzgJ6ItqpvjC+I2Iidn8yZ/F3jZXZ24on/D+b2nLQ5b7yc7pzVNyqiTFF6xHQEtRjNRv+hLS9mdD/oI6Vhwmfv7GD8U4MyudDfz5GEv2AE9cwOKRONfHdXhFX3UiubaDmDlo+mE3xXIPYJoTtadoUhVItCe5YAlp9P6uEAaWk/Z1zI+9ydYACycO0RySrphRJ3DmDITs7D2bQEsK/YB1NBzwlUJVFiTu8x2+taBk3vO66cfuyubvPXpdZs6VcnIxSMfduP29zYLj7L1YZo58y3qhKeWcZexYSBT/dtGZlOOdobI/t9YHKnrUtzUCL9JIuxqn06+dSU9DlNuOd19Mdr2wu+xncuzlkd+Y4DavctrA0uSw4CAID6e5UIoknAeOzMSFySZ+JLw79z1LpFx/t3wof5ySC6olLO1NFesK89NAYszIjeTOQnpcK9sA2OaANTDbC7sX12OmpPlRySNcNRsaNgux6Bnl4
222 222 5d08b289e2e526259d7d5ea32b70fe76d5b327d7 0 iQJJBAABCgAzFiEEgY2HzRrBgMOUyG5jOjPeRg2ew58FAmGcvOQVHDc4OTVwdWxraXRAZ21haWwuY29tAAoJEDoz3kYNnsOfNcAP/0zjJ+vfms7hBPltQJxzRX3JaMSDGyFB6+0CXJnEHClcjmcmmFq7yPYSZhO1/wRwNDag1A+xOr+xch0VHy3s2L4JDVqpTEIGDVX9MZxqDYdFMpMmx63KQeOraTbd8MCpbsiCsp+yQWwQ0k8sjajY2FhpJFezcD8EVH+XQJSkBsPGQZGezNt6IVlnsnBpTl6abVFWrsHhpos1Wa7iJM/sS91dy9We5H3B1eEn8KOMyj3eWEA6D8D29kCS66E8+AQ+f9ctresD2g/6xS1P4CTgvqacS+gj04rMUKmmQUoMzAXlS4wO2F6J0mWdKfZsv/urfJx7oc5GZysrXw+T/YLxFKuxls1uCq6mTBxbf/aJ91G4m0UT/fczNrQaDDhPIFEZVktd18NphUOebTGxDiCW/mk9IOXxEI7bprlBdBBM3dkCAg+O0h8kdN007jjoLIiTw7K+XZ1A41zqGqXMQ2R/0xTltX9NXAe9xNhAEQhwSCH2TsB5IKI6+EHE6ZaNsyuwvlPhaQXfmOU22JBlUGE9IdEU5whd9760xJYTx3WEnbuED0UltAt3vgyvq+li1/Z7HDuzUyNha8YsaPw2QeHFUFwzxqoxo501/eDs9bXjBt7E4vsYVQC51sb3uS9kRbBB9GOiyx/HICZcbEQjy5TxVW5Bp0uD6Fu3nRytL0DDDIDF
223 223 799fdf4cca80cb9ae40537a90995e6bd163ebc0b 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmHVzPMZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVmiyC/48p6+/JJi8WaY+Xdxh1IMK1/CB3dYcC99+V89asIW+g/X/0FacTSSAGkvDrjNSeYAkXGp3g/LbEbwoZhKxF8MyKU7TOn62lz8JETwebtjxehjVfPUy73RJbuLPDvn9m16YHxuC848hDZHnqk/PjaBVHeZ2cN8T7F9VgXkhyYStV9GT2PSQUsvkQAxjiLilyKs3RaZAduZPvOmGaq2CfK91PbScKaKgYShkKym7gfhU1o4pynNmuPqRwUJyihaZqsKDjOn8OHeJpqAm7ODmR+SIOvMvFbbfS8mTSfYMHsP+r+JgbqSVNG99qEqsIW3HznGe/OpG/1QS3MVVSyi87oHR1UcN91vKIiln92i+7Ct7GttjkgkkqfQEw1oAELCmiHacYEBbLvQGaXdHROeO6wqXUKvI4KeM3CPt2qsouPiKBzSF1eOPd967NNvgTgcabT2ob0YaXmWdZasJnZ74H/3FMMC98WhYe3ja+6cpl67PZlNUWlnIZBlyL63DWSJ09us=
224 224 75676122c2bf7594ac732b7388db4c74c648b365 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmH6qwUZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVogkC/4hgjtCXykyst2XuC93IkWdRoXiFn2+C/r/eX25el//+Og5T0KZmttFGrmTCSCdb/ZkjPg1ZHYBUK9gyQCOXoimATIeql/USCcglpVBRMTaaqvpJyHA1antI0HIsNFGjDTIxHsJXgghMEv7qVR33ItpZ8gtWbJJLewOwi2UHtLcmif77SgpeADh/E/PuQT+0Wd5gA6jk9Fml7VBP/nU81j25ZyxB6p8oUv4gFSNDZtrnA97mQ35jYZZITl8e80Y9Z/8KJFcRk29kxIudOikwn6AD7ZW/H85a3lDOtTMhgBDNlMxvXx6eviKfsrIVtNCm6QDF+36VstTR+idWyhnkq8g20NXcgWt79/CTWT7ssFmzdsHhdhWfJF99I0R0FCG0DSV313UmleZawavG1btOh4qCjTAWF5gnvsHfEIV1SAnDeeD6T27c8yIW7au9QXlkZds0xmFWLqkl6TxKpl7oa/bGDArAvOA3zHAeMlwXQKhhthjR7fU9PQnWsFXCt43GVo=
225 225 dcec16e799ddb6d33fcd11b04af530250a417a58 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmIPiSsZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVvRYC/9Ul8I7vJvCaFwotgAuVBGbpcyYwhCkxBuxyROInUjhQdrSqYLUo7frlDEdoos1q0y2w9DiTyBeqeewiYw77DXQzKPtxqJDO3m1exnbtsmUQhQBF8mUyDqO0yay6WcGp9daqIlFnf8HzXxBgvkpI1eReVoLBvGWzc+MWKmdPrVsY8CLyMCSXKQldyEa9uAARBRDnT2HTnPUDwS3lav5sHYhwWUuC/dwSQWlSsmIUrY2sB3yY9KS2CrUFkXGo3tmQNHayCXfKmyW04xoYlIKQxrXLQ5hOCaogExsSkdXzCDaQS6avS0U8QaM/XuXe2BDR4wq7w7iomM7xagoqbx/0VINizfbSh2sA/Nxt4/mf9V2VCPUh9QlSJztNTbSUOvpOPbk9l9KafgEQTspnsleRXQymAhBuCd9aap0Q9NC4vixVPWxjqyxyFS0eRbnZ9/LTI0+ZCHTizupG0nUiXY3cpwQB6a7CRdn8qdMsA0FURAJlVE4nDlSsY4v9AWxPHreGJw=
226 226 c00d3ce4e94bb0ee8d809e25e1dcb2a5fab84e2c 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmIPn9oZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVpamDACfmZw0FscQ6oCs1ZyWZ2sf6xxYnk242h4ca8fyILrGfuhlgkochlMwF8id3EPVKnie3QHBi33Nf5Tz9eFTFR4z/eQ5W8R+bjYWo/F+4FDkaTIprvg4gfoH1MklmpVhPa7MFVmp7tmSx/0EVdpJuMkJSeAU1kQ6Mq8ekMWQT4vtLbkAOGZcnwKiU57j8cYnOjoIqA+22/S0DBWMKjEnuz3k8TjplsZXVgTEUelFAwT4SC3qNSIBvVYyDmdAoD0C4zL88tErY0MeQ/ehId6E1khLvw9I65z/f2hOxXiDdk0b6WV2MCh1rxCX5RUiH0aNUmG+hGphpH0VVqQihkQEIdzZhXiFVlEc/rAbdt3g7pVc2RuWSanBUEOcvly0r40A2wRCka1jjgfz7dtmjZ91SKCPpOUdxHfaqqWz/0Y/oIgpq/UM+1fufDxeLZG+OY8B5y+c+ZUuGacAVNRQku6IB+0dT4/DTEsYWT3VMIH0ZzGFiAQ2g3IPo6qlLFK54LztXTg=
227 227 d4486810a1795fba9521449b8885ced034f3a6dd 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmIePhwZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVm3LC/wP9h6bFiy1l3fJhmq2yKuXu/oNWqT7CmOPqOPnQoO6Pd7a184kvgrabU9dsnXllj1mtbUhaIcfZ8XAb30lTbr0W1dSDoT0QWMY7sOFgXIvJSbWWmFo8DrYQSTlg1xA0LWdwsSKmce/r1G6D7JERj5VzBs3Hq65Kb9vg94vqdVSvyye+YzSODSh1w8P0qsgv78UWqabSrf28DlUp/kG7j43k1J93ZEOgH7+jrxgiQ2WzhmhlWcUFJOGxchbdDl5XZptwPssNstUgXfZKe5sFOI7WJSN//rHo3JgLbEDCX7TMe82aPl2DxEquHNH8rrOha4UuGZjFwO+/PzykItUCPzPWabE6z49w6+/G1us+ofts1z8Muh0ICegFxbd0bRotGRmJ/iEZqrtgFQokx1SSlZKArbRBbLfWoJcczxWxBK1qCz2avKY4qKcieC9TTo7LrHqA5JvLNuqvInKITYOfq1zCuLvxnaSCQTKKOEEb9/ortjxN9rvx1bFyRorVvXR+J0=
228 228 5bd6bcd31dd1ebb63b8914b00064f96297267af7 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmJMXf0ZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVpSlC/sHnQTin4bLp+F6keT9gGCoDqx11cf4Npl6RmqM3V4SN3hP3k8gwo5JOMWNSYzwxuBuzJ24EBTtgV139NPdeHce3LEaDMMg+n5YlQjl3vqFnYPAkX973yHH1R1ijkdGNtM4KfWw6C7b8stNaKCQmnRBsKy7oxGKvHoL8ufiSmxVtkP8ImW3x9oiYUEueIWMVhaIvNANxOzsiU++yubo1ldFGXOnNAS91MALeeu7ikClaJQQLp6jMobnn0qI8TGzbe5LnexA81/qIltgFLyUAWA2d3NXVis7hFjwLToyBkObpZfq6X/7a9XhBHMwTM+O8ViYODraupcYw0vrqT93cbuBSN106sC1UERaVN2YNb1gsoyqXTZ2F8ho5QZWJphQw9cwKJkOn81SXJ8ZWr+L8WVm78mrbDV8zT6lQ/7IsmIXTQNWMBgeGc74qyReowyswP7hSbl9iQDcdKMus/4Gm9cqTnYg3Bt8jZ3lupeYMv9ZSFmKDG8A69QFLKYKzd/FFx0=
229 229 0ddd5e1f5f67438af85d12e4ce6c39021dde9916 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmJyo/kZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVsTVDACmg+uABE36kJcVJewoVK2I2JAdrO2llq3QbvzNb0eRL7bGy5UKJvF7fy/1FfayZT9/YTc6kGcRIeG+jUUiGRxMr0fOP9RixG78OyV14MmN1vkNTfMbk6BBrkYRbJJioLyk9qsXU6HbfRUdaCkOqwOKXKHm/4lzG/JFvL4JL6v++idx8W/7sADKILNy2DtP22YaRMgz38iM3ejgZghw7ie607C6lYq4wMs39jTZdZ3s6XoN+VgsLJWsI1LFnIADU5Zry8EAFERsvphiM2zG8lkrbPjpvwtidBz999TYnnGLvTMZA5ubspQRERc/eNDRbKdA55cCWNg3DhTancOiu3bQXdYCjF1MCN9g5Q11zbEzdwrbrY0NF7AUq1VW4kGFgChIJ0IuTQ/YETbcbih2Xs4nkAGt64YPtHzmOffF1a2/SUzH3AwgMmhBQBqxa02YTqyKJDHHqgTyFrZIkH/jb+rdfIskaOZZo6JcGUoacFOUhFfhSxxB1kN2HEHvEAQPMkc=
230 230 6b10151b962108f65bfa12b3918b1021ca334f73 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmKYxvUZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVqsDC/9EKBjkHvQeY55bqhqqyf5Mccw8cXH5/WBsyJYtEl+W6ykFRlTUUukY0MKzc1xCGG4sryTwqf8qxW92Yqt4bwoFIKIEpOa6CGsf18Ir/fMVNaOmYABtbbLqFgkuarNLz5wIMkGXugqZ4RUhs7HvL0Rsgb24mWpS5temzb2f0URP5uKFCY4MMC+oBFHKFfkn9MwAVIkX+iAakDR4x6dbSPKPNRwRqILKSnGosDZ+dnvvjJTbqZdLowU5OBXdUoa57j9xxcSzCme0hQ0VNuPcn4DQ/N2yZrCsJvvv3soE94jMkhbnfLZ3/EulQAVZZs9Hjur4w/Hk9g8+YK5lIvJDUSX3cBRiYKuGojxDMnXP5f1hW4YdDVCFhnwczeG7Q20fybjwWvB+QgYUkHzGbdCYSHCWE7f/HhTivEPSudYP4SdMnEdWNx2Rqvs+QsgFAEiIgc6lhupyZwyfIdhgxPJ/BAsjUDJnFR0dj86yVoWjoQfkEyf6toK3OjrHNLPEPfWX4Ac=
231 231 0cc5f74ff7f0f4ac2427096bddbe102dbc2453ae 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmKrK5wZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVvSmC/93B3If9OY0eqbzScqY4S6XgtC1mR3tkQirYaUujCrrt75P8jlFABn1UdrOgXwjHhm+eVxxvlg/JoexSfro89j8UFFqlVzxvDXipVFFGj/n8AeRctkNiaLpDT8ejDQic7ED566gLSeAWlZ6TA14c4+O6SC1vQxr5BCEiQjBVM7bc91O4GB/VTf/31teCtdmjScv0wsISKMJdVBIOcjOaDM1dzSlWE2wNzK551hHr7D3T5v78NJ7+5NbgqzOScRpFxzO8ndDa9YCqVdpixOVbCt1PruxUc9gYjbHbCUnm+3iZ+MnGtSZdyM7XC6BLhg3IGBinzCxff3+K/1p0VR3pr53TGXdQLfkpkRiWVQlWxQUl2MFbGhpFtvqNACMKJrL/tyTFjC+2GWBTetju8OWeqpVKWmLroL6RZaotMQzNG3sRnNwDrVL9VufT1abP9LQm71Rj1c1SsvRNaFhgBannTnaQoz6UQXvM0Rr1foUESJudU5rKr4kiJdSGMqIAsH15z8=
232 232 288de6f5d724bba7bf1669e2838f196962bb7528 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmKrVSEZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVqfUDACWYt2x2yNeb3SgCQsMhntFoKgwZ/CKFpiaz8W6jYij4mnwwWNAcflJAG3NJPK1I4RJrQky+omTmoc7dTAxfbjds7kA8AsXrVIFyP7HV5OKLEACWEAlCrtBLoj+gSYwO+yHQD7CnWqcMqYocHzsfVIr6qT9QQMlixP4lCiKh8ZrwPRGameONVfDBdL+tzw/WnkA5bVeRIlGpHoPe1y7xjP1kfj0a39aDezOcNqzxnzCuhpi+AC1xOpGi9ZqYhF6CmcDVRW6m7NEonbWasYpefpxtVa1xVreI1OIeBO30l7OsPI4DNn+dUpA4tA2VvvU+4RMsHPeT5R2VadXjF3xoH1LSdxv5fSKmRDr98GSwC5MzvTgMzskfMJ3n4Z7jhfPUz4YW4DBr71H27b1Mfdnl2cwXyT/0fD9peBWXe4ZBJ6VegPBUOjuIu0lUyfk7Zj9zb6l1AZC536Q1KolJPswQm9VyrX9Mtk70s0e1Fp3q1oohZVxdLPQvpR4empP0WMdPgg=
233 233 094a5fa3cf52f936e0de3f1e507c818bee5ece6b 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmLL1jYZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVn4gC/9Ls9JQEQrJPVfqp9+VicJIUUww/aKYWedlQJOlv4oEQJzYQQU9WfJq2d9OAuX2+cXCo7BC+NdjhjKjv7n0+gK0HuhfYYUoXiJvcfa4GSeEyxxnDf55lBCDxURstVrExU7c5OKiG+dPcsTPdvRdkpeAT/4gaewZ1cR0yZILNjpUeSWzQ7zhheXqfooyVkubdZY60XCNo9cSosOl1beNdNB/K5OkCNcYOa2AbiBY8XszQTCc+OU8tj7Ti8LGLZTW2vGD1QdVmqEPhtSQzRvcjbcRPoqXy/4duhN5V6QQ/O57hEF/6m3lXbCzNUDTqBw14Q3+WyLBR8npVwG7LXTCPuTtgv8Pk1ZBqY1UPf67xQu7WZN3EGWc9yuRKGkdetjZ09PJL7dcxctBkje3kQKmv7sdtCEo2DTugw38WN4beQA2hBKgqdUQVjfL+BbD48V+RnTdB4N0Hp7gw0gQdYsI14ZNe5wWhw98COi443dlVgKFl4jriVNM8aS1TQVOy15xyxA=
234 234 f69bffd00abe3a1b94d1032eb2c92e611d16a192 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmLifPsZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVukEC/oCa6AzaJlWh6G45Ap7BCWyB3EDWmcep07W8zRTfHQuuXslNFxRfj8O1DLVP05nDa1Uo2u1nkDxTH+x1fX0q4G8U/yLzCNsiBkCWSeEM8IeolarzzzvFe9Zk+UoRoRlc+vKAjxChtYTEnggQXjLdK+EdbXfEz2kJwdYlGX3lLr0Q2BKnBjSUvFe1Ma/1wxEjZIhDr6t7o8I/49QmPjK7RCYW1WBv77gnml0Oo8cxjDUR9cjqfeKtXKbMJiCsoXCS0hx3vJkBOzcs4ONEIw934is38qPNBBsaUjMrrqm0Mxs6yFricYqGVpmtNijsSRsfS7ZgNfaGaC2Bnu1E7P0A+AzPMPf/BP4uW9ixMbP1hNdr/6N41n19lkdjyQXVWGhB8RM+muf3jc6ZVvgZPMlxvFiz4/rP9nVOdrB96ssFZ9V2Ca/j2tU40AOgjI6sYsAR8pSSgmIdqe+DZQISHTT8D+4uVbtwYD49VklBcxudlbd3dAc5z9rVI3upsyByfRMROc=
235 235 b5c8524827d20fe2e0ca8fb1234a0fe35a1a36c7 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmMQxRoZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVm2gC/9HikIaOE49euIoLj6ctYsJY9PSQK4Acw7BXvdsTVMmW27o87NxH75bGBbmPQ57X1iuKLCQ1RoU3p2Eh1gPbkIsouWO3enBIfsFmkPtWQz28zpCrI9CUXg2ug4PGFPN9XyxNmhJ7vJ4Cst2tRxz9PBKUBO2EXJN1UKIdMvurIeT2sQrDQf1ePc85QkXx79231wZyF98smnV7UYU9ZPFnAzfcuRzdFn7UmH3KKxHTZQ6wAevj/fJXf5NdTlqbeNmq/t75/nGKXSFPWtRGfFs8JHGkkLgBiTJVsHYSqcnKNdVldIFUoJP4c2/SPyoBkqNvoIrr73XRo8tdDF1iY4ddmhHMSmKgSRqLnIEgew3Apa/IwPdolg+lMsOtcjgz4CB9agJ+O0+rdZd2ZUBNMN0nBSUh+lrkMjat8TJAlvut9h/6HAe4Dz8WheoWol8f8t1jLOJvbdvsMYi+Hf9CZjp7PlHT9y/TnDarcw2YIrf6Bv+Fm14ZDelu9VlF2zR1X8cofY=
236 236 dbdee8ac3e3fcdda1fa55b90c0a235125b7f8e6f 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmM77dQZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZViOTC/sEPicecV3h3v47VAIUigyKNWpcJ+epbRRaH6gqHTkexvULOPL6nJrdfBHkNry1KRtOcjaxQvtWZM+TRCfqsE++Q3ZYakRpWKontb/8xQSbmENvbnElLh6k0STxN/JVc480us7viDG5pHS9DLsgbkHmdCv5KdmSE0hphRrWX+5X7RTqpAfCgdwTkacB5Geu9QfRnuYjz6lvqbs5ITKtBGUYbg3hKzw2894FHtMqV6qa5rk1ZMmVDbQfKQaMVG41UWNoN7bLESi69EmF4q5jsXdIbuBy0KtNXmB+gdAaHN03B5xtc+IsQZOTHEUNlMgov3yEVTcA6fSG9/Z+CMsdCbyQxqkwakbwWS1L2WcAsrkHyafvbNdR2FU34iYRWOck8IUg2Ffv7UFrHabJDy+nY7vcTLb0f7lV4jLXMWEt1hvXWMYek6Y4jtWahg6fjmAdD3Uf4BMfsTdnQKPvJpWXx303jnST3xvFvuqbbbDlhLfAB9M6kxVntvCVkMlMpe39+gM=
237 237 a3356ab610fc50000cf0ba55c424a4d96da11db7 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmNWr44ZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVjalC/9ddIeZ1qc3ykUZb+vKw+rZ6WS0rnDgrfFYBQFooK106lB+IC2PlghXSrY2hXn/7Dk95bK90S9AO4TFidDPiRYuBYdXR+G+CzmYFtCQzGBgGyrWgpUYsZUeA3VNqZ+Zbwn/vRNiFVNDsrFudjE6xEwaYdepmoXJsv3NdgZME7T0ZcDIujIa7ihiXvGFPVzMyF/VZg4QvdmerC4pvkeKC3KRNjhBkMQbf0GtQ4kpgMFBj5bmgXbq9rftL5yYy+rDiRQ0qzpOMHbdxvSZjPhK/do5M3rt2cjPxtF+7R3AHxQ6plOf0G89BONYebopY92OIyA3Qg9d/zIKDmibhgyxj4G9YU3+38gPEpsNeEw0fkyxhQbCY3QpNX4JGFaxq5GVCUywvVIuqoiOcQeXlTDN70zhAQHUx0rcGe1Lc6I+rT6Y2lNjJIdiCiMAWIl0D+4SVrLqdMYdSMXcBajTxOudb9KZnu03zNMXuLb8FFk1lFzkY7AcWA++d02f15P3sVZsDXE=
238 238 04f1dba53c961dfdb875c8469adc96fa999cfbed 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmNyC5sZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVqF+C/4uLaV/4nizZkWD3PjU1WyFYDg4bWDFOHb+PWuQ/3uoHXu1/EaYRnqmcDyOSJ99aXZBQ78rm9xhjxdmbklZ4ll1EGkqfTiYH+ld+rqE8iaqlc/DVy7pFXaenYwxletzO1OezzwF4XDLi6hcqzY9CXA3NM40vf6W4Rs5bEIi4eSbgJSNB1ll6ZzjvkU5bWTUoxSH+fxIJUuo27El2etdlKFQkS3/oTzWHejpVn6SQ1KyojTHMQBDRK4rqJBISp3gTf4TEezb0q0HTutJYDFdQNIRqx7V1Ao4Ei+YNbenJzcWJOA/2uk4V0AvZ4tnjgAzBYKwvIL1HfoQ0OmILeXjlVzV7Xu0G57lavum0sKkz/KZLKyYhKQHjYQLE7YMSM2y6/UEoFNN577vB47CHUq446PSMb8dGs2rmj66rj4iz5ml0yX+V9O2PpmIKoPAu1Y5/6zB9rCL76MRx182IW2m3rm4lsTfXPBPtea/OFt6ylxqCJRxaA0pht4FiAOvicPKXh4=
239 239 c890d8b8bc59b18e5febf60caada629df5356ee2 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmN48sEZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVqwwC/9GkaE5adkLaJBZeRqfLL710ZPMAttiPhLAYl9YcUeUjw2rTU1bxxUks0oSfW4J0AaJLscl+pG4zZW8FN2MXY3njdcpAA/bv4nb+rq50Mdm0mD3iLOyKbIDQbUoYe7YpIPbpyuf8G/y4R1IXiLJjK329vzIsHkqyKPwUzxvyfZkjg6Lx00RRcfWrosb2Jb0+EhP9Yi7tjJmNWjsaTb8Ufp+ImYAL3qcDErkqb6wJCGAM0AwVfAJ7MZz3v3E56n1HTPhNqf8UvfR4URsuDlk56mP4do/QThC7dANiKeWrFJSBPu8uSpaHzUk1XCat0RHK03DMr15Ln1YCEhTmaedHr2rtp0fgGqaMH1jLZt0+9fiPaaYjck7Y+aagdc3bt1VhqtClbCJz5KWynpCLrn8MX40QmXuwly+KHzMuPQ6i0ui95ifgtrW7/Zd7uI7mYZ2zUeFUZPnL9XmGpFI595N8TjoPuFeO/ea4OQbLUY+lmmgZQrWoTpc5LDUyFXSFzJS2bU=
240 240 59466b13a3ae0e29a5d4f485393e516cfbb057d0 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmO1XgoZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVn8nDACU04KbPloLl+if6DQYreESnF9LU8C+qnLC/j5RRuaFNh/ec6C3DzLWqWdmnWA/siV3nUR1bXHfTui95azxJfYvWoXH2R2yam+YhE256B4rDDYWS1LI9kNNM+A33xcPS2HxVowkByhjB5FPKR6I90dX42BYJpTS5s/VPx63wXLznjFWuD7XJ3P0VI7D72j/+6EQCmHaAUEE5bO00Ob2JxmzJlaP+02fYc814PAONE2/ocfR0aExAVS3VA+SJGXnXTVpoaHr7NJKC2sBLFsdnhIRwtCf3rtGEvIJ5v2U2xx0ZEz/mimtGzW5ovkthobV4mojk0DRz7xBtA96pOGSRTD8QndIsdMCUipo8zZ/AGAMByCtsQOX7OYhR6gp+I6+iPh8fTR5oCbkO7cizDDQtXcrR5OT/BDH9xkAF1ghNL8o23a09/wfZ9NPg5zrh/4T/dFfoe2COlkAJJ1ttDPYyQkCfMsoWm3OXk6xJ3ExVbwkZzUDQSzsxGS+oxbFDWJZ64Q=
241 241 8830004967ad865ead89c28a410405a6e71e0796 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmQAsOQZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVl7XC/0W+Wd4gzMUbaot+NVIZTpubNw3KHBDXrlMgwQgCDg7qcqJnVuT1NNEy5sRELjZOO0867k+pBchZaxdmAiFwY1W76+7nwiLBqfCkYgYY0iQe48JHTq9kCgohvx9PSEVbUsScmqAQImd5KzErjhsLj8D2FiFIrcMyqsCBq4ZPs0Ey7lVKu6q3z5eDjlrxUIr0up6yKvgBxhY0GxyTp6DGoinzlFMEadiJlsvlwO4C6UpzKiCGMeKNT5xHK/Hx3ChrOH2Yuu1fHaPLJ+ZpXjR33ileVYlkQrh1D6fWHXcP7ZuwsEKREtgsw1YjYczGFwmhBO362bNi5wy33mBtCvcIAqpsI0rMrExs66qqbfyG+Yp1dvkgzUfdhbYFHA+mvg3/YTSD9dLKzzsb69LM87+dvcLqhBJ0nEAuBmAzU5ECkoArbiwMT96NhhjLPRmJJdHNo0IDos/LBGTgkOZ6iqIx8Xm/tgjBjFJG8B+IVy3laNgun4AZ9Ejc3ahIfhJUIo2j8o=
242 242 05de4896508e8ec387b33eb30d8aab78d1c8e9e4 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmQBI2AZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVrRZC/wJyPOJoxpjEJZaRoBmWtkOlf0Y0TyEb6wd8tZIVALNDYZMSMqT7UBjFmaZijOYndUW7ZCj1hKShaIw80vY/hjJ3KZMODY9t91SOwmrVaGrCUeF1tXkuhEgwxfkekPWLxYYc688gLb6oc3FBm//lucNGrOWBXw6yhm1dUcndHXXpafjJslKAHwJN7vI5q69SxvS6SlJUzh/RFWYLnbZ2Qi35ixkU12FZiYVzxDl2i7XbhVoT5mit6VTU7Wh4BMSYuorAv937sF9Y6asE7sQUYHC2C2qjp8S5uFXV/IrhCPbJyWVc4ymPm58Eh6SmItC9zHDviFF9aFoZMK/lfK3Dqumu3T9x6ZYcxulpjNsM0/yv9OiiWbw33PnNb74A9uwrxZHB3XexXiigBUlUzO4lJQ5Oe1rhpPfPPRVyxaeZ8/cPmoJjCuwoiG0YtUeNH5PkHi05O0/hLR9PftDY8oMyzOBErSqjMjZ6OTkFFgk3dI9rHU72C1KL9Jh5uHwEQchBmg=
243 243 f14864fffdcab725d9eac6d4f4c07be05a35f59a 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmQc3KUZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVnYZDACh1Bcj8Yu3t8pO22SKWJnz8Ndw9Hvw+ifLaRxFUxKtqUYvy3CIl2qt8k7V13M25qw0061SKgcvNdjtkOhdmtFHNAbqryy0nK9oSZ2GfndmJfMxm9ixF/CcHrx+MmsklEz2woApViHW5PrmgKvZNsStQ5NM457Yx3B4nsT9b8t03NzdNiZRM+RZOkZ+4OdSbiB6hYuTqEFIi2YM+gfVM5Z7H8sEFBkUCtuwUjFGaWThZGGhAcqD5E7p/Lkjv4e4tzyHOzHDgdd+OCAkcbib6/E3Q1MlQ1x7CKpJ190T8R35CzAIMBVoTSI+Ov7OKw1OfGdeCvMVJsKUvqY3zrPawmJB6pG7GoVPEu5pU65H51U3Plq3GhsekUrKWY/BSHV9FOqpKZdnxOAllfWcjLYpbC/fM3l8uuQVcPAs89GvWKnDuE/NWCDYzDAYE++s/H4tP3Chv6yQbPSv/lbccst7OfLLDtXgRHIyEWLo392X3mWzhrkNtfJkBdi39uH9Aoh7pN0=
244 244 83ea6ce48b4fd09fb79c4e34cc5750c805699a53 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmQ3860ZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVk3gDACIIcQxKfis/r5UNj7SqyFhQxUCo8Njp7zdLFv3CSWFdFiOpQONI7Byt9KjwedUkUK9tqdb03V7W32ZSBTrNLM11uHY9E5Aknjoza4m+aIGbamEVRWIIHXjUZEMKS9QcY8ElbDvvPu/xdZjyTEjNNiuByUpPUcJXVzpKrHm8Wy3GWDliYBuu68mzFIX3JnZKscdK4EjCAfDysSwwfLeBMpd0Rk+SgwjDwyPWAAyU3yDPNmlUn8qTGHjXxU3vsHCXpoJWkfKmQ9n++23WEpM9vC8zx2TIy70+gFUvKG77+Ucv+djQxHRv0L6L5qUSBJukD3R3nml1xu6pUeioBHepRmTUWgPbHa/gQ+J2Pw+rPCK51x0EeT0SJjxUR2mmMLbk8N2efM35lEjF/sNxotTq17Sv9bjwXhue6BURxpQDEyOuSaS0IlF56ndXtE/4FX3H6zgU1+3jw5iBWajr1E04QjPlSOJO7nIKYM9Jq3VpHR7MiFwfT46pJEfw9pNgZX2b8o=
245 245 f952be90b0514a576dcc8bbe758ce3847faba9bb 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmQ+ZaoZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVuDOC/90SQ3UjXmByAaT5qr4bd3sVGt12lXlaKdyDxY0JMSKyHMUnb4YltHzNFxiUku10aRsRvJt5denTGeaOvAYbbXE7nbZJuyLD9rvfFTCe6EVx7kymCBwSbobKMzD79QHAFU7xu036gs7rmwyc++F4JF4IOrT4bjSYY5/8g0uLAHUexnn49QfQ5OYr325qShDFLjUZ7aH0yxA/gEr2MfXQmbIEc0eJJQXD1EhDkpSJFNIKzwWMOT1AhFk8kTlDqqbPnW7sDxTW+v/gGjAFYLHi8GMLEyrBQdEqytN7Pl9XOPXt/8RaDfIzYfl0OHxh2l1Y1MuH/PHrWO4PBPsr82QI2mxufYKuujpFMPr4PxXXl2g31OKhI8jJj+bHr62kGIOJCxZ8EPPGKXPGyoOuIVa0MeHmXxjb9kkj0SALjlaUvZrSENzRTsQXDNHQa+iDaITKLmItvLsaTEz9DJzGmI20shtJYcx4lqHsTgtMZfOtR5tmUknAFUUBZfUwvwULD4LmNI=
246 246 fc445f8abcf90b33db7c463816a1b3560681767f 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmRTok8ZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVpZ5DACBv33k//ovzSbyH5/q+Xhk3TqNRY8IDOjoEhvDyu0bJHsvygOGXLUtHpQPth1RA4/c+AVNJrUeFvT02sLqqP2d9oSA9HEAYpOuzwgr1A+1o+Q2GyfD4cElP6KfiEe8oyFVOB0rfBgWNei1C0nnrhChQr5dOPR63uAFhHzkEsgsTFS7ONxZ1DHbe7gRV8OMMf1MatAtRzRexQJCqyNv7WodQdrKtjHqPKtlWl20dbwTHhzeiZbtjiTe0CVXVsOqnA1DQkO/IaiKQrn3zWdGY5ABbqQ1K0ceLcej4NFOeLo9ZrShndU3BuFUa9Dq9bnPYOI9wMqGoDh/GdTZkZEzBy5PTokY3AJHblbub49pi8YTenFcPdtd/v71AaNi3TKa45ZNhYVkPmRETYweHkLs3CIrSyeiBwU4RGuQZVD/GujAQB5yhk0w+LPMzBsHruD4vsgXwIraCzQIIJTjgyxKuAJGdGNUFYyxEpUkgz5G6MFrBKe8HO69y3Pm/qDNZ2maV8k=
247 247 da372c745e0f053bb7a64e74cccd15810d96341d 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmSB7WkZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVoy+C/4zwO+Wxc3wr0aEzjVqAss7FuGS5e66H+0T3WzVgKIRMqiiOmUmmiNf+XloXlX4TOwoh9j9GNEpoZfV6TSwFSqV0LALaVIRRwrkJBDhnqw4eNBZbK5aBWNa2/21dkHecxF4KG3ai9kLwy2mtHxkDIy8T2LPvdx8pfNcYT4PZ19x2itqZLouBJqiZYehsqeMLNF2vRqkq+rQ+D2sFGLljgPo0JlpkOZ4IL7S/cqTOBG1sQ6KJK+hAE1kF1lhvK796VhKKXVnWVgqJLyg7ZI6168gxeFv5cyCtb+FUXJJ/5SOkxaCKJf3mg3DIYi3G7xjwB5CfUGW8A2qexgEjXeV42Mu7/Mkmn/aeTdL0UcRK3oBVHJwqt/fJlGFqVWt4/9g9KW5mJvTDQYBo/zjLyvKFEbnSLzhEP+9SvthCrtX0UYkKxOGi2M2Z7e9wgBB0gY8a36kA739lkNu6r3vH/FVh0aPTMWukLToELS90WgfViNr16lDnCeDjMgg97OKxWdOW6U=
248 248 271a4ab29605ffa0bae5d3208eaa21a95427ff92 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmSUEeMZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVlJnC/98qGmpi0gHbsoCPfoxgV2uSE4XAXZXPvbHqKAVUVJbkQoS0L2jighUArPZsduRjD+nSf/jO951/DmnxIwXfF5qA2dP1eBnjSmXS3xslmqD7nUw+pP8mKUQvXky+AbiL5onWw4gRtsqTZg4DYnPMeaE/eIUy/j60kXsf6gaDkQSAF/+9vB5UcVI1z7gKY/nE5pGW6cS9kPd/BEg2icficaOHXcetQFi53Gcy5kLEaYc9f8RUrvc0Z9jDkZSlmTHfTLOY+1hlFZ2FRAvL1Ikh7Ks+85LWuqs1ZYIdB6ucudhLW1dGd/ZyD0iU82e0XrU/tm6oDBdeSFOy1AAXN5pern18VcPeaT/zGgN7DG1LW9jISbYFzLwvHwzTMKSVgq4HSfeTHiSKoWp0qAbcFHUYfC4L1Heqd/UfzVN/1/9eSj69Hbjff8+E6OOF15Ky2gtr8PSyP7WIu9rTueUUoWIMG99btq5OYvEbmWgHuHIcJBUEJOalvhrZePbTW3v22Eh45M=
249 249 bb42988c7e156931b0ff1e93732b98173ebbcb7f 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmSUPXUZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVvYTC/wP7f8RITHgCO8djHUsnRs60P2mlEJQ71TDA3dqgdBIr3tWMELfcZMZnOTtaw4eqKemLauxa69MHgj2y++VMnfJx1pW5G61G8ZFfLjwFvAqqmXnnT6RVjo7sPuKSkL28C9NWwrLIRk5SGWK52W56Slz0bW1yhJBOV8BEIgZM5ucs4froYTxgAP8xprbLyPIroAJEtPNU3mkOXuPPGQ/zGO9czJ9sfYHU3bPmskf3YLqWAKQdCmxQgv44QluRVWoek6caIUA04mJwwlBdCCPZnr8hvaptZeYv2hhPw7CzDfWwMkyBYzmoUAZIgu/eYPtDRtxeIlEYC2WP+DQy5R+kK+X/nfxe8kVL9USow5MZZ54tmPbrwUO/dkWOWiK5NyqYnFjBDaq24XKUoPC7p7mGkfzQPNCiKcQO3qcUtiIb7tzz0olWemD2z86ws8kaEK8GSOgpBK71KOzrPZt8B01Nb+seahftCN5HxALAJSM6VRxYJFgYMFFxid+zNwEstuNipo=
250 250 3ffc7209bbae5804a53084c9dc2d41139e88c867 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmSmyeIZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVn/CC/9l24Feazay+kN3rOCvRqOOQO0Xx47+Lx5xaC4mgSAs7fkefY0ru4gnKRQkYskIksUzJX0P6aGrS3RH3y+DzxPhha75Ufq1abD8c1NJ2mUzW/DnoEI9zKnprkUdet8cwwLzNDhuWqjG6DY1ETwWpYVHo01Yv5FjDOdbMfPJ92yyF2AxLNTjkHNNfn0dpJE+/Sz8WjKsjPtTB432ZhvmfDsWgW+fTOlVATEyRqP4vNMWxPKPYif7KvH5U8vPAvX4i5Ox+csNeFQTUGV6KfgpAjXuJc2AEGr644KfpiMIyvWvEDewPAoGR+BUBz8jjT5KqBxc/9RJ8wEruCZIEKXxMAta+G+wWJyXZgKU1UN4x6mQT4RscnvX/1jMZx7zzqTSq2fe0Ddw/ta2aZtbp0JLJ5NmqiFLaKdDDdTAAONn+dBLQMO0+NNm9bOOafqI8edsOw3WoXmOVxbpdBrzIP5x18qNRU9gcTxxPqN5yy97dhsKyRpdbMVruxp1NUWeTBywARI=
251 787af4e0e8b787e1b77a8059926b123730a4cd01 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmTQs9cZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVgKODACTVTvl32CwG8xodKC9BPHmdzU4IXJb9fweHfMjsnx5rxPrOMQ8/PL1X7spR5qD7uTvvz+3ceML0WFqSBcF8R/Tt3dV4bacpKLbFTvnOToExmuWzhZnOzL6FVIOkHsSL5u2geA0o6c/y7vxglCwUZmSCAgZLxPC8CPv1PMQ1wRjHPygaZR2dDtxktFrfrZmU7uY61rY3VBG7Z5GhT9JF0biS7/K5nN687yybj76Gn7Kw/TMDK4GKCboVydRBp0poxSp8I+fty2N0Trpsw47CQp6HcBHq1FPrIv587+7X9VgajkC/+ECWBwdlo1pA5GlhJP6/4j8jvcAteFp0HS24z++NT0AYUB4UBgCCmg5hdDeF8j6A7SLcpf+YfbIwiGPkSRfIBeT+bhBJVDV4gbhoE02BMymU42OmaMqC1W8YI32WhugAfZJNPmJzdeNO7PNjTPNnjSjFzAHuQVS5Z9SvfctvJG532hygJkR+bCeaHzwAebyXkopRLm4PUpWcazoEes=
@@ -1,266 +1,267 b''
1 1 d40cc5aacc31ed673d9b5b24f98bee78c283062c 0.4f
2 2 1c590d34bf61e2ea12c71738e5a746cd74586157 0.4e
3 3 7eca4cfa8aad5fce9a04f7d8acadcd0452e2f34e 0.4d
4 4 b4d0c3786ad3e47beacf8412157326a32b6d25a4 0.4c
5 5 f40273b0ad7b3a6d3012fd37736d0611f41ecf54 0.5
6 6 0a28dfe59f8fab54a5118c5be4f40da34a53cdb7 0.5b
7 7 12e0fdbc57a0be78f0e817fd1d170a3615cd35da 0.6
8 8 4ccf3de52989b14c3d84e1097f59e39a992e00bd 0.6b
9 9 eac9c8efcd9bd8244e72fb6821f769f450457a32 0.6c
10 10 979c049974485125e1f9357f6bbe9c1b548a64c3 0.7
11 11 3a56574f329a368d645853e0f9e09472aee62349 0.8
12 12 6a03cff2b0f5d30281e6addefe96b993582f2eac 0.8.1
13 13 35fb62a3a673d5322f6274a44ba6456e5e4b3b37 0.9
14 14 2be3001847cb18a23c403439d9e7d0ace30804e9 0.9.1
15 15 36a957364b1b89c150f2d0e60a99befe0ee08bd3 0.9.2
16 16 27230c29bfec36d5540fbe1c976810aefecfd1d2 0.9.3
17 17 fb4b6d5fe100b0886f8bc3d6731ec0e5ed5c4694 0.9.4
18 18 23889160905a1b09fffe1c07378e9fc1827606eb 0.9.5
19 19 bae2e9c838e90a393bae3973a7850280413e091a 1.0
20 20 d5cbbe2c49cee22a9fbeb9ea41daa0ac4e26b846 1.0.1
21 21 d2375bbee6d47e62ba8e415c86e83a465dc4dce9 1.0.2
22 22 2a67430f92f15ea5159c26b09ec4839a0c549a26 1.1
23 23 3773e510d433969e277b1863c317b674cbee2065 1.1.1
24 24 11a4eb81fb4f4742451591489e2797dc47903277 1.1.2
25 25 11efa41037e280d08cfb07c09ad485df30fb0ea8 1.2
26 26 02981000012e3adf40c4849bd7b3d5618f9ce82d 1.2.1
27 27 196d40e7c885fa6e95f89134809b3ec7bdbca34b 1.3
28 28 3ef6c14a1e8e83a31226f5881b7fe6095bbfa6f6 1.3.1
29 29 31ec469f9b556f11819937cf68ee53f2be927ebf 1.4
30 30 439d7ea6fe3aa4ab9ec274a68846779153789de9 1.4.1
31 31 296a0b14a68621f6990c54fdba0083f6f20935bf 1.4.2
32 32 4aa619c4c2c09907034d9824ebb1dd0e878206eb 1.4.3
33 33 ff2704a8ded37fbebd8b6eb5ec733731d725da8a 1.5
34 34 2b01dab594167bc0dd33331dbaa6dca3dca1b3aa 1.5.1
35 35 39f725929f0c48c5fb3b90c071fc3066012456ca 1.5.2
36 36 fdcf80f26604f233dc4d8f0a5ef9d7470e317e8a 1.5.3
37 37 24fe2629c6fd0c74c90bd066e77387c2b02e8437 1.5.4
38 38 f786fc4b8764cd2a5526d259cf2f94d8a66924d9 1.6
39 39 bf1774d95bde614af3956d92b20e2a0c68c5fec7 1.6.1
40 40 c00f03a4982e467fb6b6bd45908767db6df4771d 1.6.2
41 41 ff5cec76b1c5b6be9c3bb923aae8c3c6d079d6b9 1.6.3
42 42 93d8bff78c96fe7e33237b257558ee97290048a4 1.6.4
43 43 333421b9e0f96c7bc788e5667c146a58a9440a55 1.7
44 44 4438875ec01bd0fc32be92b0872eb6daeed4d44f 1.7.1
45 45 6aff4f144ad356311318b0011df0bb21f2c97429 1.7.2
46 46 e3bf16703e2601de99e563cdb3a5d50b64e6d320 1.7.3
47 47 a6c855c32ea081da3c3b8ff628f1847ff271482f 1.7.4
48 48 2b2155623ee2559caf288fd333f30475966c4525 1.7.5
49 49 2616325766e3504c8ae7c84bd15ee610901fe91d 1.8
50 50 aa1f3be38ab127280761889d2dca906ca465b5f4 1.8.1
51 51 b032bec2c0a651ca0ddecb65714bfe6770f67d70 1.8.2
52 52 3cb1e95676ad089596bd81d0937cad37d6e3b7fb 1.8.3
53 53 733af5d9f6b22387913e1d11350fb8cb7c1487dd 1.8.4
54 54 de9eb6b1da4fc522b1cab16d86ca166204c24f25 1.9
55 55 4a43e23b8c55b4566b8200bf69fe2158485a2634 1.9.1
56 56 d629f1e89021103f1753addcef6b310e4435b184 1.9.2
57 57 351a9292e430e35766c552066ed3e87c557b803b 1.9.3
58 58 384082750f2c51dc917d85a7145748330fa6ef4d 2.0-rc
59 59 41453d55b481ddfcc1dacb445179649e24ca861d 2.0
60 60 195dbd1cef0c2f9f8bcf4ea303238105f716bda3 2.0.1
61 61 6344043924497cd06d781d9014c66802285072e4 2.0.2
62 62 db33555eafeaf9df1e18950e29439eaa706d399b 2.1-rc
63 63 2aa5b51f310fb3befd26bed99c02267f5c12c734 2.1
64 64 53e2cd303ecf8ca7c7eeebd785c34e5ed6b0f4a4 2.1.1
65 65 b9bd95e61b49c221c4cca24e6da7c946fc02f992 2.1.2
66 66 d9e2f09d5488c395ae9ddbb320ceacd24757e055 2.2-rc
67 67 00182b3d087909e3c3ae44761efecdde8f319ef3 2.2
68 68 5983de86462c5a9f42a3ad0f5e90ce5b1d221d25 2.2.1
69 69 85a358df5bbbe404ca25730c9c459b34263441dc 2.2.2
70 70 b013baa3898e117959984fc64c29d8c784d2f28b 2.2.3
71 71 a06e2681dd1786e2354d84a5fa9c1c88dd4fa3e0 2.3-rc
72 72 7f5094bb3f423fc799e471aac2aee81a7ce57a0b 2.3
73 73 072209ae4ddb654eb2d5fd35bff358c738414432 2.3.1
74 74 b3f0f9a39c4e1d0250048cd803ab03542d6f140a 2.3.2
75 75 d118a4f4fd16d9b558ec3f3e87bfee772861d2b7 2.4-rc
76 76 195ad823b5d58c68903a6153a25e3fb4ed25239d 2.4
77 77 0c10cf8191469e7c3c8844922e17e71a176cb7cb 2.4.1
78 78 a4765077b65e6ae29ba42bab7834717b5072d5ba 2.4.2
79 79 f5fbe15ca7449f2c9a3cf817c86d0ae68b307214 2.5-rc
80 80 a6088c05e43a8aee0472ca3a4f6f8d7dd914ebbf 2.5
81 81 7511d4df752e61fe7ae4f3682e0a0008573b0402 2.5.1
82 82 5b7175377babacce80a6c1e12366d8032a6d4340 2.5.2
83 83 50c922c1b5145dab8baefefb0437d363b6a6c21c 2.5.3
84 84 8a7bd2dccd44ed571afe7424cd7f95594f27c092 2.5.4
85 85 292cd385856d98bacb2c3086f8897bc660c2beea 2.6-rc
86 86 23f785b38af38d2fca6b8f3db56b8007a84cd73a 2.6
87 87 ddc7a6be20212d18f3e27d9d7e6f079a66d96f21 2.6.1
88 88 cceaf7af4c9e9e6fa2dbfdcfe9856c5da69c4ffd 2.6.2
89 89 009794acc6e37a650f0fae37872e733382ac1c0c 2.6.3
90 90 f0d7721d7322dcfb5af33599c2543f27335334bb 2.7-rc
91 91 f37b5a17e6a0ee17afde2cdde5393dd74715fb58 2.7
92 92 335a558f81dc73afeab4d7be63617392b130117f 2.7.1
93 93 e7fa36d2ad3a7944a52dca126458d6f482db3524 2.7.2
94 94 1596f2d8f2421314b1ddead8f7d0c91009358994 2.8-rc
95 95 d825e4025e39d1c39db943cdc89818abd0a87c27 2.8
96 96 209e04a06467e2969c0cc6501335be0406d46ef0 2.8.1
97 97 ca387377df7a3a67dbb90b6336b781cdadc3ef41 2.8.2
98 98 8862469e16f9236208581b20de5f96bd13cc039d 2.9-rc
99 99 3cec5134e9c4bceab6a00c60f52a4f80677a78f2 2.9
100 100 b96cb15ec9e04d8ac5ee08b34fcbbe4200588965 2.9.1
101 101 3f83fc5cfe715d292069ee8417c83804f6c6c1e4 2.9.2
102 102 564f55b251224f16508dd1311452db7780dafe2b 3.0-rc
103 103 2195ac506c6ababe86985b932f4948837c0891b5 3.0
104 104 269c80ee5b3cb3684fa8edc61501b3506d02eb10 3.0.1
105 105 2d8cd3d0e83c7336c0cb45a9f88638363f993848 3.0.2
106 106 6c36dc6cd61a0e1b563f1d51e55bdf4dacf12162 3.1-rc
107 107 3178e49892020336491cdc6945885c4de26ffa8b 3.1
108 108 5dc91146f35369949ea56b40172308158b59063a 3.1.1
109 109 f768c888aaa68d12dd7f509dcc7f01c9584357d0 3.1.2
110 110 7f8d16af8cae246fa5a48e723d48d58b015aed94 3.2-rc
111 111 ced632394371a36953ce4d394f86278ae51a2aae 3.2
112 112 643c58303fb0ec020907af28b9e486be299ba043 3.2.1
113 113 902554884335e5ca3661d63be9978eb4aec3f68a 3.2.2
114 114 6dad422ecc5adb63d9fa649eeb8e05a5f9bc4900 3.2.3
115 115 1265a3a71d75396f5d4cf6935ae7d9ba5407a547 3.2.4
116 116 db8e3f7948b1fdeb9ad12d448fc3525759908b9f 3.3-rc
117 117 fbdd5195528fae4f41feebc1838215c110b25d6a 3.3
118 118 5b4ed033390bf6e2879c8f5c28c84e1ee3b87231 3.3.1
119 119 07a92bbd02e5e3a625e0820389b47786b02b2cea 3.3.2
120 120 2e2e9a0750f91a6fe0ad88e4de34f8efefdcab08 3.3.3
121 121 e89f909edffad558b56f4affa8239e4832f88de0 3.4-rc
122 122 8cc6036bca532e06681c5a8fa37efaa812de67b5 3.4
123 123 ed18f4acf435a2824c6f49fba40f42b9df5da7ad 3.4.1
124 124 540cd0ddac49c1125b2e013aa2ff18ecbd4dd954 3.4.2
125 125 96a38d44ba093bd1d1ecfd34119e94056030278b 3.5-rc
126 126 21aa1c313b05b1a85f8ffa1120d51579ddf6bf24 3.5
127 127 1a45e49a6bed023deb229102a8903234d18054d3 3.5.1
128 128 9a466b9f9792e3ad7ae3fc6c43c3ff2e136b718d 3.5.2
129 129 b66e3ca0b90c3095ea28dfd39aa24247bebf5c20 3.6-rc
130 130 47dd34f2e7272be9e3b2a5a83cd0d20be44293f4 3.6
131 131 1aa5083cbebbe7575c88f3402ab377539b484897 3.6.1
132 132 2d437a0f3355834a9485bbbeb30a52a052c98f19 3.6.2
133 133 ea389970c08449440587712117f178d33bab3f1e 3.6.3
134 134 158bdc8965720ca4061f8f8d806563cfc7cdb62e 3.7-rc
135 135 2408645de650d8a29a6ce9e7dce601d8dd0d1474 3.7
136 136 b698abf971e7377d9b7ec7fc8c52df45255b0329 3.7.1
137 137 d493d64757eb45ada99fcb3693e479a51b7782da 3.7.2
138 138 ae279d4a19e9683214cbd1fe8298cf0b50571432 3.7.3
139 139 740156eedf2c450aee58b1a90b0e826f47c5da64 3.8-rc
140 140 f85de28eae32e7d3064b1a1321309071bbaaa069 3.8
141 141 a56296f55a5e1038ea5016dace2076b693c28a56 3.8.1
142 142 aaabed77791a75968a12b8c43ad263631a23ee81 3.8.2
143 143 a9764ab80e11bcf6a37255db7dd079011f767c6c 3.8.3
144 144 26a5d605b8683a292bb89aea11f37a81b06ac016 3.8.4
145 145 519bb4f9d3a47a6e83c2b414d58811ed38f503c2 3.9-rc
146 146 299546f84e68dbb9bd026f0f3a974ce4bdb93686 3.9
147 147 ccd436f7db6d5d7b9af89715179b911d031d44f1 3.9.1
148 148 149433e68974eb5c63ccb03f794d8b57339a80c4 3.9.2
149 149 438173c415874f6ac653efc1099dec9c9150e90f 4.0-rc
150 150 eab27446995210c334c3d06f1a659e3b9b5da769 4.0
151 151 b3b1ae98f6a0e14c1e1ba806a6c18e193b6dae5c 4.0.1
152 152 e69874dc1f4e142746ff3df91e678a09c6fc208c 4.0.2
153 153 a1dd2c0c479e0550040542e392e87bc91262517e 4.1-rc
154 154 e1526da1e6d84e03146151c9b6e6950fe9a83d7d 4.1
155 155 25703b624d27e3917d978af56d6ad59331e0464a 4.1.1
156 156 ed5b25874d998ababb181a939dd37a16ea644435 4.1.2
157 157 77eaf9539499a1b8be259ffe7ada787d07857f80 4.1.3
158 158 616e788321cc4ae9975b7f0c54c849f36d82182b 4.2-rc
159 159 bb96d4a497432722623ae60d9bc734a1e360179e 4.2
160 160 c850f0ed54c1d42f9aa079ad528f8127e5775217 4.2.1
161 161 26c49ed51a698ec016d2b4c6b44ca3c3f73cc788 4.2.2
162 162 857876ebaed4e315f63157bd157d6ce553c7ab73 4.3-rc
163 163 5544af8622863796a0027566f6b646e10d522c4c 4.3
164 164 943c91326b23954e6e1c6960d0239511f9530258 4.2.3
165 165 3fee7f7d2da04226914c2258cc2884dc27384fd7 4.3.1
166 166 920977f72c7b70acfdaf56ab35360584d7845827 4.3.2
167 167 2f427b57bf9019c6dc3750baa539dc22c1be50f6 4.3.3
168 168 1e2454b60e5936f5e77498cab2648db469504487 4.4-rc
169 169 0ccb43d4cf01d013ae05917ec4f305509f851b2d 4.4
170 170 cabc840ffdee8a72f3689fb77dd74d04fdc2bc04 4.4.1
171 171 a92b9f8e11ba330614cdfd6af0e03b15c1ff3797 4.4.2
172 172 27b6df1b5adbdf647cf5c6675b40575e1b197c60 4.5-rc
173 173 d334afc585e29577f271c5eda03378736a16ca6b 4.5
174 174 369aadf7a3264b03c8b09efce715bc41e6ab4a9b 4.5.1
175 175 8bba684efde7f45add05f737952093bb2aa07155 4.5.2
176 176 7de7bd407251af2bc98e5b809c8598ee95830daf 4.5.3
177 177 ed5448edcbfa747b9154099e18630e49024fd47b 4.6rc0
178 178 1ec874717d8a93b19e0d50628443e0ee5efab3a9 4.6rc1
179 179 6614cac550aea66d19c601e45efd1b7bd08d7c40 4.6
180 180 9c5ced5276d6e7d54f7c3dadf5247b7ee98ec79c 4.6.1
181 181 0b63a6743010dfdbf8a8154186e119949bdaa1cc 4.6.2
182 182 e90130af47ce8dd53a3109aed9d15876b3e7dee8 4.7rc0
183 183 33ac6a72308a215e6086fbced347ec10aa963b0a 4.7
184 184 ede3bf31fe63677fdf5bd8db687977d4e3d792ed 4.7.1
185 185 5405cb1a79010ac50c58cd84e6f50c4556bf2a4c 4.7.2
186 186 956ec6f1320df26f3133ec40f3de866ea0695fd7 4.8rc0
187 187 a91a2837150bdcb27ae76b3646e6c93cd6a15904 4.8
188 188 1c8c54cf97256f4468da2eb4dbee24f7f3888e71 4.8.1
189 189 197f092b2cd9691e2a55d198f717b231af9be6f9 4.8.2
190 190 593718ff5844cad7a27ee3eb5adad89ac8550949 4.9rc0
191 191 83377b4b4ae0e9a6b8e579f7b0a693b8cf5c3b10 4.9
192 192 4ea21df312ec7159c5b3633096b6ecf68750b0dd 4.9.1
193 193 4a8d9ed864754837a185a642170cde24392f9abf 5.0rc0
194 194 07e479ef7c9639be0029f00e6a722b96dcc05fee 5.0
195 195 c3484ddbdb9621256d597ed86b90d229c59c2af9 5.0.1
196 196 97ada9b8d51bef24c5cb4cdca4243f0db694ab6e 5.0.2
197 197 e386b5f4f8360dbb43a576dd9b1368e386fefa5b 5.1rc0
198 198 e91930d712e8507d1bc1b2dffd96c83edc4cbed3 5.1
199 199 a4e32fd539ab41489a51b2aa88bda9a73b839562 5.1.1
200 200 181e52f2b62f4768aa0d988936c929dc7c4a41a0 5.1.2
201 201 59338f9561099de77c684c00f76507f11e46ebe8 5.2rc0
202 202 ca3dca416f8d5863ca6f5a4a6a6bb835dcd5feeb 5.2
203 203 a50fecefa691c9b72a99e49aa6fe9dd13943c2bf 5.2.1
204 204 b4c82b70418022e67cc0e69b1aa3c3aa43aa1d29 5.2.2
205 205 84a0102c05c7852c8215ef6cf21d809927586b69 5.3rc0
206 206 e4344e463c0c888a2f437b78b5982ecdf3f6650a 5.3rc1
207 207 7f5410dfc8a64bb587d19637deb95d378fd1eb5c 5.3
208 208 6d121acbb82e65fe4dd3c2318a1b61981b958492 5.3.1
209 209 8fca7e8449a847e3cf1054f2c07b51237699fad3 5.3.2
210 210 26ce8e7515036d3431a03aaeb7bc72dd96cb1112 5.4rc0
211 211 cf3e07d7648a4371ce584d15dd692e7a6845792f 5.4
212 212 065704cbdbdbb05dcd6bb814eb9bbdd982211b28 5.4.1
213 213 0ea9c86fac8974cd74dc12ea681c8986eb6da6c4 5.4.2
214 214 28163c5de797e5416f9b588940f4608269b4d50a 5.5rc0
215 215 7fc3c5fbc65f6fe85d70ea63923b8767dda4f2e0 5.5
216 216 f62bb5d07848ca598aa860a517394130b61bf2ee 5.5.1
217 217 07731064ac41dacdf0ec869ebd05c2e848c14fbf 5.5.2
218 218 0e06a7ab9e0d5c65af4e511aee1e0342998799df 5.6rc0
219 219 18c17d63fdabd009e70bf994e5efb7db422f4f7f 5.6
220 220 1d5189a57405ceca5aa244052c9f948977f4699b 5.6.1
221 221 9da65e3cf3706ff41e08b311381c588440c27baf 5.7rc0
222 222 0e2e7300f4302b02412b0b734717697049494c4c 5.7
223 223 d5d9177c0045d206db575bae6daa98e2cb2fe5bc 5.7.1
224 224 f67b8946bb1b6cfa8328dbf8d6a9128b69ccdcb4 5.8rc0
225 225 8d2b62d716b095507effaa8d56f87cd27ba659ab 5.8rc1
226 226 067f2c53fb24506c9e9fb4639871b13b19a85f8a 5.8
227 227 411dc27fd9fd076d6a031a08fcaace659afe2fe3 5.8.1
228 228 d7515d29761d5ada7d9c765f517db67db75dea9a 5.9rc0
229 229 2813d406b03607cdb8c06cb04c44efcc9a79d9a2 5.9rc1
230 230 53221078e0de65d1a821ce5311dec45a7a978301 5.9
231 231 86a60679cf619e14cee9442f865fcf31b142cb9f 5.9.1
232 232 750920b18aaaddd654756be40dec59d90f2643be 5.9.2
233 233 6ee0244fc1cf889ae543d2ce0ec45201ae0be6e1 5.9.3
234 234 a44bb185f6bdbecc754996d8386722e2f0123b0a 6.0rc0
235 235 5d08b289e2e526259d7d5ea32b70fe76d5b327d7 6.0
236 236 799fdf4cca80cb9ae40537a90995e6bd163ebc0b 6.0.1
237 237 75676122c2bf7594ac732b7388db4c74c648b365 6.0.2
238 238 dcec16e799ddb6d33fcd11b04af530250a417a58 6.0.3
239 239 c00d3ce4e94bb0ee8d809e25e1dcb2a5fab84e2c 6.1rc0
240 240 d4486810a1795fba9521449b8885ced034f3a6dd 6.1
241 241 5bd6bcd31dd1ebb63b8914b00064f96297267af7 6.1.1
242 242 0ddd5e1f5f67438af85d12e4ce6c39021dde9916 6.1.2
243 243 6b10151b962108f65bfa12b3918b1021ca334f73 6.1.3
244 244 0cc5f74ff7f0f4ac2427096bddbe102dbc2453ae 6.1.4
245 245 288de6f5d724bba7bf1669e2838f196962bb7528 6.2rc0
246 246 094a5fa3cf52f936e0de3f1e507c818bee5ece6b 6.2
247 247 f69bffd00abe3a1b94d1032eb2c92e611d16a192 6.2.1
248 248 b5c8524827d20fe2e0ca8fb1234a0fe35a1a36c7 6.2.2
249 249 dbdee8ac3e3fcdda1fa55b90c0a235125b7f8e6f 6.2.3
250 250 a3356ab610fc50000cf0ba55c424a4d96da11db7 6.3rc0
251 251 04f1dba53c961dfdb875c8469adc96fa999cfbed 6.3.0
252 252 04f1dba53c961dfdb875c8469adc96fa999cfbed 6.3
253 253 04f1dba53c961dfdb875c8469adc96fa999cfbed 6.3.0
254 254 0000000000000000000000000000000000000000 6.3.0
255 255 c890d8b8bc59b18e5febf60caada629df5356ee2 6.3.1
256 256 59466b13a3ae0e29a5d4f485393e516cfbb057d0 6.3.2
257 257 8830004967ad865ead89c28a410405a6e71e0796 6.3.3
258 258 05de4896508e8ec387b33eb30d8aab78d1c8e9e4 6.4rc0
259 259 f14864fffdcab725d9eac6d4f4c07be05a35f59a 6.4
260 260 83ea6ce48b4fd09fb79c4e34cc5750c805699a53 6.4.1
261 261 f952be90b0514a576dcc8bbe758ce3847faba9bb 6.4.2
262 262 fc445f8abcf90b33db7c463816a1b3560681767f 6.4.3
263 263 da372c745e0f053bb7a64e74cccd15810d96341d 6.4.4
264 264 271a4ab29605ffa0bae5d3208eaa21a95427ff92 6.4.5
265 265 bb42988c7e156931b0ff1e93732b98173ebbcb7f 6.5rc0
266 266 3ffc7209bbae5804a53084c9dc2d41139e88c867 6.5
267 787af4e0e8b787e1b77a8059926b123730a4cd01 6.5.1
@@ -1,4449 +1,4449 b''
1 1 # perf.py - performance test routines
2 2 '''helper extension to measure performance
3 3
4 4 Configurations
5 5 ==============
6 6
7 7 ``perf``
8 8 --------
9 9
10 10 ``all-timing``
11 11 When set, additional statistics will be reported for each benchmark: best,
12 12 worst, median average. If not set only the best timing is reported
13 13 (default: off).
14 14
15 15 ``presleep``
16 16 number of second to wait before any group of runs (default: 1)
17 17
18 18 ``pre-run``
19 19 number of run to perform before starting measurement.
20 20
21 21 ``profile-benchmark``
22 22 Enable profiling for the benchmarked section.
23 23 (The first iteration is benchmarked)
24 24
25 25 ``run-limits``
26 26 Control the number of runs each benchmark will perform. The option value
27 27 should be a list of `<time>-<numberofrun>` pairs. After each run the
28 28 conditions are considered in order with the following logic:
29 29
30 30 If benchmark has been running for <time> seconds, and we have performed
31 31 <numberofrun> iterations, stop the benchmark,
32 32
33 33 The default value is: `3.0-100, 10.0-3`
34 34
35 35 ``stub``
36 36 When set, benchmarks will only be run once, useful for testing
37 37 (default: off)
38 38 '''
39 39
40 40 # "historical portability" policy of perf.py:
41 41 #
42 42 # We have to do:
43 43 # - make perf.py "loadable" with as wide Mercurial version as possible
44 44 # This doesn't mean that perf commands work correctly with that Mercurial.
45 45 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
46 46 # - make historical perf command work correctly with as wide Mercurial
47 47 # version as possible
48 48 #
49 49 # We have to do, if possible with reasonable cost:
50 50 # - make recent perf command for historical feature work correctly
51 51 # with early Mercurial
52 52 #
53 53 # We don't have to do:
54 54 # - make perf command for recent feature work correctly with early
55 55 # Mercurial
56 56
57 57 import contextlib
58 58 import functools
59 59 import gc
60 60 import os
61 61 import random
62 62 import shutil
63 63 import struct
64 64 import sys
65 65 import tempfile
66 66 import threading
67 67 import time
68 68
69 69 import mercurial.revlog
70 70 from mercurial import (
71 71 changegroup,
72 72 cmdutil,
73 73 commands,
74 74 copies,
75 75 error,
76 76 extensions,
77 77 hg,
78 78 mdiff,
79 79 merge,
80 80 util,
81 81 )
82 82
83 83 # for "historical portability":
84 84 # try to import modules separately (in dict order), and ignore
85 85 # failure, because these aren't available with early Mercurial
86 86 try:
87 87 from mercurial import branchmap # since 2.5 (or bcee63733aad)
88 88 except ImportError:
89 89 pass
90 90 try:
91 91 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
92 92 except ImportError:
93 93 pass
94 94 try:
95 95 from mercurial import registrar # since 3.7 (or 37d50250b696)
96 96
97 97 dir(registrar) # forcibly load it
98 98 except ImportError:
99 99 registrar = None
100 100 try:
101 101 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
102 102 except ImportError:
103 103 pass
104 104 try:
105 105 from mercurial.utils import repoviewutil # since 5.0
106 106 except ImportError:
107 107 repoviewutil = None
108 108 try:
109 109 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
110 110 except ImportError:
111 111 pass
112 112 try:
113 113 from mercurial import setdiscovery # since 1.9 (or cb98fed52495)
114 114 except ImportError:
115 115 pass
116 116
117 117 try:
118 118 from mercurial import profiling
119 119 except ImportError:
120 120 profiling = None
121 121
122 122 try:
123 123 from mercurial.revlogutils import constants as revlog_constants
124 124
125 125 perf_rl_kind = (revlog_constants.KIND_OTHER, b'created-by-perf')
126 126
127 127 def revlog(opener, *args, **kwargs):
128 128 return mercurial.revlog.revlog(opener, perf_rl_kind, *args, **kwargs)
129 129
130 130
131 131 except (ImportError, AttributeError):
132 132 perf_rl_kind = None
133 133
134 134 def revlog(opener, *args, **kwargs):
135 135 return mercurial.revlog.revlog(opener, *args, **kwargs)
136 136
137 137
138 138 def identity(a):
139 139 return a
140 140
141 141
142 142 try:
143 143 from mercurial import pycompat
144 144
145 145 getargspec = pycompat.getargspec # added to module after 4.5
146 146 _byteskwargs = pycompat.byteskwargs # since 4.1 (or fbc3f73dc802)
147 147 _sysstr = pycompat.sysstr # since 4.0 (or 2219f4f82ede)
148 148 _bytestr = pycompat.bytestr # since 4.2 (or b70407bd84d5)
149 149 _xrange = pycompat.xrange # since 4.8 (or 7eba8f83129b)
150 150 fsencode = pycompat.fsencode # since 3.9 (or f4a5e0e86a7e)
151 151 if pycompat.ispy3:
152 152 _maxint = sys.maxsize # per py3 docs for replacing maxint
153 153 else:
154 154 _maxint = sys.maxint
155 155 except (NameError, ImportError, AttributeError):
156 156 import inspect
157 157
158 158 getargspec = inspect.getargspec
159 159 _byteskwargs = identity
160 160 _bytestr = str
161 161 fsencode = identity # no py3 support
162 162 _maxint = sys.maxint # no py3 support
163 163 _sysstr = lambda x: x # no py3 support
164 164 _xrange = xrange
165 165
166 166 try:
167 167 # 4.7+
168 168 queue = pycompat.queue.Queue
169 169 except (NameError, AttributeError, ImportError):
170 170 # <4.7.
171 171 try:
172 172 queue = pycompat.queue
173 173 except (NameError, AttributeError, ImportError):
174 174 import Queue as queue
175 175
176 176 try:
177 177 from mercurial import logcmdutil
178 178
179 179 makelogtemplater = logcmdutil.maketemplater
180 180 except (AttributeError, ImportError):
181 181 try:
182 182 makelogtemplater = cmdutil.makelogtemplater
183 183 except (AttributeError, ImportError):
184 184 makelogtemplater = None
185 185
186 186 # for "historical portability":
187 187 # define util.safehasattr forcibly, because util.safehasattr has been
188 188 # available since 1.9.3 (or 94b200a11cf7)
189 189 _undefined = object()
190 190
191 191
192 192 def safehasattr(thing, attr):
193 193 return getattr(thing, _sysstr(attr), _undefined) is not _undefined
194 194
195 195
196 196 setattr(util, 'safehasattr', safehasattr)
197 197
198 198 # for "historical portability":
199 199 # define util.timer forcibly, because util.timer has been available
200 200 # since ae5d60bb70c9
201 201 if safehasattr(time, 'perf_counter'):
202 202 util.timer = time.perf_counter
203 203 elif os.name == b'nt':
204 204 util.timer = time.clock
205 205 else:
206 206 util.timer = time.time
207 207
208 208 # for "historical portability":
209 209 # use locally defined empty option list, if formatteropts isn't
210 210 # available, because commands.formatteropts has been available since
211 211 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
212 212 # available since 2.2 (or ae5f92e154d3)
213 213 formatteropts = getattr(
214 214 cmdutil, "formatteropts", getattr(commands, "formatteropts", [])
215 215 )
216 216
217 217 # for "historical portability":
218 218 # use locally defined option list, if debugrevlogopts isn't available,
219 219 # because commands.debugrevlogopts has been available since 3.7 (or
220 220 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
221 221 # since 1.9 (or a79fea6b3e77).
222 222 revlogopts = getattr(
223 223 cmdutil,
224 224 "debugrevlogopts",
225 225 getattr(
226 226 commands,
227 227 "debugrevlogopts",
228 228 [
229 229 (b'c', b'changelog', False, b'open changelog'),
230 230 (b'm', b'manifest', False, b'open manifest'),
231 231 (b'', b'dir', False, b'open directory manifest'),
232 232 ],
233 233 ),
234 234 )
235 235
236 236 cmdtable = {}
237 237
238 238
239 239 # for "historical portability":
240 240 # define parsealiases locally, because cmdutil.parsealiases has been
241 241 # available since 1.5 (or 6252852b4332)
242 242 def parsealiases(cmd):
243 243 return cmd.split(b"|")
244 244
245 245
246 246 if safehasattr(registrar, 'command'):
247 247 command = registrar.command(cmdtable)
248 248 elif safehasattr(cmdutil, 'command'):
249 249 command = cmdutil.command(cmdtable)
250 250 if 'norepo' not in getargspec(command).args:
251 251 # for "historical portability":
252 252 # wrap original cmdutil.command, because "norepo" option has
253 253 # been available since 3.1 (or 75a96326cecb)
254 254 _command = command
255 255
256 256 def command(name, options=(), synopsis=None, norepo=False):
257 257 if norepo:
258 258 commands.norepo += b' %s' % b' '.join(parsealiases(name))
259 259 return _command(name, list(options), synopsis)
260 260
261 261
262 262 else:
263 263 # for "historical portability":
264 264 # define "@command" annotation locally, because cmdutil.command
265 265 # has been available since 1.9 (or 2daa5179e73f)
266 266 def command(name, options=(), synopsis=None, norepo=False):
267 267 def decorator(func):
268 268 if synopsis:
269 269 cmdtable[name] = func, list(options), synopsis
270 270 else:
271 271 cmdtable[name] = func, list(options)
272 272 if norepo:
273 273 commands.norepo += b' %s' % b' '.join(parsealiases(name))
274 274 return func
275 275
276 276 return decorator
277 277
278 278
279 279 try:
280 280 import mercurial.registrar
281 281 import mercurial.configitems
282 282
283 283 configtable = {}
284 284 configitem = mercurial.registrar.configitem(configtable)
285 285 configitem(
286 286 b'perf',
287 287 b'presleep',
288 288 default=mercurial.configitems.dynamicdefault,
289 289 experimental=True,
290 290 )
291 291 configitem(
292 292 b'perf',
293 293 b'stub',
294 294 default=mercurial.configitems.dynamicdefault,
295 295 experimental=True,
296 296 )
297 297 configitem(
298 298 b'perf',
299 299 b'parentscount',
300 300 default=mercurial.configitems.dynamicdefault,
301 301 experimental=True,
302 302 )
303 303 configitem(
304 304 b'perf',
305 305 b'all-timing',
306 306 default=mercurial.configitems.dynamicdefault,
307 307 experimental=True,
308 308 )
309 309 configitem(
310 310 b'perf',
311 311 b'pre-run',
312 312 default=mercurial.configitems.dynamicdefault,
313 313 )
314 314 configitem(
315 315 b'perf',
316 316 b'profile-benchmark',
317 317 default=mercurial.configitems.dynamicdefault,
318 318 )
319 319 configitem(
320 320 b'perf',
321 321 b'run-limits',
322 322 default=mercurial.configitems.dynamicdefault,
323 323 experimental=True,
324 324 )
325 325 except (ImportError, AttributeError):
326 326 pass
327 327 except TypeError:
328 328 # compatibility fix for a11fd395e83f
329 329 # hg version: 5.2
330 330 configitem(
331 331 b'perf',
332 332 b'presleep',
333 333 default=mercurial.configitems.dynamicdefault,
334 334 )
335 335 configitem(
336 336 b'perf',
337 337 b'stub',
338 338 default=mercurial.configitems.dynamicdefault,
339 339 )
340 340 configitem(
341 341 b'perf',
342 342 b'parentscount',
343 343 default=mercurial.configitems.dynamicdefault,
344 344 )
345 345 configitem(
346 346 b'perf',
347 347 b'all-timing',
348 348 default=mercurial.configitems.dynamicdefault,
349 349 )
350 350 configitem(
351 351 b'perf',
352 352 b'pre-run',
353 353 default=mercurial.configitems.dynamicdefault,
354 354 )
355 355 configitem(
356 356 b'perf',
357 357 b'profile-benchmark',
358 358 default=mercurial.configitems.dynamicdefault,
359 359 )
360 360 configitem(
361 361 b'perf',
362 362 b'run-limits',
363 363 default=mercurial.configitems.dynamicdefault,
364 364 )
365 365
366 366
367 367 def getlen(ui):
368 368 if ui.configbool(b"perf", b"stub", False):
369 369 return lambda x: 1
370 370 return len
371 371
372 372
373 373 class noop:
374 374 """dummy context manager"""
375 375
376 376 def __enter__(self):
377 377 pass
378 378
379 379 def __exit__(self, *args):
380 380 pass
381 381
382 382
383 383 NOOPCTX = noop()
384 384
385 385
386 386 def gettimer(ui, opts=None):
387 387 """return a timer function and formatter: (timer, formatter)
388 388
389 389 This function exists to gather the creation of formatter in a single
390 390 place instead of duplicating it in all performance commands."""
391 391
392 392 # enforce an idle period before execution to counteract power management
393 393 # experimental config: perf.presleep
394 394 time.sleep(getint(ui, b"perf", b"presleep", 1))
395 395
396 396 if opts is None:
397 397 opts = {}
398 398 # redirect all to stderr unless buffer api is in use
399 399 if not ui._buffers:
400 400 ui = ui.copy()
401 401 uifout = safeattrsetter(ui, b'fout', ignoremissing=True)
402 402 if uifout:
403 403 # for "historical portability":
404 404 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
405 405 uifout.set(ui.ferr)
406 406
407 407 # get a formatter
408 408 uiformatter = getattr(ui, 'formatter', None)
409 409 if uiformatter:
410 410 fm = uiformatter(b'perf', opts)
411 411 else:
412 412 # for "historical portability":
413 413 # define formatter locally, because ui.formatter has been
414 414 # available since 2.2 (or ae5f92e154d3)
415 415 from mercurial import node
416 416
417 417 class defaultformatter:
418 418 """Minimized composition of baseformatter and plainformatter"""
419 419
420 420 def __init__(self, ui, topic, opts):
421 421 self._ui = ui
422 422 if ui.debugflag:
423 423 self.hexfunc = node.hex
424 424 else:
425 425 self.hexfunc = node.short
426 426
427 427 def __nonzero__(self):
428 428 return False
429 429
430 430 __bool__ = __nonzero__
431 431
432 432 def startitem(self):
433 433 pass
434 434
435 435 def data(self, **data):
436 436 pass
437 437
438 438 def write(self, fields, deftext, *fielddata, **opts):
439 439 self._ui.write(deftext % fielddata, **opts)
440 440
441 441 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
442 442 if cond:
443 443 self._ui.write(deftext % fielddata, **opts)
444 444
445 445 def plain(self, text, **opts):
446 446 self._ui.write(text, **opts)
447 447
448 448 def end(self):
449 449 pass
450 450
451 451 fm = defaultformatter(ui, b'perf', opts)
452 452
453 453 # stub function, runs code only once instead of in a loop
454 454 # experimental config: perf.stub
455 455 if ui.configbool(b"perf", b"stub", False):
456 456 return functools.partial(stub_timer, fm), fm
457 457
458 458 # experimental config: perf.all-timing
459 459 displayall = ui.configbool(b"perf", b"all-timing", True)
460 460
461 461 # experimental config: perf.run-limits
462 462 limitspec = ui.configlist(b"perf", b"run-limits", [])
463 463 limits = []
464 464 for item in limitspec:
465 465 parts = item.split(b'-', 1)
466 466 if len(parts) < 2:
467 467 ui.warn((b'malformatted run limit entry, missing "-": %s\n' % item))
468 468 continue
469 469 try:
470 470 time_limit = float(_sysstr(parts[0]))
471 471 except ValueError as e:
472 472 ui.warn(
473 473 (
474 474 b'malformatted run limit entry, %s: %s\n'
475 475 % (_bytestr(e), item)
476 476 )
477 477 )
478 478 continue
479 479 try:
480 480 run_limit = int(_sysstr(parts[1]))
481 481 except ValueError as e:
482 482 ui.warn(
483 483 (
484 484 b'malformatted run limit entry, %s: %s\n'
485 485 % (_bytestr(e), item)
486 486 )
487 487 )
488 488 continue
489 489 limits.append((time_limit, run_limit))
490 490 if not limits:
491 491 limits = DEFAULTLIMITS
492 492
493 493 profiler = None
494 494 if profiling is not None:
495 495 if ui.configbool(b"perf", b"profile-benchmark", False):
496 496 profiler = profiling.profile(ui)
497 497
498 498 prerun = getint(ui, b"perf", b"pre-run", 0)
499 499 t = functools.partial(
500 500 _timer,
501 501 fm,
502 502 displayall=displayall,
503 503 limits=limits,
504 504 prerun=prerun,
505 505 profiler=profiler,
506 506 )
507 507 return t, fm
508 508
509 509
510 510 def stub_timer(fm, func, setup=None, title=None):
511 511 if setup is not None:
512 512 setup()
513 513 func()
514 514
515 515
516 516 @contextlib.contextmanager
517 517 def timeone():
518 518 r = []
519 519 ostart = os.times()
520 520 cstart = util.timer()
521 521 yield r
522 522 cstop = util.timer()
523 523 ostop = os.times()
524 524 a, b = ostart, ostop
525 525 r.append((cstop - cstart, b[0] - a[0], b[1] - a[1]))
526 526
527 527
528 528 # list of stop condition (elapsed time, minimal run count)
529 529 DEFAULTLIMITS = (
530 530 (3.0, 100),
531 531 (10.0, 3),
532 532 )
533 533
534 534
535 535 @contextlib.contextmanager
536 536 def noop_context():
537 537 yield
538 538
539 539
540 540 def _timer(
541 541 fm,
542 542 func,
543 543 setup=None,
544 544 context=noop_context,
545 545 title=None,
546 546 displayall=False,
547 547 limits=DEFAULTLIMITS,
548 548 prerun=0,
549 549 profiler=None,
550 550 ):
551 551 gc.collect()
552 552 results = []
553 553 begin = util.timer()
554 554 count = 0
555 555 if profiler is None:
556 556 profiler = NOOPCTX
557 557 for i in range(prerun):
558 558 if setup is not None:
559 559 setup()
560 560 with context():
561 561 func()
562 562 keepgoing = True
563 563 while keepgoing:
564 564 if setup is not None:
565 565 setup()
566 566 with context():
567 567 with profiler:
568 568 with timeone() as item:
569 569 r = func()
570 570 profiler = NOOPCTX
571 571 count += 1
572 572 results.append(item[0])
573 573 cstop = util.timer()
574 574 # Look for a stop condition.
575 575 elapsed = cstop - begin
576 576 for t, mincount in limits:
577 577 if elapsed >= t and count >= mincount:
578 578 keepgoing = False
579 579 break
580 580
581 581 formatone(fm, results, title=title, result=r, displayall=displayall)
582 582
583 583
584 584 def formatone(fm, timings, title=None, result=None, displayall=False):
585 585 count = len(timings)
586 586
587 587 fm.startitem()
588 588
589 589 if title:
590 590 fm.write(b'title', b'! %s\n', title)
591 591 if result:
592 592 fm.write(b'result', b'! result: %s\n', result)
593 593
594 594 def display(role, entry):
595 595 prefix = b''
596 596 if role != b'best':
597 597 prefix = b'%s.' % role
598 598 fm.plain(b'!')
599 599 fm.write(prefix + b'wall', b' wall %f', entry[0])
600 600 fm.write(prefix + b'comb', b' comb %f', entry[1] + entry[2])
601 601 fm.write(prefix + b'user', b' user %f', entry[1])
602 602 fm.write(prefix + b'sys', b' sys %f', entry[2])
603 603 fm.write(prefix + b'count', b' (%s of %%d)' % role, count)
604 604 fm.plain(b'\n')
605 605
606 606 timings.sort()
607 607 min_val = timings[0]
608 608 display(b'best', min_val)
609 609 if displayall:
610 610 max_val = timings[-1]
611 611 display(b'max', max_val)
612 612 avg = tuple([sum(x) / count for x in zip(*timings)])
613 613 display(b'avg', avg)
614 614 median = timings[len(timings) // 2]
615 615 display(b'median', median)
616 616
617 617
618 618 # utilities for historical portability
619 619
620 620
621 621 def getint(ui, section, name, default):
622 622 # for "historical portability":
623 623 # ui.configint has been available since 1.9 (or fa2b596db182)
624 624 v = ui.config(section, name, None)
625 625 if v is None:
626 626 return default
627 627 try:
628 628 return int(v)
629 629 except ValueError:
630 630 raise error.ConfigError(
631 631 b"%s.%s is not an integer ('%s')" % (section, name, v)
632 632 )
633 633
634 634
635 635 def safeattrsetter(obj, name, ignoremissing=False):
636 636 """Ensure that 'obj' has 'name' attribute before subsequent setattr
637 637
638 638 This function is aborted, if 'obj' doesn't have 'name' attribute
639 639 at runtime. This avoids overlooking removal of an attribute, which
640 640 breaks assumption of performance measurement, in the future.
641 641
642 642 This function returns the object to (1) assign a new value, and
643 643 (2) restore an original value to the attribute.
644 644
645 645 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
646 646 abortion, and this function returns None. This is useful to
647 647 examine an attribute, which isn't ensured in all Mercurial
648 648 versions.
649 649 """
650 650 if not util.safehasattr(obj, name):
651 651 if ignoremissing:
652 652 return None
653 653 raise error.Abort(
654 654 (
655 655 b"missing attribute %s of %s might break assumption"
656 656 b" of performance measurement"
657 657 )
658 658 % (name, obj)
659 659 )
660 660
661 661 origvalue = getattr(obj, _sysstr(name))
662 662
663 663 class attrutil:
664 664 def set(self, newvalue):
665 665 setattr(obj, _sysstr(name), newvalue)
666 666
667 667 def restore(self):
668 668 setattr(obj, _sysstr(name), origvalue)
669 669
670 670 return attrutil()
671 671
672 672
673 673 # utilities to examine each internal API changes
674 674
675 675
676 676 def getbranchmapsubsettable():
677 677 # for "historical portability":
678 678 # subsettable is defined in:
679 679 # - branchmap since 2.9 (or 175c6fd8cacc)
680 680 # - repoview since 2.5 (or 59a9f18d4587)
681 681 # - repoviewutil since 5.0
682 682 for mod in (branchmap, repoview, repoviewutil):
683 683 subsettable = getattr(mod, 'subsettable', None)
684 684 if subsettable:
685 685 return subsettable
686 686
687 687 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
688 688 # branchmap and repoview modules exist, but subsettable attribute
689 689 # doesn't)
690 690 raise error.Abort(
691 691 b"perfbranchmap not available with this Mercurial",
692 692 hint=b"use 2.5 or later",
693 693 )
694 694
695 695
696 696 def getsvfs(repo):
697 697 """Return appropriate object to access files under .hg/store"""
698 698 # for "historical portability":
699 699 # repo.svfs has been available since 2.3 (or 7034365089bf)
700 700 svfs = getattr(repo, 'svfs', None)
701 701 if svfs:
702 702 return svfs
703 703 else:
704 704 return getattr(repo, 'sopener')
705 705
706 706
707 707 def getvfs(repo):
708 708 """Return appropriate object to access files under .hg"""
709 709 # for "historical portability":
710 710 # repo.vfs has been available since 2.3 (or 7034365089bf)
711 711 vfs = getattr(repo, 'vfs', None)
712 712 if vfs:
713 713 return vfs
714 714 else:
715 715 return getattr(repo, 'opener')
716 716
717 717
718 718 def repocleartagscachefunc(repo):
719 719 """Return the function to clear tags cache according to repo internal API"""
720 720 if util.safehasattr(repo, b'_tagscache'): # since 2.0 (or 9dca7653b525)
721 721 # in this case, setattr(repo, '_tagscache', None) or so isn't
722 722 # correct way to clear tags cache, because existing code paths
723 723 # expect _tagscache to be a structured object.
724 724 def clearcache():
725 725 # _tagscache has been filteredpropertycache since 2.5 (or
726 726 # 98c867ac1330), and delattr() can't work in such case
727 727 if '_tagscache' in vars(repo):
728 728 del repo.__dict__['_tagscache']
729 729
730 730 return clearcache
731 731
732 732 repotags = safeattrsetter(repo, b'_tags', ignoremissing=True)
733 733 if repotags: # since 1.4 (or 5614a628d173)
734 734 return lambda: repotags.set(None)
735 735
736 736 repotagscache = safeattrsetter(repo, b'tagscache', ignoremissing=True)
737 737 if repotagscache: # since 0.6 (or d7df759d0e97)
738 738 return lambda: repotagscache.set(None)
739 739
740 740 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
741 741 # this point, but it isn't so problematic, because:
742 742 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
743 743 # in perftags() causes failure soon
744 744 # - perf.py itself has been available since 1.1 (or eb240755386d)
745 745 raise error.Abort(b"tags API of this hg command is unknown")
746 746
747 747
748 748 # utilities to clear cache
749 749
750 750
751 751 def clearfilecache(obj, attrname):
752 752 unfiltered = getattr(obj, 'unfiltered', None)
753 753 if unfiltered is not None:
754 754 obj = obj.unfiltered()
755 755 if attrname in vars(obj):
756 756 delattr(obj, attrname)
757 757 obj._filecache.pop(attrname, None)
758 758
759 759
760 760 def clearchangelog(repo):
761 761 if repo is not repo.unfiltered():
762 762 object.__setattr__(repo, '_clcachekey', None)
763 763 object.__setattr__(repo, '_clcache', None)
764 764 clearfilecache(repo.unfiltered(), 'changelog')
765 765
766 766
767 767 # perf commands
768 768
769 769
770 770 @command(b'perf::walk|perfwalk', formatteropts)
771 771 def perfwalk(ui, repo, *pats, **opts):
772 772 opts = _byteskwargs(opts)
773 773 timer, fm = gettimer(ui, opts)
774 774 m = scmutil.match(repo[None], pats, {})
775 775 timer(
776 776 lambda: len(
777 777 list(
778 778 repo.dirstate.walk(m, subrepos=[], unknown=True, ignored=False)
779 779 )
780 780 )
781 781 )
782 782 fm.end()
783 783
784 784
785 785 @command(b'perf::annotate|perfannotate', formatteropts)
786 786 def perfannotate(ui, repo, f, **opts):
787 787 opts = _byteskwargs(opts)
788 788 timer, fm = gettimer(ui, opts)
789 789 fc = repo[b'.'][f]
790 790 timer(lambda: len(fc.annotate(True)))
791 791 fm.end()
792 792
793 793
794 794 @command(
795 795 b'perf::status|perfstatus',
796 796 [
797 797 (b'u', b'unknown', False, b'ask status to look for unknown files'),
798 798 (b'', b'dirstate', False, b'benchmark the internal dirstate call'),
799 799 ]
800 800 + formatteropts,
801 801 )
802 802 def perfstatus(ui, repo, **opts):
803 803 """benchmark the performance of a single status call
804 804
805 805 The repository data are preserved between each call.
806 806
807 807 By default, only the status of the tracked file are requested. If
808 808 `--unknown` is passed, the "unknown" files are also tracked.
809 809 """
810 810 opts = _byteskwargs(opts)
811 811 # m = match.always(repo.root, repo.getcwd())
812 812 # timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
813 813 # False))))
814 814 timer, fm = gettimer(ui, opts)
815 815 if opts[b'dirstate']:
816 816 dirstate = repo.dirstate
817 817 m = scmutil.matchall(repo)
818 818 unknown = opts[b'unknown']
819 819
820 820 def status_dirstate():
821 821 s = dirstate.status(
822 822 m, subrepos=[], ignored=False, clean=False, unknown=unknown
823 823 )
824 824 sum(map(bool, s))
825 825
826 826 if util.safehasattr(dirstate, 'running_status'):
827 827 with dirstate.running_status(repo):
828 828 timer(status_dirstate)
829 829 dirstate.invalidate()
830 830 else:
831 831 timer(status_dirstate)
832 832 else:
833 833 timer(lambda: sum(map(len, repo.status(unknown=opts[b'unknown']))))
834 834 fm.end()
835 835
836 836
837 837 @command(b'perf::addremove|perfaddremove', formatteropts)
838 838 def perfaddremove(ui, repo, **opts):
839 839 opts = _byteskwargs(opts)
840 840 timer, fm = gettimer(ui, opts)
841 841 try:
842 842 oldquiet = repo.ui.quiet
843 843 repo.ui.quiet = True
844 844 matcher = scmutil.match(repo[None])
845 845 opts[b'dry_run'] = True
846 846 if 'uipathfn' in getargspec(scmutil.addremove).args:
847 847 uipathfn = scmutil.getuipathfn(repo)
848 848 timer(lambda: scmutil.addremove(repo, matcher, b"", uipathfn, opts))
849 849 else:
850 850 timer(lambda: scmutil.addremove(repo, matcher, b"", opts))
851 851 finally:
852 852 repo.ui.quiet = oldquiet
853 853 fm.end()
854 854
855 855
856 856 def clearcaches(cl):
857 857 # behave somewhat consistently across internal API changes
858 858 if util.safehasattr(cl, b'clearcaches'):
859 859 cl.clearcaches()
860 860 elif util.safehasattr(cl, b'_nodecache'):
861 861 # <= hg-5.2
862 862 from mercurial.node import nullid, nullrev
863 863
864 864 cl._nodecache = {nullid: nullrev}
865 865 cl._nodepos = None
866 866
867 867
868 868 @command(b'perf::heads|perfheads', formatteropts)
869 869 def perfheads(ui, repo, **opts):
870 870 """benchmark the computation of a changelog heads"""
871 871 opts = _byteskwargs(opts)
872 872 timer, fm = gettimer(ui, opts)
873 873 cl = repo.changelog
874 874
875 875 def s():
876 876 clearcaches(cl)
877 877
878 878 def d():
879 879 len(cl.headrevs())
880 880
881 881 timer(d, setup=s)
882 882 fm.end()
883 883
884 884
885 885 @command(
886 886 b'perf::tags|perftags',
887 887 formatteropts
888 888 + [
889 889 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
890 890 ],
891 891 )
892 892 def perftags(ui, repo, **opts):
893 893 opts = _byteskwargs(opts)
894 894 timer, fm = gettimer(ui, opts)
895 895 repocleartagscache = repocleartagscachefunc(repo)
896 896 clearrevlogs = opts[b'clear_revlogs']
897 897
898 898 def s():
899 899 if clearrevlogs:
900 900 clearchangelog(repo)
901 901 clearfilecache(repo.unfiltered(), 'manifest')
902 902 repocleartagscache()
903 903
904 904 def t():
905 return len(repo.tags())
905 len(repo.tags())
906 906
907 907 timer(t, setup=s)
908 908 fm.end()
909 909
910 910
911 911 @command(b'perf::ancestors|perfancestors', formatteropts)
912 912 def perfancestors(ui, repo, **opts):
913 913 opts = _byteskwargs(opts)
914 914 timer, fm = gettimer(ui, opts)
915 915 heads = repo.changelog.headrevs()
916 916
917 917 def d():
918 918 for a in repo.changelog.ancestors(heads):
919 919 pass
920 920
921 921 timer(d)
922 922 fm.end()
923 923
924 924
925 925 @command(b'perf::ancestorset|perfancestorset', formatteropts)
926 926 def perfancestorset(ui, repo, revset, **opts):
927 927 opts = _byteskwargs(opts)
928 928 timer, fm = gettimer(ui, opts)
929 929 revs = repo.revs(revset)
930 930 heads = repo.changelog.headrevs()
931 931
932 932 def d():
933 933 s = repo.changelog.ancestors(heads)
934 934 for rev in revs:
935 935 rev in s
936 936
937 937 timer(d)
938 938 fm.end()
939 939
940 940
941 941 @command(
942 942 b'perf::delta-find',
943 943 revlogopts + formatteropts,
944 944 b'-c|-m|FILE REV',
945 945 )
946 946 def perf_delta_find(ui, repo, arg_1, arg_2=None, **opts):
947 947 """benchmark the process of finding a valid delta for a revlog revision
948 948
949 949 When a revlog receives a new revision (e.g. from a commit, or from an
950 950 incoming bundle), it searches for a suitable delta-base to produce a delta.
951 951 This perf command measures how much time we spend in this process. It
952 952 operates on an already stored revision.
953 953
954 954 See `hg help debug-delta-find` for another related command.
955 955 """
956 956 from mercurial import revlogutils
957 957 import mercurial.revlogutils.deltas as deltautil
958 958
959 959 opts = _byteskwargs(opts)
960 960 if arg_2 is None:
961 961 file_ = None
962 962 rev = arg_1
963 963 else:
964 964 file_ = arg_1
965 965 rev = arg_2
966 966
967 967 repo = repo.unfiltered()
968 968
969 969 timer, fm = gettimer(ui, opts)
970 970
971 971 rev = int(rev)
972 972
973 973 revlog = cmdutil.openrevlog(repo, b'perf::delta-find', file_, opts)
974 974
975 975 deltacomputer = deltautil.deltacomputer(revlog)
976 976
977 977 node = revlog.node(rev)
978 978 p1r, p2r = revlog.parentrevs(rev)
979 979 p1 = revlog.node(p1r)
980 980 p2 = revlog.node(p2r)
981 981 full_text = revlog.revision(rev)
982 982 textlen = len(full_text)
983 983 cachedelta = None
984 984 flags = revlog.flags(rev)
985 985
986 986 revinfo = revlogutils.revisioninfo(
987 987 node,
988 988 p1,
989 989 p2,
990 990 [full_text], # btext
991 991 textlen,
992 992 cachedelta,
993 993 flags,
994 994 )
995 995
996 996 # Note: we should probably purge the potential caches (like the full
997 997 # manifest cache) between runs.
998 998 def find_one():
999 999 with revlog._datafp() as fh:
1000 1000 deltacomputer.finddeltainfo(revinfo, fh, target_rev=rev)
1001 1001
1002 1002 timer(find_one)
1003 1003 fm.end()
1004 1004
1005 1005
1006 1006 @command(b'perf::discovery|perfdiscovery', formatteropts, b'PATH')
1007 1007 def perfdiscovery(ui, repo, path, **opts):
1008 1008 """benchmark discovery between local repo and the peer at given path"""
1009 1009 repos = [repo, None]
1010 1010 timer, fm = gettimer(ui, opts)
1011 1011
1012 1012 try:
1013 1013 from mercurial.utils.urlutil import get_unique_pull_path_obj
1014 1014
1015 1015 path = get_unique_pull_path_obj(b'perfdiscovery', ui, path)
1016 1016 except ImportError:
1017 1017 try:
1018 1018 from mercurial.utils.urlutil import get_unique_pull_path
1019 1019
1020 1020 path = get_unique_pull_path(b'perfdiscovery', repo, ui, path)[0]
1021 1021 except ImportError:
1022 1022 path = ui.expandpath(path)
1023 1023
1024 1024 def s():
1025 1025 repos[1] = hg.peer(ui, opts, path)
1026 1026
1027 1027 def d():
1028 1028 setdiscovery.findcommonheads(ui, *repos)
1029 1029
1030 1030 timer(d, setup=s)
1031 1031 fm.end()
1032 1032
1033 1033
1034 1034 @command(
1035 1035 b'perf::bookmarks|perfbookmarks',
1036 1036 formatteropts
1037 1037 + [
1038 1038 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
1039 1039 ],
1040 1040 )
1041 1041 def perfbookmarks(ui, repo, **opts):
1042 1042 """benchmark parsing bookmarks from disk to memory"""
1043 1043 opts = _byteskwargs(opts)
1044 1044 timer, fm = gettimer(ui, opts)
1045 1045
1046 1046 clearrevlogs = opts[b'clear_revlogs']
1047 1047
1048 1048 def s():
1049 1049 if clearrevlogs:
1050 1050 clearchangelog(repo)
1051 1051 clearfilecache(repo, b'_bookmarks')
1052 1052
1053 1053 def d():
1054 1054 repo._bookmarks
1055 1055
1056 1056 timer(d, setup=s)
1057 1057 fm.end()
1058 1058
1059 1059
1060 1060 @command(
1061 1061 b'perf::bundle',
1062 1062 [
1063 1063 (
1064 1064 b'r',
1065 1065 b'rev',
1066 1066 [],
1067 1067 b'changesets to bundle',
1068 1068 b'REV',
1069 1069 ),
1070 1070 (
1071 1071 b't',
1072 1072 b'type',
1073 1073 b'none',
1074 1074 b'bundlespec to use (see `hg help bundlespec`)',
1075 1075 b'TYPE',
1076 1076 ),
1077 1077 ]
1078 1078 + formatteropts,
1079 1079 b'REVS',
1080 1080 )
1081 1081 def perfbundle(ui, repo, *revs, **opts):
1082 1082 """benchmark the creation of a bundle from a repository
1083 1083
1084 1084 For now, this only supports "none" compression.
1085 1085 """
1086 1086 try:
1087 1087 from mercurial import bundlecaches
1088 1088
1089 1089 parsebundlespec = bundlecaches.parsebundlespec
1090 1090 except ImportError:
1091 1091 from mercurial import exchange
1092 1092
1093 1093 parsebundlespec = exchange.parsebundlespec
1094 1094
1095 1095 from mercurial import discovery
1096 1096 from mercurial import bundle2
1097 1097
1098 1098 opts = _byteskwargs(opts)
1099 1099 timer, fm = gettimer(ui, opts)
1100 1100
1101 1101 cl = repo.changelog
1102 1102 revs = list(revs)
1103 1103 revs.extend(opts.get(b'rev', ()))
1104 1104 revs = scmutil.revrange(repo, revs)
1105 1105 if not revs:
1106 1106 raise error.Abort(b"not revision specified")
1107 1107 # make it a consistent set (ie: without topological gaps)
1108 1108 old_len = len(revs)
1109 1109 revs = list(repo.revs(b"%ld::%ld", revs, revs))
1110 1110 if old_len != len(revs):
1111 1111 new_count = len(revs) - old_len
1112 1112 msg = b"add %d new revisions to make it a consistent set\n"
1113 1113 ui.write_err(msg % new_count)
1114 1114
1115 1115 targets = [cl.node(r) for r in repo.revs(b"heads(::%ld)", revs)]
1116 1116 bases = [cl.node(r) for r in repo.revs(b"heads(::%ld - %ld)", revs, revs)]
1117 1117 outgoing = discovery.outgoing(repo, bases, targets)
1118 1118
1119 1119 bundle_spec = opts.get(b'type')
1120 1120
1121 1121 bundle_spec = parsebundlespec(repo, bundle_spec, strict=False)
1122 1122
1123 1123 cgversion = bundle_spec.params.get(b"cg.version")
1124 1124 if cgversion is None:
1125 1125 if bundle_spec.version == b'v1':
1126 1126 cgversion = b'01'
1127 1127 if bundle_spec.version == b'v2':
1128 1128 cgversion = b'02'
1129 1129 if cgversion not in changegroup.supportedoutgoingversions(repo):
1130 1130 err = b"repository does not support bundle version %s"
1131 1131 raise error.Abort(err % cgversion)
1132 1132
1133 1133 if cgversion == b'01': # bundle1
1134 1134 bversion = b'HG10' + bundle_spec.wirecompression
1135 1135 bcompression = None
1136 1136 elif cgversion in (b'02', b'03'):
1137 1137 bversion = b'HG20'
1138 1138 bcompression = bundle_spec.wirecompression
1139 1139 else:
1140 1140 err = b'perf::bundle: unexpected changegroup version %s'
1141 1141 raise error.ProgrammingError(err % cgversion)
1142 1142
1143 1143 if bcompression is None:
1144 1144 bcompression = b'UN'
1145 1145
1146 1146 if bcompression != b'UN':
1147 1147 err = b'perf::bundle: compression currently unsupported: %s'
1148 1148 raise error.ProgrammingError(err % bcompression)
1149 1149
1150 1150 def do_bundle():
1151 1151 bundle2.writenewbundle(
1152 1152 ui,
1153 1153 repo,
1154 1154 b'perf::bundle',
1155 1155 os.devnull,
1156 1156 bversion,
1157 1157 outgoing,
1158 1158 bundle_spec.params,
1159 1159 )
1160 1160
1161 1161 timer(do_bundle)
1162 1162 fm.end()
1163 1163
1164 1164
1165 1165 @command(b'perf::bundleread|perfbundleread', formatteropts, b'BUNDLE')
1166 1166 def perfbundleread(ui, repo, bundlepath, **opts):
1167 1167 """Benchmark reading of bundle files.
1168 1168
1169 1169 This command is meant to isolate the I/O part of bundle reading as
1170 1170 much as possible.
1171 1171 """
1172 1172 from mercurial import (
1173 1173 bundle2,
1174 1174 exchange,
1175 1175 streamclone,
1176 1176 )
1177 1177
1178 1178 opts = _byteskwargs(opts)
1179 1179
1180 1180 def makebench(fn):
1181 1181 def run():
1182 1182 with open(bundlepath, b'rb') as fh:
1183 1183 bundle = exchange.readbundle(ui, fh, bundlepath)
1184 1184 fn(bundle)
1185 1185
1186 1186 return run
1187 1187
1188 1188 def makereadnbytes(size):
1189 1189 def run():
1190 1190 with open(bundlepath, b'rb') as fh:
1191 1191 bundle = exchange.readbundle(ui, fh, bundlepath)
1192 1192 while bundle.read(size):
1193 1193 pass
1194 1194
1195 1195 return run
1196 1196
1197 1197 def makestdioread(size):
1198 1198 def run():
1199 1199 with open(bundlepath, b'rb') as fh:
1200 1200 while fh.read(size):
1201 1201 pass
1202 1202
1203 1203 return run
1204 1204
1205 1205 # bundle1
1206 1206
1207 1207 def deltaiter(bundle):
1208 1208 for delta in bundle.deltaiter():
1209 1209 pass
1210 1210
1211 1211 def iterchunks(bundle):
1212 1212 for chunk in bundle.getchunks():
1213 1213 pass
1214 1214
1215 1215 # bundle2
1216 1216
1217 1217 def forwardchunks(bundle):
1218 1218 for chunk in bundle._forwardchunks():
1219 1219 pass
1220 1220
1221 1221 def iterparts(bundle):
1222 1222 for part in bundle.iterparts():
1223 1223 pass
1224 1224
1225 1225 def iterpartsseekable(bundle):
1226 1226 for part in bundle.iterparts(seekable=True):
1227 1227 pass
1228 1228
1229 1229 def seek(bundle):
1230 1230 for part in bundle.iterparts(seekable=True):
1231 1231 part.seek(0, os.SEEK_END)
1232 1232
1233 1233 def makepartreadnbytes(size):
1234 1234 def run():
1235 1235 with open(bundlepath, b'rb') as fh:
1236 1236 bundle = exchange.readbundle(ui, fh, bundlepath)
1237 1237 for part in bundle.iterparts():
1238 1238 while part.read(size):
1239 1239 pass
1240 1240
1241 1241 return run
1242 1242
1243 1243 benches = [
1244 1244 (makestdioread(8192), b'read(8k)'),
1245 1245 (makestdioread(16384), b'read(16k)'),
1246 1246 (makestdioread(32768), b'read(32k)'),
1247 1247 (makestdioread(131072), b'read(128k)'),
1248 1248 ]
1249 1249
1250 1250 with open(bundlepath, b'rb') as fh:
1251 1251 bundle = exchange.readbundle(ui, fh, bundlepath)
1252 1252
1253 1253 if isinstance(bundle, changegroup.cg1unpacker):
1254 1254 benches.extend(
1255 1255 [
1256 1256 (makebench(deltaiter), b'cg1 deltaiter()'),
1257 1257 (makebench(iterchunks), b'cg1 getchunks()'),
1258 1258 (makereadnbytes(8192), b'cg1 read(8k)'),
1259 1259 (makereadnbytes(16384), b'cg1 read(16k)'),
1260 1260 (makereadnbytes(32768), b'cg1 read(32k)'),
1261 1261 (makereadnbytes(131072), b'cg1 read(128k)'),
1262 1262 ]
1263 1263 )
1264 1264 elif isinstance(bundle, bundle2.unbundle20):
1265 1265 benches.extend(
1266 1266 [
1267 1267 (makebench(forwardchunks), b'bundle2 forwardchunks()'),
1268 1268 (makebench(iterparts), b'bundle2 iterparts()'),
1269 1269 (
1270 1270 makebench(iterpartsseekable),
1271 1271 b'bundle2 iterparts() seekable',
1272 1272 ),
1273 1273 (makebench(seek), b'bundle2 part seek()'),
1274 1274 (makepartreadnbytes(8192), b'bundle2 part read(8k)'),
1275 1275 (makepartreadnbytes(16384), b'bundle2 part read(16k)'),
1276 1276 (makepartreadnbytes(32768), b'bundle2 part read(32k)'),
1277 1277 (makepartreadnbytes(131072), b'bundle2 part read(128k)'),
1278 1278 ]
1279 1279 )
1280 1280 elif isinstance(bundle, streamclone.streamcloneapplier):
1281 1281 raise error.Abort(b'stream clone bundles not supported')
1282 1282 else:
1283 1283 raise error.Abort(b'unhandled bundle type: %s' % type(bundle))
1284 1284
1285 1285 for fn, title in benches:
1286 1286 timer, fm = gettimer(ui, opts)
1287 1287 timer(fn, title=title)
1288 1288 fm.end()
1289 1289
1290 1290
1291 1291 @command(
1292 1292 b'perf::changegroupchangelog|perfchangegroupchangelog',
1293 1293 formatteropts
1294 1294 + [
1295 1295 (b'', b'cgversion', b'02', b'changegroup version'),
1296 1296 (b'r', b'rev', b'', b'revisions to add to changegroup'),
1297 1297 ],
1298 1298 )
1299 1299 def perfchangegroupchangelog(ui, repo, cgversion=b'02', rev=None, **opts):
1300 1300 """Benchmark producing a changelog group for a changegroup.
1301 1301
1302 1302 This measures the time spent processing the changelog during a
1303 1303 bundle operation. This occurs during `hg bundle` and on a server
1304 1304 processing a `getbundle` wire protocol request (handles clones
1305 1305 and pull requests).
1306 1306
1307 1307 By default, all revisions are added to the changegroup.
1308 1308 """
1309 1309 opts = _byteskwargs(opts)
1310 1310 cl = repo.changelog
1311 1311 nodes = [cl.lookup(r) for r in repo.revs(rev or b'all()')]
1312 1312 bundler = changegroup.getbundler(cgversion, repo)
1313 1313
1314 1314 def d():
1315 1315 state, chunks = bundler._generatechangelog(cl, nodes)
1316 1316 for chunk in chunks:
1317 1317 pass
1318 1318
1319 1319 timer, fm = gettimer(ui, opts)
1320 1320
1321 1321 # Terminal printing can interfere with timing. So disable it.
1322 1322 with ui.configoverride({(b'progress', b'disable'): True}):
1323 1323 timer(d)
1324 1324
1325 1325 fm.end()
1326 1326
1327 1327
1328 1328 @command(b'perf::dirs|perfdirs', formatteropts)
1329 1329 def perfdirs(ui, repo, **opts):
1330 1330 opts = _byteskwargs(opts)
1331 1331 timer, fm = gettimer(ui, opts)
1332 1332 dirstate = repo.dirstate
1333 1333 b'a' in dirstate
1334 1334
1335 1335 def d():
1336 1336 dirstate.hasdir(b'a')
1337 1337 try:
1338 1338 del dirstate._map._dirs
1339 1339 except AttributeError:
1340 1340 pass
1341 1341
1342 1342 timer(d)
1343 1343 fm.end()
1344 1344
1345 1345
1346 1346 @command(
1347 1347 b'perf::dirstate|perfdirstate',
1348 1348 [
1349 1349 (
1350 1350 b'',
1351 1351 b'iteration',
1352 1352 None,
1353 1353 b'benchmark a full iteration for the dirstate',
1354 1354 ),
1355 1355 (
1356 1356 b'',
1357 1357 b'contains',
1358 1358 None,
1359 1359 b'benchmark a large amount of `nf in dirstate` calls',
1360 1360 ),
1361 1361 ]
1362 1362 + formatteropts,
1363 1363 )
1364 1364 def perfdirstate(ui, repo, **opts):
1365 1365 """benchmap the time of various distate operations
1366 1366
1367 1367 By default benchmark the time necessary to load a dirstate from scratch.
1368 1368 The dirstate is loaded to the point were a "contains" request can be
1369 1369 answered.
1370 1370 """
1371 1371 opts = _byteskwargs(opts)
1372 1372 timer, fm = gettimer(ui, opts)
1373 1373 b"a" in repo.dirstate
1374 1374
1375 1375 if opts[b'iteration'] and opts[b'contains']:
1376 1376 msg = b'only specify one of --iteration or --contains'
1377 1377 raise error.Abort(msg)
1378 1378
1379 1379 if opts[b'iteration']:
1380 1380 setup = None
1381 1381 dirstate = repo.dirstate
1382 1382
1383 1383 def d():
1384 1384 for f in dirstate:
1385 1385 pass
1386 1386
1387 1387 elif opts[b'contains']:
1388 1388 setup = None
1389 1389 dirstate = repo.dirstate
1390 1390 allfiles = list(dirstate)
1391 1391 # also add file path that will be "missing" from the dirstate
1392 1392 allfiles.extend([f[::-1] for f in allfiles])
1393 1393
1394 1394 def d():
1395 1395 for f in allfiles:
1396 1396 f in dirstate
1397 1397
1398 1398 else:
1399 1399
1400 1400 def setup():
1401 1401 repo.dirstate.invalidate()
1402 1402
1403 1403 def d():
1404 1404 b"a" in repo.dirstate
1405 1405
1406 1406 timer(d, setup=setup)
1407 1407 fm.end()
1408 1408
1409 1409
1410 1410 @command(b'perf::dirstatedirs|perfdirstatedirs', formatteropts)
1411 1411 def perfdirstatedirs(ui, repo, **opts):
1412 1412 """benchmap a 'dirstate.hasdir' call from an empty `dirs` cache"""
1413 1413 opts = _byteskwargs(opts)
1414 1414 timer, fm = gettimer(ui, opts)
1415 1415 repo.dirstate.hasdir(b"a")
1416 1416
1417 1417 def setup():
1418 1418 try:
1419 1419 del repo.dirstate._map._dirs
1420 1420 except AttributeError:
1421 1421 pass
1422 1422
1423 1423 def d():
1424 1424 repo.dirstate.hasdir(b"a")
1425 1425
1426 1426 timer(d, setup=setup)
1427 1427 fm.end()
1428 1428
1429 1429
1430 1430 @command(b'perf::dirstatefoldmap|perfdirstatefoldmap', formatteropts)
1431 1431 def perfdirstatefoldmap(ui, repo, **opts):
1432 1432 """benchmap a `dirstate._map.filefoldmap.get()` request
1433 1433
1434 1434 The dirstate filefoldmap cache is dropped between every request.
1435 1435 """
1436 1436 opts = _byteskwargs(opts)
1437 1437 timer, fm = gettimer(ui, opts)
1438 1438 dirstate = repo.dirstate
1439 1439 dirstate._map.filefoldmap.get(b'a')
1440 1440
1441 1441 def setup():
1442 1442 del dirstate._map.filefoldmap
1443 1443
1444 1444 def d():
1445 1445 dirstate._map.filefoldmap.get(b'a')
1446 1446
1447 1447 timer(d, setup=setup)
1448 1448 fm.end()
1449 1449
1450 1450
1451 1451 @command(b'perf::dirfoldmap|perfdirfoldmap', formatteropts)
1452 1452 def perfdirfoldmap(ui, repo, **opts):
1453 1453 """benchmap a `dirstate._map.dirfoldmap.get()` request
1454 1454
1455 1455 The dirstate dirfoldmap cache is dropped between every request.
1456 1456 """
1457 1457 opts = _byteskwargs(opts)
1458 1458 timer, fm = gettimer(ui, opts)
1459 1459 dirstate = repo.dirstate
1460 1460 dirstate._map.dirfoldmap.get(b'a')
1461 1461
1462 1462 def setup():
1463 1463 del dirstate._map.dirfoldmap
1464 1464 try:
1465 1465 del dirstate._map._dirs
1466 1466 except AttributeError:
1467 1467 pass
1468 1468
1469 1469 def d():
1470 1470 dirstate._map.dirfoldmap.get(b'a')
1471 1471
1472 1472 timer(d, setup=setup)
1473 1473 fm.end()
1474 1474
1475 1475
1476 1476 @command(b'perf::dirstatewrite|perfdirstatewrite', formatteropts)
1477 1477 def perfdirstatewrite(ui, repo, **opts):
1478 1478 """benchmap the time it take to write a dirstate on disk"""
1479 1479 opts = _byteskwargs(opts)
1480 1480 timer, fm = gettimer(ui, opts)
1481 1481 ds = repo.dirstate
1482 1482 b"a" in ds
1483 1483
1484 1484 def setup():
1485 1485 ds._dirty = True
1486 1486
1487 1487 def d():
1488 1488 ds.write(repo.currenttransaction())
1489 1489
1490 1490 with repo.wlock():
1491 1491 timer(d, setup=setup)
1492 1492 fm.end()
1493 1493
1494 1494
1495 1495 def _getmergerevs(repo, opts):
1496 1496 """parse command argument to return rev involved in merge
1497 1497
1498 1498 input: options dictionnary with `rev`, `from` and `bse`
1499 1499 output: (localctx, otherctx, basectx)
1500 1500 """
1501 1501 if opts[b'from']:
1502 1502 fromrev = scmutil.revsingle(repo, opts[b'from'])
1503 1503 wctx = repo[fromrev]
1504 1504 else:
1505 1505 wctx = repo[None]
1506 1506 # we don't want working dir files to be stat'd in the benchmark, so
1507 1507 # prime that cache
1508 1508 wctx.dirty()
1509 1509 rctx = scmutil.revsingle(repo, opts[b'rev'], opts[b'rev'])
1510 1510 if opts[b'base']:
1511 1511 fromrev = scmutil.revsingle(repo, opts[b'base'])
1512 1512 ancestor = repo[fromrev]
1513 1513 else:
1514 1514 ancestor = wctx.ancestor(rctx)
1515 1515 return (wctx, rctx, ancestor)
1516 1516
1517 1517
1518 1518 @command(
1519 1519 b'perf::mergecalculate|perfmergecalculate',
1520 1520 [
1521 1521 (b'r', b'rev', b'.', b'rev to merge against'),
1522 1522 (b'', b'from', b'', b'rev to merge from'),
1523 1523 (b'', b'base', b'', b'the revision to use as base'),
1524 1524 ]
1525 1525 + formatteropts,
1526 1526 )
1527 1527 def perfmergecalculate(ui, repo, **opts):
1528 1528 opts = _byteskwargs(opts)
1529 1529 timer, fm = gettimer(ui, opts)
1530 1530
1531 1531 wctx, rctx, ancestor = _getmergerevs(repo, opts)
1532 1532
1533 1533 def d():
1534 1534 # acceptremote is True because we don't want prompts in the middle of
1535 1535 # our benchmark
1536 1536 merge.calculateupdates(
1537 1537 repo,
1538 1538 wctx,
1539 1539 rctx,
1540 1540 [ancestor],
1541 1541 branchmerge=False,
1542 1542 force=False,
1543 1543 acceptremote=True,
1544 1544 followcopies=True,
1545 1545 )
1546 1546
1547 1547 timer(d)
1548 1548 fm.end()
1549 1549
1550 1550
1551 1551 @command(
1552 1552 b'perf::mergecopies|perfmergecopies',
1553 1553 [
1554 1554 (b'r', b'rev', b'.', b'rev to merge against'),
1555 1555 (b'', b'from', b'', b'rev to merge from'),
1556 1556 (b'', b'base', b'', b'the revision to use as base'),
1557 1557 ]
1558 1558 + formatteropts,
1559 1559 )
1560 1560 def perfmergecopies(ui, repo, **opts):
1561 1561 """measure runtime of `copies.mergecopies`"""
1562 1562 opts = _byteskwargs(opts)
1563 1563 timer, fm = gettimer(ui, opts)
1564 1564 wctx, rctx, ancestor = _getmergerevs(repo, opts)
1565 1565
1566 1566 def d():
1567 1567 # acceptremote is True because we don't want prompts in the middle of
1568 1568 # our benchmark
1569 1569 copies.mergecopies(repo, wctx, rctx, ancestor)
1570 1570
1571 1571 timer(d)
1572 1572 fm.end()
1573 1573
1574 1574
1575 1575 @command(b'perf::pathcopies|perfpathcopies', [], b"REV REV")
1576 1576 def perfpathcopies(ui, repo, rev1, rev2, **opts):
1577 1577 """benchmark the copy tracing logic"""
1578 1578 opts = _byteskwargs(opts)
1579 1579 timer, fm = gettimer(ui, opts)
1580 1580 ctx1 = scmutil.revsingle(repo, rev1, rev1)
1581 1581 ctx2 = scmutil.revsingle(repo, rev2, rev2)
1582 1582
1583 1583 def d():
1584 1584 copies.pathcopies(ctx1, ctx2)
1585 1585
1586 1586 timer(d)
1587 1587 fm.end()
1588 1588
1589 1589
1590 1590 @command(
1591 1591 b'perf::phases|perfphases',
1592 1592 [
1593 1593 (b'', b'full', False, b'include file reading time too'),
1594 1594 ],
1595 1595 b"",
1596 1596 )
1597 1597 def perfphases(ui, repo, **opts):
1598 1598 """benchmark phasesets computation"""
1599 1599 opts = _byteskwargs(opts)
1600 1600 timer, fm = gettimer(ui, opts)
1601 1601 _phases = repo._phasecache
1602 1602 full = opts.get(b'full')
1603 1603
1604 1604 def d():
1605 1605 phases = _phases
1606 1606 if full:
1607 1607 clearfilecache(repo, b'_phasecache')
1608 1608 phases = repo._phasecache
1609 1609 phases.invalidate()
1610 1610 phases.loadphaserevs(repo)
1611 1611
1612 1612 timer(d)
1613 1613 fm.end()
1614 1614
1615 1615
1616 1616 @command(b'perf::phasesremote|perfphasesremote', [], b"[DEST]")
1617 1617 def perfphasesremote(ui, repo, dest=None, **opts):
1618 1618 """benchmark time needed to analyse phases of the remote server"""
1619 1619 from mercurial.node import bin
1620 1620 from mercurial import (
1621 1621 exchange,
1622 1622 hg,
1623 1623 phases,
1624 1624 )
1625 1625
1626 1626 opts = _byteskwargs(opts)
1627 1627 timer, fm = gettimer(ui, opts)
1628 1628
1629 1629 path = ui.getpath(dest, default=(b'default-push', b'default'))
1630 1630 if not path:
1631 1631 raise error.Abort(
1632 1632 b'default repository not configured!',
1633 1633 hint=b"see 'hg help config.paths'",
1634 1634 )
1635 1635 if util.safehasattr(path, 'main_path'):
1636 1636 path = path.get_push_variant()
1637 1637 dest = path.loc
1638 1638 else:
1639 1639 dest = path.pushloc or path.loc
1640 1640 ui.statusnoi18n(b'analysing phase of %s\n' % util.hidepassword(dest))
1641 1641 other = hg.peer(repo, opts, dest)
1642 1642
1643 1643 # easier to perform discovery through the operation
1644 1644 op = exchange.pushoperation(repo, other)
1645 1645 exchange._pushdiscoverychangeset(op)
1646 1646
1647 1647 remotesubset = op.fallbackheads
1648 1648
1649 1649 with other.commandexecutor() as e:
1650 1650 remotephases = e.callcommand(
1651 1651 b'listkeys', {b'namespace': b'phases'}
1652 1652 ).result()
1653 1653 del other
1654 1654 publishing = remotephases.get(b'publishing', False)
1655 1655 if publishing:
1656 1656 ui.statusnoi18n(b'publishing: yes\n')
1657 1657 else:
1658 1658 ui.statusnoi18n(b'publishing: no\n')
1659 1659
1660 1660 has_node = getattr(repo.changelog.index, 'has_node', None)
1661 1661 if has_node is None:
1662 1662 has_node = repo.changelog.nodemap.__contains__
1663 1663 nonpublishroots = 0
1664 1664 for nhex, phase in remotephases.iteritems():
1665 1665 if nhex == b'publishing': # ignore data related to publish option
1666 1666 continue
1667 1667 node = bin(nhex)
1668 1668 if has_node(node) and int(phase):
1669 1669 nonpublishroots += 1
1670 1670 ui.statusnoi18n(b'number of roots: %d\n' % len(remotephases))
1671 1671 ui.statusnoi18n(b'number of known non public roots: %d\n' % nonpublishroots)
1672 1672
1673 1673 def d():
1674 1674 phases.remotephasessummary(repo, remotesubset, remotephases)
1675 1675
1676 1676 timer(d)
1677 1677 fm.end()
1678 1678
1679 1679
1680 1680 @command(
1681 1681 b'perf::manifest|perfmanifest',
1682 1682 [
1683 1683 (b'm', b'manifest-rev', False, b'Look up a manifest node revision'),
1684 1684 (b'', b'clear-disk', False, b'clear on-disk caches too'),
1685 1685 ]
1686 1686 + formatteropts,
1687 1687 b'REV|NODE',
1688 1688 )
1689 1689 def perfmanifest(ui, repo, rev, manifest_rev=False, clear_disk=False, **opts):
1690 1690 """benchmark the time to read a manifest from disk and return a usable
1691 1691 dict-like object
1692 1692
1693 1693 Manifest caches are cleared before retrieval."""
1694 1694 opts = _byteskwargs(opts)
1695 1695 timer, fm = gettimer(ui, opts)
1696 1696 if not manifest_rev:
1697 1697 ctx = scmutil.revsingle(repo, rev, rev)
1698 1698 t = ctx.manifestnode()
1699 1699 else:
1700 1700 from mercurial.node import bin
1701 1701
1702 1702 if len(rev) == 40:
1703 1703 t = bin(rev)
1704 1704 else:
1705 1705 try:
1706 1706 rev = int(rev)
1707 1707
1708 1708 if util.safehasattr(repo.manifestlog, b'getstorage'):
1709 1709 t = repo.manifestlog.getstorage(b'').node(rev)
1710 1710 else:
1711 1711 t = repo.manifestlog._revlog.lookup(rev)
1712 1712 except ValueError:
1713 1713 raise error.Abort(
1714 1714 b'manifest revision must be integer or full node'
1715 1715 )
1716 1716
1717 1717 def d():
1718 1718 repo.manifestlog.clearcaches(clear_persisted_data=clear_disk)
1719 1719 repo.manifestlog[t].read()
1720 1720
1721 1721 timer(d)
1722 1722 fm.end()
1723 1723
1724 1724
1725 1725 @command(b'perf::changeset|perfchangeset', formatteropts)
1726 1726 def perfchangeset(ui, repo, rev, **opts):
1727 1727 opts = _byteskwargs(opts)
1728 1728 timer, fm = gettimer(ui, opts)
1729 1729 n = scmutil.revsingle(repo, rev).node()
1730 1730
1731 1731 def d():
1732 1732 repo.changelog.read(n)
1733 1733 # repo.changelog._cache = None
1734 1734
1735 1735 timer(d)
1736 1736 fm.end()
1737 1737
1738 1738
1739 1739 @command(b'perf::ignore|perfignore', formatteropts)
1740 1740 def perfignore(ui, repo, **opts):
1741 1741 """benchmark operation related to computing ignore"""
1742 1742 opts = _byteskwargs(opts)
1743 1743 timer, fm = gettimer(ui, opts)
1744 1744 dirstate = repo.dirstate
1745 1745
1746 1746 def setupone():
1747 1747 dirstate.invalidate()
1748 1748 clearfilecache(dirstate, b'_ignore')
1749 1749
1750 1750 def runone():
1751 1751 dirstate._ignore
1752 1752
1753 1753 timer(runone, setup=setupone, title=b"load")
1754 1754 fm.end()
1755 1755
1756 1756
1757 1757 @command(
1758 1758 b'perf::index|perfindex',
1759 1759 [
1760 1760 (b'', b'rev', [], b'revision to be looked up (default tip)'),
1761 1761 (b'', b'no-lookup', None, b'do not revision lookup post creation'),
1762 1762 ]
1763 1763 + formatteropts,
1764 1764 )
1765 1765 def perfindex(ui, repo, **opts):
1766 1766 """benchmark index creation time followed by a lookup
1767 1767
1768 1768 The default is to look `tip` up. Depending on the index implementation,
1769 1769 the revision looked up can matters. For example, an implementation
1770 1770 scanning the index will have a faster lookup time for `--rev tip` than for
1771 1771 `--rev 0`. The number of looked up revisions and their order can also
1772 1772 matters.
1773 1773
1774 1774 Example of useful set to test:
1775 1775
1776 1776 * tip
1777 1777 * 0
1778 1778 * -10:
1779 1779 * :10
1780 1780 * -10: + :10
1781 1781 * :10: + -10:
1782 1782 * -10000:
1783 1783 * -10000: + 0
1784 1784
1785 1785 It is not currently possible to check for lookup of a missing node. For
1786 1786 deeper lookup benchmarking, checkout the `perfnodemap` command."""
1787 1787 import mercurial.revlog
1788 1788
1789 1789 opts = _byteskwargs(opts)
1790 1790 timer, fm = gettimer(ui, opts)
1791 1791 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1792 1792 if opts[b'no_lookup']:
1793 1793 if opts['rev']:
1794 1794 raise error.Abort('--no-lookup and --rev are mutually exclusive')
1795 1795 nodes = []
1796 1796 elif not opts[b'rev']:
1797 1797 nodes = [repo[b"tip"].node()]
1798 1798 else:
1799 1799 revs = scmutil.revrange(repo, opts[b'rev'])
1800 1800 cl = repo.changelog
1801 1801 nodes = [cl.node(r) for r in revs]
1802 1802
1803 1803 unfi = repo.unfiltered()
1804 1804 # find the filecache func directly
1805 1805 # This avoid polluting the benchmark with the filecache logic
1806 1806 makecl = unfi.__class__.changelog.func
1807 1807
1808 1808 def setup():
1809 1809 # probably not necessary, but for good measure
1810 1810 clearchangelog(unfi)
1811 1811
1812 1812 def d():
1813 1813 cl = makecl(unfi)
1814 1814 for n in nodes:
1815 1815 cl.rev(n)
1816 1816
1817 1817 timer(d, setup=setup)
1818 1818 fm.end()
1819 1819
1820 1820
1821 1821 @command(
1822 1822 b'perf::nodemap|perfnodemap',
1823 1823 [
1824 1824 (b'', b'rev', [], b'revision to be looked up (default tip)'),
1825 1825 (b'', b'clear-caches', True, b'clear revlog cache between calls'),
1826 1826 ]
1827 1827 + formatteropts,
1828 1828 )
1829 1829 def perfnodemap(ui, repo, **opts):
1830 1830 """benchmark the time necessary to look up revision from a cold nodemap
1831 1831
1832 1832 Depending on the implementation, the amount and order of revision we look
1833 1833 up can varies. Example of useful set to test:
1834 1834 * tip
1835 1835 * 0
1836 1836 * -10:
1837 1837 * :10
1838 1838 * -10: + :10
1839 1839 * :10: + -10:
1840 1840 * -10000:
1841 1841 * -10000: + 0
1842 1842
1843 1843 The command currently focus on valid binary lookup. Benchmarking for
1844 1844 hexlookup, prefix lookup and missing lookup would also be valuable.
1845 1845 """
1846 1846 import mercurial.revlog
1847 1847
1848 1848 opts = _byteskwargs(opts)
1849 1849 timer, fm = gettimer(ui, opts)
1850 1850 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1851 1851
1852 1852 unfi = repo.unfiltered()
1853 1853 clearcaches = opts[b'clear_caches']
1854 1854 # find the filecache func directly
1855 1855 # This avoid polluting the benchmark with the filecache logic
1856 1856 makecl = unfi.__class__.changelog.func
1857 1857 if not opts[b'rev']:
1858 1858 raise error.Abort(b'use --rev to specify revisions to look up')
1859 1859 revs = scmutil.revrange(repo, opts[b'rev'])
1860 1860 cl = repo.changelog
1861 1861 nodes = [cl.node(r) for r in revs]
1862 1862
1863 1863 # use a list to pass reference to a nodemap from one closure to the next
1864 1864 nodeget = [None]
1865 1865
1866 1866 def setnodeget():
1867 1867 # probably not necessary, but for good measure
1868 1868 clearchangelog(unfi)
1869 1869 cl = makecl(unfi)
1870 1870 if util.safehasattr(cl.index, 'get_rev'):
1871 1871 nodeget[0] = cl.index.get_rev
1872 1872 else:
1873 1873 nodeget[0] = cl.nodemap.get
1874 1874
1875 1875 def d():
1876 1876 get = nodeget[0]
1877 1877 for n in nodes:
1878 1878 get(n)
1879 1879
1880 1880 setup = None
1881 1881 if clearcaches:
1882 1882
1883 1883 def setup():
1884 1884 setnodeget()
1885 1885
1886 1886 else:
1887 1887 setnodeget()
1888 1888 d() # prewarm the data structure
1889 1889 timer(d, setup=setup)
1890 1890 fm.end()
1891 1891
1892 1892
1893 1893 @command(b'perf::startup|perfstartup', formatteropts)
1894 1894 def perfstartup(ui, repo, **opts):
1895 1895 opts = _byteskwargs(opts)
1896 1896 timer, fm = gettimer(ui, opts)
1897 1897
1898 1898 def d():
1899 1899 if os.name != 'nt':
1900 1900 os.system(
1901 1901 b"HGRCPATH= %s version -q > /dev/null" % fsencode(sys.argv[0])
1902 1902 )
1903 1903 else:
1904 1904 os.environ['HGRCPATH'] = r' '
1905 1905 os.system("%s version -q > NUL" % sys.argv[0])
1906 1906
1907 1907 timer(d)
1908 1908 fm.end()
1909 1909
1910 1910
1911 1911 def _find_stream_generator(version):
1912 1912 """find the proper generator function for this stream version"""
1913 1913 import mercurial.streamclone
1914 1914
1915 1915 available = {}
1916 1916
1917 1917 # try to fetch a v1 generator
1918 1918 generatev1 = getattr(mercurial.streamclone, "generatev1", None)
1919 1919 if generatev1 is not None:
1920 1920
1921 1921 def generate(repo):
1922 1922 entries, bytes, data = generatev2(repo, None, None, True)
1923 1923 return data
1924 1924
1925 1925 available[b'v1'] = generatev1
1926 1926 # try to fetch a v2 generator
1927 1927 generatev2 = getattr(mercurial.streamclone, "generatev2", None)
1928 1928 if generatev2 is not None:
1929 1929
1930 1930 def generate(repo):
1931 1931 entries, bytes, data = generatev2(repo, None, None, True)
1932 1932 return data
1933 1933
1934 1934 available[b'v2'] = generate
1935 1935 # try to fetch a v3 generator
1936 1936 generatev3 = getattr(mercurial.streamclone, "generatev3", None)
1937 1937 if generatev3 is not None:
1938 1938
1939 1939 def generate(repo):
1940 1940 entries, bytes, data = generatev3(repo, None, None, True)
1941 1941 return data
1942 1942
1943 1943 available[b'v3-exp'] = generate
1944 1944
1945 1945 # resolve the request
1946 1946 if version == b"latest":
1947 1947 # latest is the highest non experimental version
1948 1948 latest_key = max(v for v in available if b'-exp' not in v)
1949 1949 return available[latest_key]
1950 1950 elif version in available:
1951 1951 return available[version]
1952 1952 else:
1953 1953 msg = b"unkown or unavailable version: %s"
1954 1954 msg %= version
1955 1955 hint = b"available versions: %s"
1956 1956 hint %= b', '.join(sorted(available))
1957 1957 raise error.Abort(msg, hint=hint)
1958 1958
1959 1959
1960 1960 @command(
1961 1961 b'perf::stream-locked-section',
1962 1962 [
1963 1963 (
1964 1964 b'',
1965 1965 b'stream-version',
1966 1966 b'latest',
1967 1967 b'stream version to use ("v1", "v2", "v3" or "latest", (the default))',
1968 1968 ),
1969 1969 ]
1970 1970 + formatteropts,
1971 1971 )
1972 1972 def perf_stream_clone_scan(ui, repo, stream_version, **opts):
1973 1973 """benchmark the initial, repo-locked, section of a stream-clone"""
1974 1974
1975 1975 opts = _byteskwargs(opts)
1976 1976 timer, fm = gettimer(ui, opts)
1977 1977
1978 1978 # deletion of the generator may trigger some cleanup that we do not want to
1979 1979 # measure
1980 1980 result_holder = [None]
1981 1981
1982 1982 def setupone():
1983 1983 result_holder[0] = None
1984 1984
1985 1985 generate = _find_stream_generator(stream_version)
1986 1986
1987 1987 def runone():
1988 1988 # the lock is held for the duration the initialisation
1989 1989 result_holder[0] = generate(repo)
1990 1990
1991 1991 timer(runone, setup=setupone, title=b"load")
1992 1992 fm.end()
1993 1993
1994 1994
1995 1995 @command(
1996 1996 b'perf::stream-generate',
1997 1997 [
1998 1998 (
1999 1999 b'',
2000 2000 b'stream-version',
2001 2001 b'latest',
2002 2002 b'stream version to us ("v1", "v2" or "latest", (the default))',
2003 2003 ),
2004 2004 ]
2005 2005 + formatteropts,
2006 2006 )
2007 2007 def perf_stream_clone_generate(ui, repo, stream_version, **opts):
2008 2008 """benchmark the full generation of a stream clone"""
2009 2009
2010 2010 opts = _byteskwargs(opts)
2011 2011 timer, fm = gettimer(ui, opts)
2012 2012
2013 2013 # deletion of the generator may trigger some cleanup that we do not want to
2014 2014 # measure
2015 2015
2016 2016 generate = _find_stream_generator(stream_version)
2017 2017
2018 2018 def runone():
2019 2019 # the lock is held for the duration the initialisation
2020 2020 for chunk in generate(repo):
2021 2021 pass
2022 2022
2023 2023 timer(runone, title=b"generate")
2024 2024 fm.end()
2025 2025
2026 2026
2027 2027 @command(
2028 2028 b'perf::stream-consume',
2029 2029 formatteropts,
2030 2030 )
2031 2031 def perf_stream_clone_consume(ui, repo, filename, **opts):
2032 2032 """benchmark the full application of a stream clone
2033 2033
2034 2034 This include the creation of the repository
2035 2035 """
2036 2036 # try except to appease check code
2037 2037 msg = b"mercurial too old, missing necessary module: %s"
2038 2038 try:
2039 2039 from mercurial import bundle2
2040 2040 except ImportError as exc:
2041 2041 msg %= _bytestr(exc)
2042 2042 raise error.Abort(msg)
2043 2043 try:
2044 2044 from mercurial import exchange
2045 2045 except ImportError as exc:
2046 2046 msg %= _bytestr(exc)
2047 2047 raise error.Abort(msg)
2048 2048 try:
2049 2049 from mercurial import hg
2050 2050 except ImportError as exc:
2051 2051 msg %= _bytestr(exc)
2052 2052 raise error.Abort(msg)
2053 2053 try:
2054 2054 from mercurial import localrepo
2055 2055 except ImportError as exc:
2056 2056 msg %= _bytestr(exc)
2057 2057 raise error.Abort(msg)
2058 2058
2059 2059 opts = _byteskwargs(opts)
2060 2060 timer, fm = gettimer(ui, opts)
2061 2061
2062 2062 # deletion of the generator may trigger some cleanup that we do not want to
2063 2063 # measure
2064 2064 if not (os.path.isfile(filename) and os.access(filename, os.R_OK)):
2065 2065 raise error.Abort("not a readable file: %s" % filename)
2066 2066
2067 2067 run_variables = [None, None]
2068 2068
2069 2069 @contextlib.contextmanager
2070 2070 def context():
2071 2071 with open(filename, mode='rb') as bundle:
2072 2072 with tempfile.TemporaryDirectory() as tmp_dir:
2073 2073 tmp_dir = fsencode(tmp_dir)
2074 2074 run_variables[0] = bundle
2075 2075 run_variables[1] = tmp_dir
2076 2076 yield
2077 2077 run_variables[0] = None
2078 2078 run_variables[1] = None
2079 2079
2080 2080 def runone():
2081 2081 bundle = run_variables[0]
2082 2082 tmp_dir = run_variables[1]
2083 2083 # only pass ui when no srcrepo
2084 2084 localrepo.createrepository(
2085 2085 repo.ui, tmp_dir, requirements=repo.requirements
2086 2086 )
2087 2087 target = hg.repository(repo.ui, tmp_dir)
2088 2088 gen = exchange.readbundle(target.ui, bundle, bundle.name)
2089 2089 # stream v1
2090 2090 if util.safehasattr(gen, 'apply'):
2091 2091 gen.apply(target)
2092 2092 else:
2093 2093 with target.transaction(b"perf::stream-consume") as tr:
2094 2094 bundle2.applybundle(
2095 2095 target,
2096 2096 gen,
2097 2097 tr,
2098 2098 source=b'unbundle',
2099 2099 url=filename,
2100 2100 )
2101 2101
2102 2102 timer(runone, context=context, title=b"consume")
2103 2103 fm.end()
2104 2104
2105 2105
2106 2106 @command(b'perf::parents|perfparents', formatteropts)
2107 2107 def perfparents(ui, repo, **opts):
2108 2108 """benchmark the time necessary to fetch one changeset's parents.
2109 2109
2110 2110 The fetch is done using the `node identifier`, traversing all object layers
2111 2111 from the repository object. The first N revisions will be used for this
2112 2112 benchmark. N is controlled by the ``perf.parentscount`` config option
2113 2113 (default: 1000).
2114 2114 """
2115 2115 opts = _byteskwargs(opts)
2116 2116 timer, fm = gettimer(ui, opts)
2117 2117 # control the number of commits perfparents iterates over
2118 2118 # experimental config: perf.parentscount
2119 2119 count = getint(ui, b"perf", b"parentscount", 1000)
2120 2120 if len(repo.changelog) < count:
2121 2121 raise error.Abort(b"repo needs %d commits for this test" % count)
2122 2122 repo = repo.unfiltered()
2123 2123 nl = [repo.changelog.node(i) for i in _xrange(count)]
2124 2124
2125 2125 def d():
2126 2126 for n in nl:
2127 2127 repo.changelog.parents(n)
2128 2128
2129 2129 timer(d)
2130 2130 fm.end()
2131 2131
2132 2132
2133 2133 @command(b'perf::ctxfiles|perfctxfiles', formatteropts)
2134 2134 def perfctxfiles(ui, repo, x, **opts):
2135 2135 opts = _byteskwargs(opts)
2136 2136 x = int(x)
2137 2137 timer, fm = gettimer(ui, opts)
2138 2138
2139 2139 def d():
2140 2140 len(repo[x].files())
2141 2141
2142 2142 timer(d)
2143 2143 fm.end()
2144 2144
2145 2145
2146 2146 @command(b'perf::rawfiles|perfrawfiles', formatteropts)
2147 2147 def perfrawfiles(ui, repo, x, **opts):
2148 2148 opts = _byteskwargs(opts)
2149 2149 x = int(x)
2150 2150 timer, fm = gettimer(ui, opts)
2151 2151 cl = repo.changelog
2152 2152
2153 2153 def d():
2154 2154 len(cl.read(x)[3])
2155 2155
2156 2156 timer(d)
2157 2157 fm.end()
2158 2158
2159 2159
2160 2160 @command(b'perf::lookup|perflookup', formatteropts)
2161 2161 def perflookup(ui, repo, rev, **opts):
2162 2162 opts = _byteskwargs(opts)
2163 2163 timer, fm = gettimer(ui, opts)
2164 2164 timer(lambda: len(repo.lookup(rev)))
2165 2165 fm.end()
2166 2166
2167 2167
2168 2168 @command(
2169 2169 b'perf::linelogedits|perflinelogedits',
2170 2170 [
2171 2171 (b'n', b'edits', 10000, b'number of edits'),
2172 2172 (b'', b'max-hunk-lines', 10, b'max lines in a hunk'),
2173 2173 ],
2174 2174 norepo=True,
2175 2175 )
2176 2176 def perflinelogedits(ui, **opts):
2177 2177 from mercurial import linelog
2178 2178
2179 2179 opts = _byteskwargs(opts)
2180 2180
2181 2181 edits = opts[b'edits']
2182 2182 maxhunklines = opts[b'max_hunk_lines']
2183 2183
2184 2184 maxb1 = 100000
2185 2185 random.seed(0)
2186 2186 randint = random.randint
2187 2187 currentlines = 0
2188 2188 arglist = []
2189 2189 for rev in _xrange(edits):
2190 2190 a1 = randint(0, currentlines)
2191 2191 a2 = randint(a1, min(currentlines, a1 + maxhunklines))
2192 2192 b1 = randint(0, maxb1)
2193 2193 b2 = randint(b1, b1 + maxhunklines)
2194 2194 currentlines += (b2 - b1) - (a2 - a1)
2195 2195 arglist.append((rev, a1, a2, b1, b2))
2196 2196
2197 2197 def d():
2198 2198 ll = linelog.linelog()
2199 2199 for args in arglist:
2200 2200 ll.replacelines(*args)
2201 2201
2202 2202 timer, fm = gettimer(ui, opts)
2203 2203 timer(d)
2204 2204 fm.end()
2205 2205
2206 2206
2207 2207 @command(b'perf::revrange|perfrevrange', formatteropts)
2208 2208 def perfrevrange(ui, repo, *specs, **opts):
2209 2209 opts = _byteskwargs(opts)
2210 2210 timer, fm = gettimer(ui, opts)
2211 2211 revrange = scmutil.revrange
2212 2212 timer(lambda: len(revrange(repo, specs)))
2213 2213 fm.end()
2214 2214
2215 2215
2216 2216 @command(b'perf::nodelookup|perfnodelookup', formatteropts)
2217 2217 def perfnodelookup(ui, repo, rev, **opts):
2218 2218 opts = _byteskwargs(opts)
2219 2219 timer, fm = gettimer(ui, opts)
2220 2220 import mercurial.revlog
2221 2221
2222 2222 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
2223 2223 n = scmutil.revsingle(repo, rev).node()
2224 2224
2225 2225 try:
2226 2226 cl = revlog(getsvfs(repo), radix=b"00changelog")
2227 2227 except TypeError:
2228 2228 cl = revlog(getsvfs(repo), indexfile=b"00changelog.i")
2229 2229
2230 2230 def d():
2231 2231 cl.rev(n)
2232 2232 clearcaches(cl)
2233 2233
2234 2234 timer(d)
2235 2235 fm.end()
2236 2236
2237 2237
2238 2238 @command(
2239 2239 b'perf::log|perflog',
2240 2240 [(b'', b'rename', False, b'ask log to follow renames')] + formatteropts,
2241 2241 )
2242 2242 def perflog(ui, repo, rev=None, **opts):
2243 2243 opts = _byteskwargs(opts)
2244 2244 if rev is None:
2245 2245 rev = []
2246 2246 timer, fm = gettimer(ui, opts)
2247 2247 ui.pushbuffer()
2248 2248 timer(
2249 2249 lambda: commands.log(
2250 2250 ui, repo, rev=rev, date=b'', user=b'', copies=opts.get(b'rename')
2251 2251 )
2252 2252 )
2253 2253 ui.popbuffer()
2254 2254 fm.end()
2255 2255
2256 2256
2257 2257 @command(b'perf::moonwalk|perfmoonwalk', formatteropts)
2258 2258 def perfmoonwalk(ui, repo, **opts):
2259 2259 """benchmark walking the changelog backwards
2260 2260
2261 2261 This also loads the changelog data for each revision in the changelog.
2262 2262 """
2263 2263 opts = _byteskwargs(opts)
2264 2264 timer, fm = gettimer(ui, opts)
2265 2265
2266 2266 def moonwalk():
2267 2267 for i in repo.changelog.revs(start=(len(repo) - 1), stop=-1):
2268 2268 ctx = repo[i]
2269 2269 ctx.branch() # read changelog data (in addition to the index)
2270 2270
2271 2271 timer(moonwalk)
2272 2272 fm.end()
2273 2273
2274 2274
2275 2275 @command(
2276 2276 b'perf::templating|perftemplating',
2277 2277 [
2278 2278 (b'r', b'rev', [], b'revisions to run the template on'),
2279 2279 ]
2280 2280 + formatteropts,
2281 2281 )
2282 2282 def perftemplating(ui, repo, testedtemplate=None, **opts):
2283 2283 """test the rendering time of a given template"""
2284 2284 if makelogtemplater is None:
2285 2285 raise error.Abort(
2286 2286 b"perftemplating not available with this Mercurial",
2287 2287 hint=b"use 4.3 or later",
2288 2288 )
2289 2289
2290 2290 opts = _byteskwargs(opts)
2291 2291
2292 2292 nullui = ui.copy()
2293 2293 nullui.fout = open(os.devnull, 'wb')
2294 2294 nullui.disablepager()
2295 2295 revs = opts.get(b'rev')
2296 2296 if not revs:
2297 2297 revs = [b'all()']
2298 2298 revs = list(scmutil.revrange(repo, revs))
2299 2299
2300 2300 defaulttemplate = (
2301 2301 b'{date|shortdate} [{rev}:{node|short}]'
2302 2302 b' {author|person}: {desc|firstline}\n'
2303 2303 )
2304 2304 if testedtemplate is None:
2305 2305 testedtemplate = defaulttemplate
2306 2306 displayer = makelogtemplater(nullui, repo, testedtemplate)
2307 2307
2308 2308 def format():
2309 2309 for r in revs:
2310 2310 ctx = repo[r]
2311 2311 displayer.show(ctx)
2312 2312 displayer.flush(ctx)
2313 2313
2314 2314 timer, fm = gettimer(ui, opts)
2315 2315 timer(format)
2316 2316 fm.end()
2317 2317
2318 2318
2319 2319 def _displaystats(ui, opts, entries, data):
2320 2320 # use a second formatter because the data are quite different, not sure
2321 2321 # how it flies with the templater.
2322 2322 fm = ui.formatter(b'perf-stats', opts)
2323 2323 for key, title in entries:
2324 2324 values = data[key]
2325 2325 nbvalues = len(data)
2326 2326 values.sort()
2327 2327 stats = {
2328 2328 'key': key,
2329 2329 'title': title,
2330 2330 'nbitems': len(values),
2331 2331 'min': values[0][0],
2332 2332 '10%': values[(nbvalues * 10) // 100][0],
2333 2333 '25%': values[(nbvalues * 25) // 100][0],
2334 2334 '50%': values[(nbvalues * 50) // 100][0],
2335 2335 '75%': values[(nbvalues * 75) // 100][0],
2336 2336 '80%': values[(nbvalues * 80) // 100][0],
2337 2337 '85%': values[(nbvalues * 85) // 100][0],
2338 2338 '90%': values[(nbvalues * 90) // 100][0],
2339 2339 '95%': values[(nbvalues * 95) // 100][0],
2340 2340 '99%': values[(nbvalues * 99) // 100][0],
2341 2341 'max': values[-1][0],
2342 2342 }
2343 2343 fm.startitem()
2344 2344 fm.data(**stats)
2345 2345 # make node pretty for the human output
2346 2346 fm.plain('### %s (%d items)\n' % (title, len(values)))
2347 2347 lines = [
2348 2348 'min',
2349 2349 '10%',
2350 2350 '25%',
2351 2351 '50%',
2352 2352 '75%',
2353 2353 '80%',
2354 2354 '85%',
2355 2355 '90%',
2356 2356 '95%',
2357 2357 '99%',
2358 2358 'max',
2359 2359 ]
2360 2360 for l in lines:
2361 2361 fm.plain('%s: %s\n' % (l, stats[l]))
2362 2362 fm.end()
2363 2363
2364 2364
2365 2365 @command(
2366 2366 b'perf::helper-mergecopies|perfhelper-mergecopies',
2367 2367 formatteropts
2368 2368 + [
2369 2369 (b'r', b'revs', [], b'restrict search to these revisions'),
2370 2370 (b'', b'timing', False, b'provides extra data (costly)'),
2371 2371 (b'', b'stats', False, b'provides statistic about the measured data'),
2372 2372 ],
2373 2373 )
2374 2374 def perfhelpermergecopies(ui, repo, revs=[], **opts):
2375 2375 """find statistics about potential parameters for `perfmergecopies`
2376 2376
2377 2377 This command find (base, p1, p2) triplet relevant for copytracing
2378 2378 benchmarking in the context of a merge. It reports values for some of the
2379 2379 parameters that impact merge copy tracing time during merge.
2380 2380
2381 2381 If `--timing` is set, rename detection is run and the associated timing
2382 2382 will be reported. The extra details come at the cost of slower command
2383 2383 execution.
2384 2384
2385 2385 Since rename detection is only run once, other factors might easily
2386 2386 affect the precision of the timing. However it should give a good
2387 2387 approximation of which revision triplets are very costly.
2388 2388 """
2389 2389 opts = _byteskwargs(opts)
2390 2390 fm = ui.formatter(b'perf', opts)
2391 2391 dotiming = opts[b'timing']
2392 2392 dostats = opts[b'stats']
2393 2393
2394 2394 output_template = [
2395 2395 ("base", "%(base)12s"),
2396 2396 ("p1", "%(p1.node)12s"),
2397 2397 ("p2", "%(p2.node)12s"),
2398 2398 ("p1.nb-revs", "%(p1.nbrevs)12d"),
2399 2399 ("p1.nb-files", "%(p1.nbmissingfiles)12d"),
2400 2400 ("p1.renames", "%(p1.renamedfiles)12d"),
2401 2401 ("p1.time", "%(p1.time)12.3f"),
2402 2402 ("p2.nb-revs", "%(p2.nbrevs)12d"),
2403 2403 ("p2.nb-files", "%(p2.nbmissingfiles)12d"),
2404 2404 ("p2.renames", "%(p2.renamedfiles)12d"),
2405 2405 ("p2.time", "%(p2.time)12.3f"),
2406 2406 ("renames", "%(nbrenamedfiles)12d"),
2407 2407 ("total.time", "%(time)12.3f"),
2408 2408 ]
2409 2409 if not dotiming:
2410 2410 output_template = [
2411 2411 i
2412 2412 for i in output_template
2413 2413 if not ('time' in i[0] or 'renames' in i[0])
2414 2414 ]
2415 2415 header_names = [h for (h, v) in output_template]
2416 2416 output = ' '.join([v for (h, v) in output_template]) + '\n'
2417 2417 header = ' '.join(['%12s'] * len(header_names)) + '\n'
2418 2418 fm.plain(header % tuple(header_names))
2419 2419
2420 2420 if not revs:
2421 2421 revs = ['all()']
2422 2422 revs = scmutil.revrange(repo, revs)
2423 2423
2424 2424 if dostats:
2425 2425 alldata = {
2426 2426 'nbrevs': [],
2427 2427 'nbmissingfiles': [],
2428 2428 }
2429 2429 if dotiming:
2430 2430 alldata['parentnbrenames'] = []
2431 2431 alldata['totalnbrenames'] = []
2432 2432 alldata['parenttime'] = []
2433 2433 alldata['totaltime'] = []
2434 2434
2435 2435 roi = repo.revs('merge() and %ld', revs)
2436 2436 for r in roi:
2437 2437 ctx = repo[r]
2438 2438 p1 = ctx.p1()
2439 2439 p2 = ctx.p2()
2440 2440 bases = repo.changelog._commonancestorsheads(p1.rev(), p2.rev())
2441 2441 for b in bases:
2442 2442 b = repo[b]
2443 2443 p1missing = copies._computeforwardmissing(b, p1)
2444 2444 p2missing = copies._computeforwardmissing(b, p2)
2445 2445 data = {
2446 2446 b'base': b.hex(),
2447 2447 b'p1.node': p1.hex(),
2448 2448 b'p1.nbrevs': len(repo.revs('only(%d, %d)', p1.rev(), b.rev())),
2449 2449 b'p1.nbmissingfiles': len(p1missing),
2450 2450 b'p2.node': p2.hex(),
2451 2451 b'p2.nbrevs': len(repo.revs('only(%d, %d)', p2.rev(), b.rev())),
2452 2452 b'p2.nbmissingfiles': len(p2missing),
2453 2453 }
2454 2454 if dostats:
2455 2455 if p1missing:
2456 2456 alldata['nbrevs'].append(
2457 2457 (data['p1.nbrevs'], b.hex(), p1.hex())
2458 2458 )
2459 2459 alldata['nbmissingfiles'].append(
2460 2460 (data['p1.nbmissingfiles'], b.hex(), p1.hex())
2461 2461 )
2462 2462 if p2missing:
2463 2463 alldata['nbrevs'].append(
2464 2464 (data['p2.nbrevs'], b.hex(), p2.hex())
2465 2465 )
2466 2466 alldata['nbmissingfiles'].append(
2467 2467 (data['p2.nbmissingfiles'], b.hex(), p2.hex())
2468 2468 )
2469 2469 if dotiming:
2470 2470 begin = util.timer()
2471 2471 mergedata = copies.mergecopies(repo, p1, p2, b)
2472 2472 end = util.timer()
2473 2473 # not very stable timing since we did only one run
2474 2474 data['time'] = end - begin
2475 2475 # mergedata contains five dicts: "copy", "movewithdir",
2476 2476 # "diverge", "renamedelete" and "dirmove".
2477 2477 # The first 4 are about renamed file so lets count that.
2478 2478 renames = len(mergedata[0])
2479 2479 renames += len(mergedata[1])
2480 2480 renames += len(mergedata[2])
2481 2481 renames += len(mergedata[3])
2482 2482 data['nbrenamedfiles'] = renames
2483 2483 begin = util.timer()
2484 2484 p1renames = copies.pathcopies(b, p1)
2485 2485 end = util.timer()
2486 2486 data['p1.time'] = end - begin
2487 2487 begin = util.timer()
2488 2488 p2renames = copies.pathcopies(b, p2)
2489 2489 end = util.timer()
2490 2490 data['p2.time'] = end - begin
2491 2491 data['p1.renamedfiles'] = len(p1renames)
2492 2492 data['p2.renamedfiles'] = len(p2renames)
2493 2493
2494 2494 if dostats:
2495 2495 if p1missing:
2496 2496 alldata['parentnbrenames'].append(
2497 2497 (data['p1.renamedfiles'], b.hex(), p1.hex())
2498 2498 )
2499 2499 alldata['parenttime'].append(
2500 2500 (data['p1.time'], b.hex(), p1.hex())
2501 2501 )
2502 2502 if p2missing:
2503 2503 alldata['parentnbrenames'].append(
2504 2504 (data['p2.renamedfiles'], b.hex(), p2.hex())
2505 2505 )
2506 2506 alldata['parenttime'].append(
2507 2507 (data['p2.time'], b.hex(), p2.hex())
2508 2508 )
2509 2509 if p1missing or p2missing:
2510 2510 alldata['totalnbrenames'].append(
2511 2511 (
2512 2512 data['nbrenamedfiles'],
2513 2513 b.hex(),
2514 2514 p1.hex(),
2515 2515 p2.hex(),
2516 2516 )
2517 2517 )
2518 2518 alldata['totaltime'].append(
2519 2519 (data['time'], b.hex(), p1.hex(), p2.hex())
2520 2520 )
2521 2521 fm.startitem()
2522 2522 fm.data(**data)
2523 2523 # make node pretty for the human output
2524 2524 out = data.copy()
2525 2525 out['base'] = fm.hexfunc(b.node())
2526 2526 out['p1.node'] = fm.hexfunc(p1.node())
2527 2527 out['p2.node'] = fm.hexfunc(p2.node())
2528 2528 fm.plain(output % out)
2529 2529
2530 2530 fm.end()
2531 2531 if dostats:
2532 2532 # use a second formatter because the data are quite different, not sure
2533 2533 # how it flies with the templater.
2534 2534 entries = [
2535 2535 ('nbrevs', 'number of revision covered'),
2536 2536 ('nbmissingfiles', 'number of missing files at head'),
2537 2537 ]
2538 2538 if dotiming:
2539 2539 entries.append(
2540 2540 ('parentnbrenames', 'rename from one parent to base')
2541 2541 )
2542 2542 entries.append(('totalnbrenames', 'total number of renames'))
2543 2543 entries.append(('parenttime', 'time for one parent'))
2544 2544 entries.append(('totaltime', 'time for both parents'))
2545 2545 _displaystats(ui, opts, entries, alldata)
2546 2546
2547 2547
2548 2548 @command(
2549 2549 b'perf::helper-pathcopies|perfhelper-pathcopies',
2550 2550 formatteropts
2551 2551 + [
2552 2552 (b'r', b'revs', [], b'restrict search to these revisions'),
2553 2553 (b'', b'timing', False, b'provides extra data (costly)'),
2554 2554 (b'', b'stats', False, b'provides statistic about the measured data'),
2555 2555 ],
2556 2556 )
2557 2557 def perfhelperpathcopies(ui, repo, revs=[], **opts):
2558 2558 """find statistic about potential parameters for the `perftracecopies`
2559 2559
2560 2560 This command find source-destination pair relevant for copytracing testing.
2561 2561 It report value for some of the parameters that impact copy tracing time.
2562 2562
2563 2563 If `--timing` is set, rename detection is run and the associated timing
2564 2564 will be reported. The extra details comes at the cost of a slower command
2565 2565 execution.
2566 2566
2567 2567 Since the rename detection is only run once, other factors might easily
2568 2568 affect the precision of the timing. However it should give a good
2569 2569 approximation of which revision pairs are very costly.
2570 2570 """
2571 2571 opts = _byteskwargs(opts)
2572 2572 fm = ui.formatter(b'perf', opts)
2573 2573 dotiming = opts[b'timing']
2574 2574 dostats = opts[b'stats']
2575 2575
2576 2576 if dotiming:
2577 2577 header = '%12s %12s %12s %12s %12s %12s\n'
2578 2578 output = (
2579 2579 "%(source)12s %(destination)12s "
2580 2580 "%(nbrevs)12d %(nbmissingfiles)12d "
2581 2581 "%(nbrenamedfiles)12d %(time)18.5f\n"
2582 2582 )
2583 2583 header_names = (
2584 2584 "source",
2585 2585 "destination",
2586 2586 "nb-revs",
2587 2587 "nb-files",
2588 2588 "nb-renames",
2589 2589 "time",
2590 2590 )
2591 2591 fm.plain(header % header_names)
2592 2592 else:
2593 2593 header = '%12s %12s %12s %12s\n'
2594 2594 output = (
2595 2595 "%(source)12s %(destination)12s "
2596 2596 "%(nbrevs)12d %(nbmissingfiles)12d\n"
2597 2597 )
2598 2598 fm.plain(header % ("source", "destination", "nb-revs", "nb-files"))
2599 2599
2600 2600 if not revs:
2601 2601 revs = ['all()']
2602 2602 revs = scmutil.revrange(repo, revs)
2603 2603
2604 2604 if dostats:
2605 2605 alldata = {
2606 2606 'nbrevs': [],
2607 2607 'nbmissingfiles': [],
2608 2608 }
2609 2609 if dotiming:
2610 2610 alldata['nbrenames'] = []
2611 2611 alldata['time'] = []
2612 2612
2613 2613 roi = repo.revs('merge() and %ld', revs)
2614 2614 for r in roi:
2615 2615 ctx = repo[r]
2616 2616 p1 = ctx.p1().rev()
2617 2617 p2 = ctx.p2().rev()
2618 2618 bases = repo.changelog._commonancestorsheads(p1, p2)
2619 2619 for p in (p1, p2):
2620 2620 for b in bases:
2621 2621 base = repo[b]
2622 2622 parent = repo[p]
2623 2623 missing = copies._computeforwardmissing(base, parent)
2624 2624 if not missing:
2625 2625 continue
2626 2626 data = {
2627 2627 b'source': base.hex(),
2628 2628 b'destination': parent.hex(),
2629 2629 b'nbrevs': len(repo.revs('only(%d, %d)', p, b)),
2630 2630 b'nbmissingfiles': len(missing),
2631 2631 }
2632 2632 if dostats:
2633 2633 alldata['nbrevs'].append(
2634 2634 (
2635 2635 data['nbrevs'],
2636 2636 base.hex(),
2637 2637 parent.hex(),
2638 2638 )
2639 2639 )
2640 2640 alldata['nbmissingfiles'].append(
2641 2641 (
2642 2642 data['nbmissingfiles'],
2643 2643 base.hex(),
2644 2644 parent.hex(),
2645 2645 )
2646 2646 )
2647 2647 if dotiming:
2648 2648 begin = util.timer()
2649 2649 renames = copies.pathcopies(base, parent)
2650 2650 end = util.timer()
2651 2651 # not very stable timing since we did only one run
2652 2652 data['time'] = end - begin
2653 2653 data['nbrenamedfiles'] = len(renames)
2654 2654 if dostats:
2655 2655 alldata['time'].append(
2656 2656 (
2657 2657 data['time'],
2658 2658 base.hex(),
2659 2659 parent.hex(),
2660 2660 )
2661 2661 )
2662 2662 alldata['nbrenames'].append(
2663 2663 (
2664 2664 data['nbrenamedfiles'],
2665 2665 base.hex(),
2666 2666 parent.hex(),
2667 2667 )
2668 2668 )
2669 2669 fm.startitem()
2670 2670 fm.data(**data)
2671 2671 out = data.copy()
2672 2672 out['source'] = fm.hexfunc(base.node())
2673 2673 out['destination'] = fm.hexfunc(parent.node())
2674 2674 fm.plain(output % out)
2675 2675
2676 2676 fm.end()
2677 2677 if dostats:
2678 2678 entries = [
2679 2679 ('nbrevs', 'number of revision covered'),
2680 2680 ('nbmissingfiles', 'number of missing files at head'),
2681 2681 ]
2682 2682 if dotiming:
2683 2683 entries.append(('nbrenames', 'renamed files'))
2684 2684 entries.append(('time', 'time'))
2685 2685 _displaystats(ui, opts, entries, alldata)
2686 2686
2687 2687
2688 2688 @command(b'perf::cca|perfcca', formatteropts)
2689 2689 def perfcca(ui, repo, **opts):
2690 2690 opts = _byteskwargs(opts)
2691 2691 timer, fm = gettimer(ui, opts)
2692 2692 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
2693 2693 fm.end()
2694 2694
2695 2695
2696 2696 @command(b'perf::fncacheload|perffncacheload', formatteropts)
2697 2697 def perffncacheload(ui, repo, **opts):
2698 2698 opts = _byteskwargs(opts)
2699 2699 timer, fm = gettimer(ui, opts)
2700 2700 s = repo.store
2701 2701
2702 2702 def d():
2703 2703 s.fncache._load()
2704 2704
2705 2705 timer(d)
2706 2706 fm.end()
2707 2707
2708 2708
2709 2709 @command(b'perf::fncachewrite|perffncachewrite', formatteropts)
2710 2710 def perffncachewrite(ui, repo, **opts):
2711 2711 opts = _byteskwargs(opts)
2712 2712 timer, fm = gettimer(ui, opts)
2713 2713 s = repo.store
2714 2714 lock = repo.lock()
2715 2715 s.fncache._load()
2716 2716 tr = repo.transaction(b'perffncachewrite')
2717 2717 tr.addbackup(b'fncache')
2718 2718
2719 2719 def d():
2720 2720 s.fncache._dirty = True
2721 2721 s.fncache.write(tr)
2722 2722
2723 2723 timer(d)
2724 2724 tr.close()
2725 2725 lock.release()
2726 2726 fm.end()
2727 2727
2728 2728
2729 2729 @command(b'perf::fncacheencode|perffncacheencode', formatteropts)
2730 2730 def perffncacheencode(ui, repo, **opts):
2731 2731 opts = _byteskwargs(opts)
2732 2732 timer, fm = gettimer(ui, opts)
2733 2733 s = repo.store
2734 2734 s.fncache._load()
2735 2735
2736 2736 def d():
2737 2737 for p in s.fncache.entries:
2738 2738 s.encode(p)
2739 2739
2740 2740 timer(d)
2741 2741 fm.end()
2742 2742
2743 2743
2744 2744 def _bdiffworker(q, blocks, xdiff, ready, done):
2745 2745 while not done.is_set():
2746 2746 pair = q.get()
2747 2747 while pair is not None:
2748 2748 if xdiff:
2749 2749 mdiff.bdiff.xdiffblocks(*pair)
2750 2750 elif blocks:
2751 2751 mdiff.bdiff.blocks(*pair)
2752 2752 else:
2753 2753 mdiff.textdiff(*pair)
2754 2754 q.task_done()
2755 2755 pair = q.get()
2756 2756 q.task_done() # for the None one
2757 2757 with ready:
2758 2758 ready.wait()
2759 2759
2760 2760
2761 2761 def _manifestrevision(repo, mnode):
2762 2762 ml = repo.manifestlog
2763 2763
2764 2764 if util.safehasattr(ml, b'getstorage'):
2765 2765 store = ml.getstorage(b'')
2766 2766 else:
2767 2767 store = ml._revlog
2768 2768
2769 2769 return store.revision(mnode)
2770 2770
2771 2771
2772 2772 @command(
2773 2773 b'perf::bdiff|perfbdiff',
2774 2774 revlogopts
2775 2775 + formatteropts
2776 2776 + [
2777 2777 (
2778 2778 b'',
2779 2779 b'count',
2780 2780 1,
2781 2781 b'number of revisions to test (when using --startrev)',
2782 2782 ),
2783 2783 (b'', b'alldata', False, b'test bdiffs for all associated revisions'),
2784 2784 (b'', b'threads', 0, b'number of thread to use (disable with 0)'),
2785 2785 (b'', b'blocks', False, b'test computing diffs into blocks'),
2786 2786 (b'', b'xdiff', False, b'use xdiff algorithm'),
2787 2787 ],
2788 2788 b'-c|-m|FILE REV',
2789 2789 )
2790 2790 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
2791 2791 """benchmark a bdiff between revisions
2792 2792
2793 2793 By default, benchmark a bdiff between its delta parent and itself.
2794 2794
2795 2795 With ``--count``, benchmark bdiffs between delta parents and self for N
2796 2796 revisions starting at the specified revision.
2797 2797
2798 2798 With ``--alldata``, assume the requested revision is a changeset and
2799 2799 measure bdiffs for all changes related to that changeset (manifest
2800 2800 and filelogs).
2801 2801 """
2802 2802 opts = _byteskwargs(opts)
2803 2803
2804 2804 if opts[b'xdiff'] and not opts[b'blocks']:
2805 2805 raise error.CommandError(b'perfbdiff', b'--xdiff requires --blocks')
2806 2806
2807 2807 if opts[b'alldata']:
2808 2808 opts[b'changelog'] = True
2809 2809
2810 2810 if opts.get(b'changelog') or opts.get(b'manifest'):
2811 2811 file_, rev = None, file_
2812 2812 elif rev is None:
2813 2813 raise error.CommandError(b'perfbdiff', b'invalid arguments')
2814 2814
2815 2815 blocks = opts[b'blocks']
2816 2816 xdiff = opts[b'xdiff']
2817 2817 textpairs = []
2818 2818
2819 2819 r = cmdutil.openrevlog(repo, b'perfbdiff', file_, opts)
2820 2820
2821 2821 startrev = r.rev(r.lookup(rev))
2822 2822 for rev in range(startrev, min(startrev + count, len(r) - 1)):
2823 2823 if opts[b'alldata']:
2824 2824 # Load revisions associated with changeset.
2825 2825 ctx = repo[rev]
2826 2826 mtext = _manifestrevision(repo, ctx.manifestnode())
2827 2827 for pctx in ctx.parents():
2828 2828 pman = _manifestrevision(repo, pctx.manifestnode())
2829 2829 textpairs.append((pman, mtext))
2830 2830
2831 2831 # Load filelog revisions by iterating manifest delta.
2832 2832 man = ctx.manifest()
2833 2833 pman = ctx.p1().manifest()
2834 2834 for filename, change in pman.diff(man).items():
2835 2835 fctx = repo.file(filename)
2836 2836 f1 = fctx.revision(change[0][0] or -1)
2837 2837 f2 = fctx.revision(change[1][0] or -1)
2838 2838 textpairs.append((f1, f2))
2839 2839 else:
2840 2840 dp = r.deltaparent(rev)
2841 2841 textpairs.append((r.revision(dp), r.revision(rev)))
2842 2842
2843 2843 withthreads = threads > 0
2844 2844 if not withthreads:
2845 2845
2846 2846 def d():
2847 2847 for pair in textpairs:
2848 2848 if xdiff:
2849 2849 mdiff.bdiff.xdiffblocks(*pair)
2850 2850 elif blocks:
2851 2851 mdiff.bdiff.blocks(*pair)
2852 2852 else:
2853 2853 mdiff.textdiff(*pair)
2854 2854
2855 2855 else:
2856 2856 q = queue()
2857 2857 for i in _xrange(threads):
2858 2858 q.put(None)
2859 2859 ready = threading.Condition()
2860 2860 done = threading.Event()
2861 2861 for i in _xrange(threads):
2862 2862 threading.Thread(
2863 2863 target=_bdiffworker, args=(q, blocks, xdiff, ready, done)
2864 2864 ).start()
2865 2865 q.join()
2866 2866
2867 2867 def d():
2868 2868 for pair in textpairs:
2869 2869 q.put(pair)
2870 2870 for i in _xrange(threads):
2871 2871 q.put(None)
2872 2872 with ready:
2873 2873 ready.notify_all()
2874 2874 q.join()
2875 2875
2876 2876 timer, fm = gettimer(ui, opts)
2877 2877 timer(d)
2878 2878 fm.end()
2879 2879
2880 2880 if withthreads:
2881 2881 done.set()
2882 2882 for i in _xrange(threads):
2883 2883 q.put(None)
2884 2884 with ready:
2885 2885 ready.notify_all()
2886 2886
2887 2887
2888 2888 @command(
2889 2889 b'perf::unbundle',
2890 2890 formatteropts,
2891 2891 b'BUNDLE_FILE',
2892 2892 )
2893 2893 def perf_unbundle(ui, repo, fname, **opts):
2894 2894 """benchmark application of a bundle in a repository.
2895 2895
2896 2896 This does not include the final transaction processing"""
2897 2897
2898 2898 from mercurial import exchange
2899 2899 from mercurial import bundle2
2900 2900 from mercurial import transaction
2901 2901
2902 2902 opts = _byteskwargs(opts)
2903 2903
2904 2904 ### some compatibility hotfix
2905 2905 #
2906 2906 # the data attribute is dropped in 63edc384d3b7 a changeset introducing a
2907 2907 # critical regression that break transaction rollback for files that are
2908 2908 # de-inlined.
2909 2909 method = transaction.transaction._addentry
2910 2910 pre_63edc384d3b7 = "data" in getargspec(method).args
2911 2911 # the `detailed_exit_code` attribute is introduced in 33c0c25d0b0f
2912 2912 # a changeset that is a close descendant of 18415fc918a1, the changeset
2913 2913 # that conclude the fix run for the bug introduced in 63edc384d3b7.
2914 2914 args = getargspec(error.Abort.__init__).args
2915 2915 post_18415fc918a1 = "detailed_exit_code" in args
2916 2916
2917 2917 old_max_inline = None
2918 2918 try:
2919 2919 if not (pre_63edc384d3b7 or post_18415fc918a1):
2920 2920 # disable inlining
2921 2921 old_max_inline = mercurial.revlog._maxinline
2922 2922 # large enough to never happen
2923 2923 mercurial.revlog._maxinline = 2 ** 50
2924 2924
2925 2925 with repo.lock():
2926 2926 bundle = [None, None]
2927 2927 orig_quiet = repo.ui.quiet
2928 2928 try:
2929 2929 repo.ui.quiet = True
2930 2930 with open(fname, mode="rb") as f:
2931 2931
2932 2932 def noop_report(*args, **kwargs):
2933 2933 pass
2934 2934
2935 2935 def setup():
2936 2936 gen, tr = bundle
2937 2937 if tr is not None:
2938 2938 tr.abort()
2939 2939 bundle[:] = [None, None]
2940 2940 f.seek(0)
2941 2941 bundle[0] = exchange.readbundle(ui, f, fname)
2942 2942 bundle[1] = repo.transaction(b'perf::unbundle')
2943 2943 # silence the transaction
2944 2944 bundle[1]._report = noop_report
2945 2945
2946 2946 def apply():
2947 2947 gen, tr = bundle
2948 2948 bundle2.applybundle(
2949 2949 repo,
2950 2950 gen,
2951 2951 tr,
2952 2952 source=b'perf::unbundle',
2953 2953 url=fname,
2954 2954 )
2955 2955
2956 2956 timer, fm = gettimer(ui, opts)
2957 2957 timer(apply, setup=setup)
2958 2958 fm.end()
2959 2959 finally:
2960 2960 repo.ui.quiet == orig_quiet
2961 2961 gen, tr = bundle
2962 2962 if tr is not None:
2963 2963 tr.abort()
2964 2964 finally:
2965 2965 if old_max_inline is not None:
2966 2966 mercurial.revlog._maxinline = old_max_inline
2967 2967
2968 2968
2969 2969 @command(
2970 2970 b'perf::unidiff|perfunidiff',
2971 2971 revlogopts
2972 2972 + formatteropts
2973 2973 + [
2974 2974 (
2975 2975 b'',
2976 2976 b'count',
2977 2977 1,
2978 2978 b'number of revisions to test (when using --startrev)',
2979 2979 ),
2980 2980 (b'', b'alldata', False, b'test unidiffs for all associated revisions'),
2981 2981 ],
2982 2982 b'-c|-m|FILE REV',
2983 2983 )
2984 2984 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
2985 2985 """benchmark a unified diff between revisions
2986 2986
2987 2987 This doesn't include any copy tracing - it's just a unified diff
2988 2988 of the texts.
2989 2989
2990 2990 By default, benchmark a diff between its delta parent and itself.
2991 2991
2992 2992 With ``--count``, benchmark diffs between delta parents and self for N
2993 2993 revisions starting at the specified revision.
2994 2994
2995 2995 With ``--alldata``, assume the requested revision is a changeset and
2996 2996 measure diffs for all changes related to that changeset (manifest
2997 2997 and filelogs).
2998 2998 """
2999 2999 opts = _byteskwargs(opts)
3000 3000 if opts[b'alldata']:
3001 3001 opts[b'changelog'] = True
3002 3002
3003 3003 if opts.get(b'changelog') or opts.get(b'manifest'):
3004 3004 file_, rev = None, file_
3005 3005 elif rev is None:
3006 3006 raise error.CommandError(b'perfunidiff', b'invalid arguments')
3007 3007
3008 3008 textpairs = []
3009 3009
3010 3010 r = cmdutil.openrevlog(repo, b'perfunidiff', file_, opts)
3011 3011
3012 3012 startrev = r.rev(r.lookup(rev))
3013 3013 for rev in range(startrev, min(startrev + count, len(r) - 1)):
3014 3014 if opts[b'alldata']:
3015 3015 # Load revisions associated with changeset.
3016 3016 ctx = repo[rev]
3017 3017 mtext = _manifestrevision(repo, ctx.manifestnode())
3018 3018 for pctx in ctx.parents():
3019 3019 pman = _manifestrevision(repo, pctx.manifestnode())
3020 3020 textpairs.append((pman, mtext))
3021 3021
3022 3022 # Load filelog revisions by iterating manifest delta.
3023 3023 man = ctx.manifest()
3024 3024 pman = ctx.p1().manifest()
3025 3025 for filename, change in pman.diff(man).items():
3026 3026 fctx = repo.file(filename)
3027 3027 f1 = fctx.revision(change[0][0] or -1)
3028 3028 f2 = fctx.revision(change[1][0] or -1)
3029 3029 textpairs.append((f1, f2))
3030 3030 else:
3031 3031 dp = r.deltaparent(rev)
3032 3032 textpairs.append((r.revision(dp), r.revision(rev)))
3033 3033
3034 3034 def d():
3035 3035 for left, right in textpairs:
3036 3036 # The date strings don't matter, so we pass empty strings.
3037 3037 headerlines, hunks = mdiff.unidiff(
3038 3038 left, b'', right, b'', b'left', b'right', binary=False
3039 3039 )
3040 3040 # consume iterators in roughly the way patch.py does
3041 3041 b'\n'.join(headerlines)
3042 3042 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
3043 3043
3044 3044 timer, fm = gettimer(ui, opts)
3045 3045 timer(d)
3046 3046 fm.end()
3047 3047
3048 3048
3049 3049 @command(b'perf::diffwd|perfdiffwd', formatteropts)
3050 3050 def perfdiffwd(ui, repo, **opts):
3051 3051 """Profile diff of working directory changes"""
3052 3052 opts = _byteskwargs(opts)
3053 3053 timer, fm = gettimer(ui, opts)
3054 3054 options = {
3055 3055 'w': 'ignore_all_space',
3056 3056 'b': 'ignore_space_change',
3057 3057 'B': 'ignore_blank_lines',
3058 3058 }
3059 3059
3060 3060 for diffopt in ('', 'w', 'b', 'B', 'wB'):
3061 3061 opts = {options[c]: b'1' for c in diffopt}
3062 3062
3063 3063 def d():
3064 3064 ui.pushbuffer()
3065 3065 commands.diff(ui, repo, **opts)
3066 3066 ui.popbuffer()
3067 3067
3068 3068 diffopt = diffopt.encode('ascii')
3069 3069 title = b'diffopts: %s' % (diffopt and (b'-' + diffopt) or b'none')
3070 3070 timer(d, title=title)
3071 3071 fm.end()
3072 3072
3073 3073
3074 3074 @command(
3075 3075 b'perf::revlogindex|perfrevlogindex',
3076 3076 revlogopts + formatteropts,
3077 3077 b'-c|-m|FILE',
3078 3078 )
3079 3079 def perfrevlogindex(ui, repo, file_=None, **opts):
3080 3080 """Benchmark operations against a revlog index.
3081 3081
3082 3082 This tests constructing a revlog instance, reading index data,
3083 3083 parsing index data, and performing various operations related to
3084 3084 index data.
3085 3085 """
3086 3086
3087 3087 opts = _byteskwargs(opts)
3088 3088
3089 3089 rl = cmdutil.openrevlog(repo, b'perfrevlogindex', file_, opts)
3090 3090
3091 3091 opener = getattr(rl, 'opener') # trick linter
3092 3092 # compat with hg <= 5.8
3093 3093 radix = getattr(rl, 'radix', None)
3094 3094 indexfile = getattr(rl, '_indexfile', None)
3095 3095 if indexfile is None:
3096 3096 # compatibility with <= hg-5.8
3097 3097 indexfile = getattr(rl, 'indexfile')
3098 3098 data = opener.read(indexfile)
3099 3099
3100 3100 header = struct.unpack(b'>I', data[0:4])[0]
3101 3101 version = header & 0xFFFF
3102 3102 if version == 1:
3103 3103 inline = header & (1 << 16)
3104 3104 else:
3105 3105 raise error.Abort(b'unsupported revlog version: %d' % version)
3106 3106
3107 3107 parse_index_v1 = getattr(mercurial.revlog, 'parse_index_v1', None)
3108 3108 if parse_index_v1 is None:
3109 3109 parse_index_v1 = mercurial.revlog.revlogio().parseindex
3110 3110
3111 3111 rllen = len(rl)
3112 3112
3113 3113 node0 = rl.node(0)
3114 3114 node25 = rl.node(rllen // 4)
3115 3115 node50 = rl.node(rllen // 2)
3116 3116 node75 = rl.node(rllen // 4 * 3)
3117 3117 node100 = rl.node(rllen - 1)
3118 3118
3119 3119 allrevs = range(rllen)
3120 3120 allrevsrev = list(reversed(allrevs))
3121 3121 allnodes = [rl.node(rev) for rev in range(rllen)]
3122 3122 allnodesrev = list(reversed(allnodes))
3123 3123
3124 3124 def constructor():
3125 3125 if radix is not None:
3126 3126 revlog(opener, radix=radix)
3127 3127 else:
3128 3128 # hg <= 5.8
3129 3129 revlog(opener, indexfile=indexfile)
3130 3130
3131 3131 def read():
3132 3132 with opener(indexfile) as fh:
3133 3133 fh.read()
3134 3134
3135 3135 def parseindex():
3136 3136 parse_index_v1(data, inline)
3137 3137
3138 3138 def getentry(revornode):
3139 3139 index = parse_index_v1(data, inline)[0]
3140 3140 index[revornode]
3141 3141
3142 3142 def getentries(revs, count=1):
3143 3143 index = parse_index_v1(data, inline)[0]
3144 3144
3145 3145 for i in range(count):
3146 3146 for rev in revs:
3147 3147 index[rev]
3148 3148
3149 3149 def resolvenode(node):
3150 3150 index = parse_index_v1(data, inline)[0]
3151 3151 rev = getattr(index, 'rev', None)
3152 3152 if rev is None:
3153 3153 nodemap = getattr(parse_index_v1(data, inline)[0], 'nodemap', None)
3154 3154 # This only works for the C code.
3155 3155 if nodemap is None:
3156 3156 return
3157 3157 rev = nodemap.__getitem__
3158 3158
3159 3159 try:
3160 3160 rev(node)
3161 3161 except error.RevlogError:
3162 3162 pass
3163 3163
3164 3164 def resolvenodes(nodes, count=1):
3165 3165 index = parse_index_v1(data, inline)[0]
3166 3166 rev = getattr(index, 'rev', None)
3167 3167 if rev is None:
3168 3168 nodemap = getattr(parse_index_v1(data, inline)[0], 'nodemap', None)
3169 3169 # This only works for the C code.
3170 3170 if nodemap is None:
3171 3171 return
3172 3172 rev = nodemap.__getitem__
3173 3173
3174 3174 for i in range(count):
3175 3175 for node in nodes:
3176 3176 try:
3177 3177 rev(node)
3178 3178 except error.RevlogError:
3179 3179 pass
3180 3180
3181 3181 benches = [
3182 3182 (constructor, b'revlog constructor'),
3183 3183 (read, b'read'),
3184 3184 (parseindex, b'create index object'),
3185 3185 (lambda: getentry(0), b'retrieve index entry for rev 0'),
3186 3186 (lambda: resolvenode(b'a' * 20), b'look up missing node'),
3187 3187 (lambda: resolvenode(node0), b'look up node at rev 0'),
3188 3188 (lambda: resolvenode(node25), b'look up node at 1/4 len'),
3189 3189 (lambda: resolvenode(node50), b'look up node at 1/2 len'),
3190 3190 (lambda: resolvenode(node75), b'look up node at 3/4 len'),
3191 3191 (lambda: resolvenode(node100), b'look up node at tip'),
3192 3192 # 2x variation is to measure caching impact.
3193 3193 (lambda: resolvenodes(allnodes), b'look up all nodes (forward)'),
3194 3194 (lambda: resolvenodes(allnodes, 2), b'look up all nodes 2x (forward)'),
3195 3195 (lambda: resolvenodes(allnodesrev), b'look up all nodes (reverse)'),
3196 3196 (
3197 3197 lambda: resolvenodes(allnodesrev, 2),
3198 3198 b'look up all nodes 2x (reverse)',
3199 3199 ),
3200 3200 (lambda: getentries(allrevs), b'retrieve all index entries (forward)'),
3201 3201 (
3202 3202 lambda: getentries(allrevs, 2),
3203 3203 b'retrieve all index entries 2x (forward)',
3204 3204 ),
3205 3205 (
3206 3206 lambda: getentries(allrevsrev),
3207 3207 b'retrieve all index entries (reverse)',
3208 3208 ),
3209 3209 (
3210 3210 lambda: getentries(allrevsrev, 2),
3211 3211 b'retrieve all index entries 2x (reverse)',
3212 3212 ),
3213 3213 ]
3214 3214
3215 3215 for fn, title in benches:
3216 3216 timer, fm = gettimer(ui, opts)
3217 3217 timer(fn, title=title)
3218 3218 fm.end()
3219 3219
3220 3220
3221 3221 @command(
3222 3222 b'perf::revlogrevisions|perfrevlogrevisions',
3223 3223 revlogopts
3224 3224 + formatteropts
3225 3225 + [
3226 3226 (b'd', b'dist', 100, b'distance between the revisions'),
3227 3227 (b's', b'startrev', 0, b'revision to start reading at'),
3228 3228 (b'', b'reverse', False, b'read in reverse'),
3229 3229 ],
3230 3230 b'-c|-m|FILE',
3231 3231 )
3232 3232 def perfrevlogrevisions(
3233 3233 ui, repo, file_=None, startrev=0, reverse=False, **opts
3234 3234 ):
3235 3235 """Benchmark reading a series of revisions from a revlog.
3236 3236
3237 3237 By default, we read every ``-d/--dist`` revision from 0 to tip of
3238 3238 the specified revlog.
3239 3239
3240 3240 The start revision can be defined via ``-s/--startrev``.
3241 3241 """
3242 3242 opts = _byteskwargs(opts)
3243 3243
3244 3244 rl = cmdutil.openrevlog(repo, b'perfrevlogrevisions', file_, opts)
3245 3245 rllen = getlen(ui)(rl)
3246 3246
3247 3247 if startrev < 0:
3248 3248 startrev = rllen + startrev
3249 3249
3250 3250 def d():
3251 3251 rl.clearcaches()
3252 3252
3253 3253 beginrev = startrev
3254 3254 endrev = rllen
3255 3255 dist = opts[b'dist']
3256 3256
3257 3257 if reverse:
3258 3258 beginrev, endrev = endrev - 1, beginrev - 1
3259 3259 dist = -1 * dist
3260 3260
3261 3261 for x in _xrange(beginrev, endrev, dist):
3262 3262 # Old revisions don't support passing int.
3263 3263 n = rl.node(x)
3264 3264 rl.revision(n)
3265 3265
3266 3266 timer, fm = gettimer(ui, opts)
3267 3267 timer(d)
3268 3268 fm.end()
3269 3269
3270 3270
3271 3271 @command(
3272 3272 b'perf::revlogwrite|perfrevlogwrite',
3273 3273 revlogopts
3274 3274 + formatteropts
3275 3275 + [
3276 3276 (b's', b'startrev', 1000, b'revision to start writing at'),
3277 3277 (b'', b'stoprev', -1, b'last revision to write'),
3278 3278 (b'', b'count', 3, b'number of passes to perform'),
3279 3279 (b'', b'details', False, b'print timing for every revisions tested'),
3280 3280 (b'', b'source', b'full', b'the kind of data feed in the revlog'),
3281 3281 (b'', b'lazydeltabase', True, b'try the provided delta first'),
3282 3282 (b'', b'clear-caches', True, b'clear revlog cache between calls'),
3283 3283 ],
3284 3284 b'-c|-m|FILE',
3285 3285 )
3286 3286 def perfrevlogwrite(ui, repo, file_=None, startrev=1000, stoprev=-1, **opts):
3287 3287 """Benchmark writing a series of revisions to a revlog.
3288 3288
3289 3289 Possible source values are:
3290 3290 * `full`: add from a full text (default).
3291 3291 * `parent-1`: add from a delta to the first parent
3292 3292 * `parent-2`: add from a delta to the second parent if it exists
3293 3293 (use a delta from the first parent otherwise)
3294 3294 * `parent-smallest`: add from the smallest delta (either p1 or p2)
3295 3295 * `storage`: add from the existing precomputed deltas
3296 3296
3297 3297 Note: This performance command measures performance in a custom way. As a
3298 3298 result some of the global configuration of the 'perf' command does not
3299 3299 apply to it:
3300 3300
3301 3301 * ``pre-run``: disabled
3302 3302
3303 3303 * ``profile-benchmark``: disabled
3304 3304
3305 3305 * ``run-limits``: disabled use --count instead
3306 3306 """
3307 3307 opts = _byteskwargs(opts)
3308 3308
3309 3309 rl = cmdutil.openrevlog(repo, b'perfrevlogwrite', file_, opts)
3310 3310 rllen = getlen(ui)(rl)
3311 3311 if startrev < 0:
3312 3312 startrev = rllen + startrev
3313 3313 if stoprev < 0:
3314 3314 stoprev = rllen + stoprev
3315 3315
3316 3316 lazydeltabase = opts['lazydeltabase']
3317 3317 source = opts['source']
3318 3318 clearcaches = opts['clear_caches']
3319 3319 validsource = (
3320 3320 b'full',
3321 3321 b'parent-1',
3322 3322 b'parent-2',
3323 3323 b'parent-smallest',
3324 3324 b'storage',
3325 3325 )
3326 3326 if source not in validsource:
3327 3327 raise error.Abort('invalid source type: %s' % source)
3328 3328
3329 3329 ### actually gather results
3330 3330 count = opts['count']
3331 3331 if count <= 0:
3332 3332 raise error.Abort('invalide run count: %d' % count)
3333 3333 allresults = []
3334 3334 for c in range(count):
3335 3335 timing = _timeonewrite(
3336 3336 ui,
3337 3337 rl,
3338 3338 source,
3339 3339 startrev,
3340 3340 stoprev,
3341 3341 c + 1,
3342 3342 lazydeltabase=lazydeltabase,
3343 3343 clearcaches=clearcaches,
3344 3344 )
3345 3345 allresults.append(timing)
3346 3346
3347 3347 ### consolidate the results in a single list
3348 3348 results = []
3349 3349 for idx, (rev, t) in enumerate(allresults[0]):
3350 3350 ts = [t]
3351 3351 for other in allresults[1:]:
3352 3352 orev, ot = other[idx]
3353 3353 assert orev == rev
3354 3354 ts.append(ot)
3355 3355 results.append((rev, ts))
3356 3356 resultcount = len(results)
3357 3357
3358 3358 ### Compute and display relevant statistics
3359 3359
3360 3360 # get a formatter
3361 3361 fm = ui.formatter(b'perf', opts)
3362 3362 displayall = ui.configbool(b"perf", b"all-timing", True)
3363 3363
3364 3364 # print individual details if requested
3365 3365 if opts['details']:
3366 3366 for idx, item in enumerate(results, 1):
3367 3367 rev, data = item
3368 3368 title = 'revisions #%d of %d, rev %d' % (idx, resultcount, rev)
3369 3369 formatone(fm, data, title=title, displayall=displayall)
3370 3370
3371 3371 # sorts results by median time
3372 3372 results.sort(key=lambda x: sorted(x[1])[len(x[1]) // 2])
3373 3373 # list of (name, index) to display)
3374 3374 relevants = [
3375 3375 ("min", 0),
3376 3376 ("10%", resultcount * 10 // 100),
3377 3377 ("25%", resultcount * 25 // 100),
3378 3378 ("50%", resultcount * 70 // 100),
3379 3379 ("75%", resultcount * 75 // 100),
3380 3380 ("90%", resultcount * 90 // 100),
3381 3381 ("95%", resultcount * 95 // 100),
3382 3382 ("99%", resultcount * 99 // 100),
3383 3383 ("99.9%", resultcount * 999 // 1000),
3384 3384 ("99.99%", resultcount * 9999 // 10000),
3385 3385 ("99.999%", resultcount * 99999 // 100000),
3386 3386 ("max", -1),
3387 3387 ]
3388 3388 if not ui.quiet:
3389 3389 for name, idx in relevants:
3390 3390 data = results[idx]
3391 3391 title = '%s of %d, rev %d' % (name, resultcount, data[0])
3392 3392 formatone(fm, data[1], title=title, displayall=displayall)
3393 3393
3394 3394 # XXX summing that many float will not be very precise, we ignore this fact
3395 3395 # for now
3396 3396 totaltime = []
3397 3397 for item in allresults:
3398 3398 totaltime.append(
3399 3399 (
3400 3400 sum(x[1][0] for x in item),
3401 3401 sum(x[1][1] for x in item),
3402 3402 sum(x[1][2] for x in item),
3403 3403 )
3404 3404 )
3405 3405 formatone(
3406 3406 fm,
3407 3407 totaltime,
3408 3408 title="total time (%d revs)" % resultcount,
3409 3409 displayall=displayall,
3410 3410 )
3411 3411 fm.end()
3412 3412
3413 3413
3414 3414 class _faketr:
3415 3415 def add(s, x, y, z=None):
3416 3416 return None
3417 3417
3418 3418
3419 3419 def _timeonewrite(
3420 3420 ui,
3421 3421 orig,
3422 3422 source,
3423 3423 startrev,
3424 3424 stoprev,
3425 3425 runidx=None,
3426 3426 lazydeltabase=True,
3427 3427 clearcaches=True,
3428 3428 ):
3429 3429 timings = []
3430 3430 tr = _faketr()
3431 3431 with _temprevlog(ui, orig, startrev) as dest:
3432 3432 dest._lazydeltabase = lazydeltabase
3433 3433 revs = list(orig.revs(startrev, stoprev))
3434 3434 total = len(revs)
3435 3435 topic = 'adding'
3436 3436 if runidx is not None:
3437 3437 topic += ' (run #%d)' % runidx
3438 3438 # Support both old and new progress API
3439 3439 if util.safehasattr(ui, 'makeprogress'):
3440 3440 progress = ui.makeprogress(topic, unit='revs', total=total)
3441 3441
3442 3442 def updateprogress(pos):
3443 3443 progress.update(pos)
3444 3444
3445 3445 def completeprogress():
3446 3446 progress.complete()
3447 3447
3448 3448 else:
3449 3449
3450 3450 def updateprogress(pos):
3451 3451 ui.progress(topic, pos, unit='revs', total=total)
3452 3452
3453 3453 def completeprogress():
3454 3454 ui.progress(topic, None, unit='revs', total=total)
3455 3455
3456 3456 for idx, rev in enumerate(revs):
3457 3457 updateprogress(idx)
3458 3458 addargs, addkwargs = _getrevisionseed(orig, rev, tr, source)
3459 3459 if clearcaches:
3460 3460 dest.index.clearcaches()
3461 3461 dest.clearcaches()
3462 3462 with timeone() as r:
3463 3463 dest.addrawrevision(*addargs, **addkwargs)
3464 3464 timings.append((rev, r[0]))
3465 3465 updateprogress(total)
3466 3466 completeprogress()
3467 3467 return timings
3468 3468
3469 3469
3470 3470 def _getrevisionseed(orig, rev, tr, source):
3471 3471 from mercurial.node import nullid
3472 3472
3473 3473 linkrev = orig.linkrev(rev)
3474 3474 node = orig.node(rev)
3475 3475 p1, p2 = orig.parents(node)
3476 3476 flags = orig.flags(rev)
3477 3477 cachedelta = None
3478 3478 text = None
3479 3479
3480 3480 if source == b'full':
3481 3481 text = orig.revision(rev)
3482 3482 elif source == b'parent-1':
3483 3483 baserev = orig.rev(p1)
3484 3484 cachedelta = (baserev, orig.revdiff(p1, rev))
3485 3485 elif source == b'parent-2':
3486 3486 parent = p2
3487 3487 if p2 == nullid:
3488 3488 parent = p1
3489 3489 baserev = orig.rev(parent)
3490 3490 cachedelta = (baserev, orig.revdiff(parent, rev))
3491 3491 elif source == b'parent-smallest':
3492 3492 p1diff = orig.revdiff(p1, rev)
3493 3493 parent = p1
3494 3494 diff = p1diff
3495 3495 if p2 != nullid:
3496 3496 p2diff = orig.revdiff(p2, rev)
3497 3497 if len(p1diff) > len(p2diff):
3498 3498 parent = p2
3499 3499 diff = p2diff
3500 3500 baserev = orig.rev(parent)
3501 3501 cachedelta = (baserev, diff)
3502 3502 elif source == b'storage':
3503 3503 baserev = orig.deltaparent(rev)
3504 3504 cachedelta = (baserev, orig.revdiff(orig.node(baserev), rev))
3505 3505
3506 3506 return (
3507 3507 (text, tr, linkrev, p1, p2),
3508 3508 {'node': node, 'flags': flags, 'cachedelta': cachedelta},
3509 3509 )
3510 3510
3511 3511
3512 3512 @contextlib.contextmanager
3513 3513 def _temprevlog(ui, orig, truncaterev):
3514 3514 from mercurial import vfs as vfsmod
3515 3515
3516 3516 if orig._inline:
3517 3517 raise error.Abort('not supporting inline revlog (yet)')
3518 3518 revlogkwargs = {}
3519 3519 k = 'upperboundcomp'
3520 3520 if util.safehasattr(orig, k):
3521 3521 revlogkwargs[k] = getattr(orig, k)
3522 3522
3523 3523 indexfile = getattr(orig, '_indexfile', None)
3524 3524 if indexfile is None:
3525 3525 # compatibility with <= hg-5.8
3526 3526 indexfile = getattr(orig, 'indexfile')
3527 3527 origindexpath = orig.opener.join(indexfile)
3528 3528
3529 3529 datafile = getattr(orig, '_datafile', getattr(orig, 'datafile'))
3530 3530 origdatapath = orig.opener.join(datafile)
3531 3531 radix = b'revlog'
3532 3532 indexname = b'revlog.i'
3533 3533 dataname = b'revlog.d'
3534 3534
3535 3535 tmpdir = tempfile.mkdtemp(prefix='tmp-hgperf-')
3536 3536 try:
3537 3537 # copy the data file in a temporary directory
3538 3538 ui.debug('copying data in %s\n' % tmpdir)
3539 3539 destindexpath = os.path.join(tmpdir, 'revlog.i')
3540 3540 destdatapath = os.path.join(tmpdir, 'revlog.d')
3541 3541 shutil.copyfile(origindexpath, destindexpath)
3542 3542 shutil.copyfile(origdatapath, destdatapath)
3543 3543
3544 3544 # remove the data we want to add again
3545 3545 ui.debug('truncating data to be rewritten\n')
3546 3546 with open(destindexpath, 'ab') as index:
3547 3547 index.seek(0)
3548 3548 index.truncate(truncaterev * orig._io.size)
3549 3549 with open(destdatapath, 'ab') as data:
3550 3550 data.seek(0)
3551 3551 data.truncate(orig.start(truncaterev))
3552 3552
3553 3553 # instantiate a new revlog from the temporary copy
3554 3554 ui.debug('truncating adding to be rewritten\n')
3555 3555 vfs = vfsmod.vfs(tmpdir)
3556 3556 vfs.options = getattr(orig.opener, 'options', None)
3557 3557
3558 3558 try:
3559 3559 dest = revlog(vfs, radix=radix, **revlogkwargs)
3560 3560 except TypeError:
3561 3561 dest = revlog(
3562 3562 vfs, indexfile=indexname, datafile=dataname, **revlogkwargs
3563 3563 )
3564 3564 if dest._inline:
3565 3565 raise error.Abort('not supporting inline revlog (yet)')
3566 3566 # make sure internals are initialized
3567 3567 dest.revision(len(dest) - 1)
3568 3568 yield dest
3569 3569 del dest, vfs
3570 3570 finally:
3571 3571 shutil.rmtree(tmpdir, True)
3572 3572
3573 3573
3574 3574 @command(
3575 3575 b'perf::revlogchunks|perfrevlogchunks',
3576 3576 revlogopts
3577 3577 + formatteropts
3578 3578 + [
3579 3579 (b'e', b'engines', b'', b'compression engines to use'),
3580 3580 (b's', b'startrev', 0, b'revision to start at'),
3581 3581 ],
3582 3582 b'-c|-m|FILE',
3583 3583 )
3584 3584 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
3585 3585 """Benchmark operations on revlog chunks.
3586 3586
3587 3587 Logically, each revlog is a collection of fulltext revisions. However,
3588 3588 stored within each revlog are "chunks" of possibly compressed data. This
3589 3589 data needs to be read and decompressed or compressed and written.
3590 3590
3591 3591 This command measures the time it takes to read+decompress and recompress
3592 3592 chunks in a revlog. It effectively isolates I/O and compression performance.
3593 3593 For measurements of higher-level operations like resolving revisions,
3594 3594 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
3595 3595 """
3596 3596 opts = _byteskwargs(opts)
3597 3597
3598 3598 rl = cmdutil.openrevlog(repo, b'perfrevlogchunks', file_, opts)
3599 3599
3600 3600 # _chunkraw was renamed to _getsegmentforrevs.
3601 3601 try:
3602 3602 segmentforrevs = rl._getsegmentforrevs
3603 3603 except AttributeError:
3604 3604 segmentforrevs = rl._chunkraw
3605 3605
3606 3606 # Verify engines argument.
3607 3607 if engines:
3608 3608 engines = {e.strip() for e in engines.split(b',')}
3609 3609 for engine in engines:
3610 3610 try:
3611 3611 util.compressionengines[engine]
3612 3612 except KeyError:
3613 3613 raise error.Abort(b'unknown compression engine: %s' % engine)
3614 3614 else:
3615 3615 engines = []
3616 3616 for e in util.compengines:
3617 3617 engine = util.compengines[e]
3618 3618 try:
3619 3619 if engine.available():
3620 3620 engine.revlogcompressor().compress(b'dummy')
3621 3621 engines.append(e)
3622 3622 except NotImplementedError:
3623 3623 pass
3624 3624
3625 3625 revs = list(rl.revs(startrev, len(rl) - 1))
3626 3626
3627 3627 def rlfh(rl):
3628 3628 if rl._inline:
3629 3629 indexfile = getattr(rl, '_indexfile', None)
3630 3630 if indexfile is None:
3631 3631 # compatibility with <= hg-5.8
3632 3632 indexfile = getattr(rl, 'indexfile')
3633 3633 return getsvfs(repo)(indexfile)
3634 3634 else:
3635 3635 datafile = getattr(rl, 'datafile', getattr(rl, 'datafile'))
3636 3636 return getsvfs(repo)(datafile)
3637 3637
3638 3638 def doread():
3639 3639 rl.clearcaches()
3640 3640 for rev in revs:
3641 3641 segmentforrevs(rev, rev)
3642 3642
3643 3643 def doreadcachedfh():
3644 3644 rl.clearcaches()
3645 3645 fh = rlfh(rl)
3646 3646 for rev in revs:
3647 3647 segmentforrevs(rev, rev, df=fh)
3648 3648
3649 3649 def doreadbatch():
3650 3650 rl.clearcaches()
3651 3651 segmentforrevs(revs[0], revs[-1])
3652 3652
3653 3653 def doreadbatchcachedfh():
3654 3654 rl.clearcaches()
3655 3655 fh = rlfh(rl)
3656 3656 segmentforrevs(revs[0], revs[-1], df=fh)
3657 3657
3658 3658 def dochunk():
3659 3659 rl.clearcaches()
3660 3660 fh = rlfh(rl)
3661 3661 for rev in revs:
3662 3662 rl._chunk(rev, df=fh)
3663 3663
3664 3664 chunks = [None]
3665 3665
3666 3666 def dochunkbatch():
3667 3667 rl.clearcaches()
3668 3668 fh = rlfh(rl)
3669 3669 # Save chunks as a side-effect.
3670 3670 chunks[0] = rl._chunks(revs, df=fh)
3671 3671
3672 3672 def docompress(compressor):
3673 3673 rl.clearcaches()
3674 3674
3675 3675 try:
3676 3676 # Swap in the requested compression engine.
3677 3677 oldcompressor = rl._compressor
3678 3678 rl._compressor = compressor
3679 3679 for chunk in chunks[0]:
3680 3680 rl.compress(chunk)
3681 3681 finally:
3682 3682 rl._compressor = oldcompressor
3683 3683
3684 3684 benches = [
3685 3685 (lambda: doread(), b'read'),
3686 3686 (lambda: doreadcachedfh(), b'read w/ reused fd'),
3687 3687 (lambda: doreadbatch(), b'read batch'),
3688 3688 (lambda: doreadbatchcachedfh(), b'read batch w/ reused fd'),
3689 3689 (lambda: dochunk(), b'chunk'),
3690 3690 (lambda: dochunkbatch(), b'chunk batch'),
3691 3691 ]
3692 3692
3693 3693 for engine in sorted(engines):
3694 3694 compressor = util.compengines[engine].revlogcompressor()
3695 3695 benches.append(
3696 3696 (
3697 3697 functools.partial(docompress, compressor),
3698 3698 b'compress w/ %s' % engine,
3699 3699 )
3700 3700 )
3701 3701
3702 3702 for fn, title in benches:
3703 3703 timer, fm = gettimer(ui, opts)
3704 3704 timer(fn, title=title)
3705 3705 fm.end()
3706 3706
3707 3707
3708 3708 @command(
3709 3709 b'perf::revlogrevision|perfrevlogrevision',
3710 3710 revlogopts
3711 3711 + formatteropts
3712 3712 + [(b'', b'cache', False, b'use caches instead of clearing')],
3713 3713 b'-c|-m|FILE REV',
3714 3714 )
3715 3715 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
3716 3716 """Benchmark obtaining a revlog revision.
3717 3717
3718 3718 Obtaining a revlog revision consists of roughly the following steps:
3719 3719
3720 3720 1. Compute the delta chain
3721 3721 2. Slice the delta chain if applicable
3722 3722 3. Obtain the raw chunks for that delta chain
3723 3723 4. Decompress each raw chunk
3724 3724 5. Apply binary patches to obtain fulltext
3725 3725 6. Verify hash of fulltext
3726 3726
3727 3727 This command measures the time spent in each of these phases.
3728 3728 """
3729 3729 opts = _byteskwargs(opts)
3730 3730
3731 3731 if opts.get(b'changelog') or opts.get(b'manifest'):
3732 3732 file_, rev = None, file_
3733 3733 elif rev is None:
3734 3734 raise error.CommandError(b'perfrevlogrevision', b'invalid arguments')
3735 3735
3736 3736 r = cmdutil.openrevlog(repo, b'perfrevlogrevision', file_, opts)
3737 3737
3738 3738 # _chunkraw was renamed to _getsegmentforrevs.
3739 3739 try:
3740 3740 segmentforrevs = r._getsegmentforrevs
3741 3741 except AttributeError:
3742 3742 segmentforrevs = r._chunkraw
3743 3743
3744 3744 node = r.lookup(rev)
3745 3745 rev = r.rev(node)
3746 3746
3747 3747 def getrawchunks(data, chain):
3748 3748 start = r.start
3749 3749 length = r.length
3750 3750 inline = r._inline
3751 3751 try:
3752 3752 iosize = r.index.entry_size
3753 3753 except AttributeError:
3754 3754 iosize = r._io.size
3755 3755 buffer = util.buffer
3756 3756
3757 3757 chunks = []
3758 3758 ladd = chunks.append
3759 3759 for idx, item in enumerate(chain):
3760 3760 offset = start(item[0])
3761 3761 bits = data[idx]
3762 3762 for rev in item:
3763 3763 chunkstart = start(rev)
3764 3764 if inline:
3765 3765 chunkstart += (rev + 1) * iosize
3766 3766 chunklength = length(rev)
3767 3767 ladd(buffer(bits, chunkstart - offset, chunklength))
3768 3768
3769 3769 return chunks
3770 3770
3771 3771 def dodeltachain(rev):
3772 3772 if not cache:
3773 3773 r.clearcaches()
3774 3774 r._deltachain(rev)
3775 3775
3776 3776 def doread(chain):
3777 3777 if not cache:
3778 3778 r.clearcaches()
3779 3779 for item in slicedchain:
3780 3780 segmentforrevs(item[0], item[-1])
3781 3781
3782 3782 def doslice(r, chain, size):
3783 3783 for s in slicechunk(r, chain, targetsize=size):
3784 3784 pass
3785 3785
3786 3786 def dorawchunks(data, chain):
3787 3787 if not cache:
3788 3788 r.clearcaches()
3789 3789 getrawchunks(data, chain)
3790 3790
3791 3791 def dodecompress(chunks):
3792 3792 decomp = r.decompress
3793 3793 for chunk in chunks:
3794 3794 decomp(chunk)
3795 3795
3796 3796 def dopatch(text, bins):
3797 3797 if not cache:
3798 3798 r.clearcaches()
3799 3799 mdiff.patches(text, bins)
3800 3800
3801 3801 def dohash(text):
3802 3802 if not cache:
3803 3803 r.clearcaches()
3804 3804 r.checkhash(text, node, rev=rev)
3805 3805
3806 3806 def dorevision():
3807 3807 if not cache:
3808 3808 r.clearcaches()
3809 3809 r.revision(node)
3810 3810
3811 3811 try:
3812 3812 from mercurial.revlogutils.deltas import slicechunk
3813 3813 except ImportError:
3814 3814 slicechunk = getattr(revlog, '_slicechunk', None)
3815 3815
3816 3816 size = r.length(rev)
3817 3817 chain = r._deltachain(rev)[0]
3818 3818 if not getattr(r, '_withsparseread', False):
3819 3819 slicedchain = (chain,)
3820 3820 else:
3821 3821 slicedchain = tuple(slicechunk(r, chain, targetsize=size))
3822 3822 data = [segmentforrevs(seg[0], seg[-1])[1] for seg in slicedchain]
3823 3823 rawchunks = getrawchunks(data, slicedchain)
3824 3824 bins = r._chunks(chain)
3825 3825 text = bytes(bins[0])
3826 3826 bins = bins[1:]
3827 3827 text = mdiff.patches(text, bins)
3828 3828
3829 3829 benches = [
3830 3830 (lambda: dorevision(), b'full'),
3831 3831 (lambda: dodeltachain(rev), b'deltachain'),
3832 3832 (lambda: doread(chain), b'read'),
3833 3833 ]
3834 3834
3835 3835 if getattr(r, '_withsparseread', False):
3836 3836 slicing = (lambda: doslice(r, chain, size), b'slice-sparse-chain')
3837 3837 benches.append(slicing)
3838 3838
3839 3839 benches.extend(
3840 3840 [
3841 3841 (lambda: dorawchunks(data, slicedchain), b'rawchunks'),
3842 3842 (lambda: dodecompress(rawchunks), b'decompress'),
3843 3843 (lambda: dopatch(text, bins), b'patch'),
3844 3844 (lambda: dohash(text), b'hash'),
3845 3845 ]
3846 3846 )
3847 3847
3848 3848 timer, fm = gettimer(ui, opts)
3849 3849 for fn, title in benches:
3850 3850 timer(fn, title=title)
3851 3851 fm.end()
3852 3852
3853 3853
3854 3854 @command(
3855 3855 b'perf::revset|perfrevset',
3856 3856 [
3857 3857 (b'C', b'clear', False, b'clear volatile cache between each call.'),
3858 3858 (b'', b'contexts', False, b'obtain changectx for each revision'),
3859 3859 ]
3860 3860 + formatteropts,
3861 3861 b"REVSET",
3862 3862 )
3863 3863 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
3864 3864 """benchmark the execution time of a revset
3865 3865
3866 3866 Use the --clean option if need to evaluate the impact of build volatile
3867 3867 revisions set cache on the revset execution. Volatile cache hold filtered
3868 3868 and obsolete related cache."""
3869 3869 opts = _byteskwargs(opts)
3870 3870
3871 3871 timer, fm = gettimer(ui, opts)
3872 3872
3873 3873 def d():
3874 3874 if clear:
3875 3875 repo.invalidatevolatilesets()
3876 3876 if contexts:
3877 3877 for ctx in repo.set(expr):
3878 3878 pass
3879 3879 else:
3880 3880 for r in repo.revs(expr):
3881 3881 pass
3882 3882
3883 3883 timer(d)
3884 3884 fm.end()
3885 3885
3886 3886
3887 3887 @command(
3888 3888 b'perf::volatilesets|perfvolatilesets',
3889 3889 [
3890 3890 (b'', b'clear-obsstore', False, b'drop obsstore between each call.'),
3891 3891 ]
3892 3892 + formatteropts,
3893 3893 )
3894 3894 def perfvolatilesets(ui, repo, *names, **opts):
3895 3895 """benchmark the computation of various volatile set
3896 3896
3897 3897 Volatile set computes element related to filtering and obsolescence."""
3898 3898 opts = _byteskwargs(opts)
3899 3899 timer, fm = gettimer(ui, opts)
3900 3900 repo = repo.unfiltered()
3901 3901
3902 3902 def getobs(name):
3903 3903 def d():
3904 3904 repo.invalidatevolatilesets()
3905 3905 if opts[b'clear_obsstore']:
3906 3906 clearfilecache(repo, b'obsstore')
3907 3907 obsolete.getrevs(repo, name)
3908 3908
3909 3909 return d
3910 3910
3911 3911 allobs = sorted(obsolete.cachefuncs)
3912 3912 if names:
3913 3913 allobs = [n for n in allobs if n in names]
3914 3914
3915 3915 for name in allobs:
3916 3916 timer(getobs(name), title=name)
3917 3917
3918 3918 def getfiltered(name):
3919 3919 def d():
3920 3920 repo.invalidatevolatilesets()
3921 3921 if opts[b'clear_obsstore']:
3922 3922 clearfilecache(repo, b'obsstore')
3923 3923 repoview.filterrevs(repo, name)
3924 3924
3925 3925 return d
3926 3926
3927 3927 allfilter = sorted(repoview.filtertable)
3928 3928 if names:
3929 3929 allfilter = [n for n in allfilter if n in names]
3930 3930
3931 3931 for name in allfilter:
3932 3932 timer(getfiltered(name), title=name)
3933 3933 fm.end()
3934 3934
3935 3935
3936 3936 @command(
3937 3937 b'perf::branchmap|perfbranchmap',
3938 3938 [
3939 3939 (b'f', b'full', False, b'Includes build time of subset'),
3940 3940 (
3941 3941 b'',
3942 3942 b'clear-revbranch',
3943 3943 False,
3944 3944 b'purge the revbranch cache between computation',
3945 3945 ),
3946 3946 ]
3947 3947 + formatteropts,
3948 3948 )
3949 3949 def perfbranchmap(ui, repo, *filternames, **opts):
3950 3950 """benchmark the update of a branchmap
3951 3951
3952 3952 This benchmarks the full repo.branchmap() call with read and write disabled
3953 3953 """
3954 3954 opts = _byteskwargs(opts)
3955 3955 full = opts.get(b"full", False)
3956 3956 clear_revbranch = opts.get(b"clear_revbranch", False)
3957 3957 timer, fm = gettimer(ui, opts)
3958 3958
3959 3959 def getbranchmap(filtername):
3960 3960 """generate a benchmark function for the filtername"""
3961 3961 if filtername is None:
3962 3962 view = repo
3963 3963 else:
3964 3964 view = repo.filtered(filtername)
3965 3965 if util.safehasattr(view._branchcaches, '_per_filter'):
3966 3966 filtered = view._branchcaches._per_filter
3967 3967 else:
3968 3968 # older versions
3969 3969 filtered = view._branchcaches
3970 3970
3971 3971 def d():
3972 3972 if clear_revbranch:
3973 3973 repo.revbranchcache()._clear()
3974 3974 if full:
3975 3975 view._branchcaches.clear()
3976 3976 else:
3977 3977 filtered.pop(filtername, None)
3978 3978 view.branchmap()
3979 3979
3980 3980 return d
3981 3981
3982 3982 # add filter in smaller subset to bigger subset
3983 3983 possiblefilters = set(repoview.filtertable)
3984 3984 if filternames:
3985 3985 possiblefilters &= set(filternames)
3986 3986 subsettable = getbranchmapsubsettable()
3987 3987 allfilters = []
3988 3988 while possiblefilters:
3989 3989 for name in possiblefilters:
3990 3990 subset = subsettable.get(name)
3991 3991 if subset not in possiblefilters:
3992 3992 break
3993 3993 else:
3994 3994 assert False, b'subset cycle %s!' % possiblefilters
3995 3995 allfilters.append(name)
3996 3996 possiblefilters.remove(name)
3997 3997
3998 3998 # warm the cache
3999 3999 if not full:
4000 4000 for name in allfilters:
4001 4001 repo.filtered(name).branchmap()
4002 4002 if not filternames or b'unfiltered' in filternames:
4003 4003 # add unfiltered
4004 4004 allfilters.append(None)
4005 4005
4006 4006 if util.safehasattr(branchmap.branchcache, 'fromfile'):
4007 4007 branchcacheread = safeattrsetter(branchmap.branchcache, b'fromfile')
4008 4008 branchcacheread.set(classmethod(lambda *args: None))
4009 4009 else:
4010 4010 # older versions
4011 4011 branchcacheread = safeattrsetter(branchmap, b'read')
4012 4012 branchcacheread.set(lambda *args: None)
4013 4013 branchcachewrite = safeattrsetter(branchmap.branchcache, b'write')
4014 4014 branchcachewrite.set(lambda *args: None)
4015 4015 try:
4016 4016 for name in allfilters:
4017 4017 printname = name
4018 4018 if name is None:
4019 4019 printname = b'unfiltered'
4020 4020 timer(getbranchmap(name), title=printname)
4021 4021 finally:
4022 4022 branchcacheread.restore()
4023 4023 branchcachewrite.restore()
4024 4024 fm.end()
4025 4025
4026 4026
4027 4027 @command(
4028 4028 b'perf::branchmapupdate|perfbranchmapupdate',
4029 4029 [
4030 4030 (b'', b'base', [], b'subset of revision to start from'),
4031 4031 (b'', b'target', [], b'subset of revision to end with'),
4032 4032 (b'', b'clear-caches', False, b'clear cache between each runs'),
4033 4033 ]
4034 4034 + formatteropts,
4035 4035 )
4036 4036 def perfbranchmapupdate(ui, repo, base=(), target=(), **opts):
4037 4037 """benchmark branchmap update from for <base> revs to <target> revs
4038 4038
4039 4039 If `--clear-caches` is passed, the following items will be reset before
4040 4040 each update:
4041 4041 * the changelog instance and associated indexes
4042 4042 * the rev-branch-cache instance
4043 4043
4044 4044 Examples:
4045 4045
4046 4046 # update for the one last revision
4047 4047 $ hg perfbranchmapupdate --base 'not tip' --target 'tip'
4048 4048
4049 4049 $ update for change coming with a new branch
4050 4050 $ hg perfbranchmapupdate --base 'stable' --target 'default'
4051 4051 """
4052 4052 from mercurial import branchmap
4053 4053 from mercurial import repoview
4054 4054
4055 4055 opts = _byteskwargs(opts)
4056 4056 timer, fm = gettimer(ui, opts)
4057 4057 clearcaches = opts[b'clear_caches']
4058 4058 unfi = repo.unfiltered()
4059 4059 x = [None] # used to pass data between closure
4060 4060
4061 4061 # we use a `list` here to avoid possible side effect from smartset
4062 4062 baserevs = list(scmutil.revrange(repo, base))
4063 4063 targetrevs = list(scmutil.revrange(repo, target))
4064 4064 if not baserevs:
4065 4065 raise error.Abort(b'no revisions selected for --base')
4066 4066 if not targetrevs:
4067 4067 raise error.Abort(b'no revisions selected for --target')
4068 4068
4069 4069 # make sure the target branchmap also contains the one in the base
4070 4070 targetrevs = list(set(baserevs) | set(targetrevs))
4071 4071 targetrevs.sort()
4072 4072
4073 4073 cl = repo.changelog
4074 4074 allbaserevs = list(cl.ancestors(baserevs, inclusive=True))
4075 4075 allbaserevs.sort()
4076 4076 alltargetrevs = frozenset(cl.ancestors(targetrevs, inclusive=True))
4077 4077
4078 4078 newrevs = list(alltargetrevs.difference(allbaserevs))
4079 4079 newrevs.sort()
4080 4080
4081 4081 allrevs = frozenset(unfi.changelog.revs())
4082 4082 basefilterrevs = frozenset(allrevs.difference(allbaserevs))
4083 4083 targetfilterrevs = frozenset(allrevs.difference(alltargetrevs))
4084 4084
4085 4085 def basefilter(repo, visibilityexceptions=None):
4086 4086 return basefilterrevs
4087 4087
4088 4088 def targetfilter(repo, visibilityexceptions=None):
4089 4089 return targetfilterrevs
4090 4090
4091 4091 msg = b'benchmark of branchmap with %d revisions with %d new ones\n'
4092 4092 ui.status(msg % (len(allbaserevs), len(newrevs)))
4093 4093 if targetfilterrevs:
4094 4094 msg = b'(%d revisions still filtered)\n'
4095 4095 ui.status(msg % len(targetfilterrevs))
4096 4096
4097 4097 try:
4098 4098 repoview.filtertable[b'__perf_branchmap_update_base'] = basefilter
4099 4099 repoview.filtertable[b'__perf_branchmap_update_target'] = targetfilter
4100 4100
4101 4101 baserepo = repo.filtered(b'__perf_branchmap_update_base')
4102 4102 targetrepo = repo.filtered(b'__perf_branchmap_update_target')
4103 4103
4104 4104 # try to find an existing branchmap to reuse
4105 4105 subsettable = getbranchmapsubsettable()
4106 4106 candidatefilter = subsettable.get(None)
4107 4107 while candidatefilter is not None:
4108 4108 candidatebm = repo.filtered(candidatefilter).branchmap()
4109 4109 if candidatebm.validfor(baserepo):
4110 4110 filtered = repoview.filterrevs(repo, candidatefilter)
4111 4111 missing = [r for r in allbaserevs if r in filtered]
4112 4112 base = candidatebm.copy()
4113 4113 base.update(baserepo, missing)
4114 4114 break
4115 4115 candidatefilter = subsettable.get(candidatefilter)
4116 4116 else:
4117 4117 # no suitable subset where found
4118 4118 base = branchmap.branchcache()
4119 4119 base.update(baserepo, allbaserevs)
4120 4120
4121 4121 def setup():
4122 4122 x[0] = base.copy()
4123 4123 if clearcaches:
4124 4124 unfi._revbranchcache = None
4125 4125 clearchangelog(repo)
4126 4126
4127 4127 def bench():
4128 4128 x[0].update(targetrepo, newrevs)
4129 4129
4130 4130 timer(bench, setup=setup)
4131 4131 fm.end()
4132 4132 finally:
4133 4133 repoview.filtertable.pop(b'__perf_branchmap_update_base', None)
4134 4134 repoview.filtertable.pop(b'__perf_branchmap_update_target', None)
4135 4135
4136 4136
4137 4137 @command(
4138 4138 b'perf::branchmapload|perfbranchmapload',
4139 4139 [
4140 4140 (b'f', b'filter', b'', b'Specify repoview filter'),
4141 4141 (b'', b'list', False, b'List brachmap filter caches'),
4142 4142 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
4143 4143 ]
4144 4144 + formatteropts,
4145 4145 )
4146 4146 def perfbranchmapload(ui, repo, filter=b'', list=False, **opts):
4147 4147 """benchmark reading the branchmap"""
4148 4148 opts = _byteskwargs(opts)
4149 4149 clearrevlogs = opts[b'clear_revlogs']
4150 4150
4151 4151 if list:
4152 4152 for name, kind, st in repo.cachevfs.readdir(stat=True):
4153 4153 if name.startswith(b'branch2'):
4154 4154 filtername = name.partition(b'-')[2] or b'unfiltered'
4155 4155 ui.status(
4156 4156 b'%s - %s\n' % (filtername, util.bytecount(st.st_size))
4157 4157 )
4158 4158 return
4159 4159 if not filter:
4160 4160 filter = None
4161 4161 subsettable = getbranchmapsubsettable()
4162 4162 if filter is None:
4163 4163 repo = repo.unfiltered()
4164 4164 else:
4165 4165 repo = repoview.repoview(repo, filter)
4166 4166
4167 4167 repo.branchmap() # make sure we have a relevant, up to date branchmap
4168 4168
4169 4169 try:
4170 4170 fromfile = branchmap.branchcache.fromfile
4171 4171 except AttributeError:
4172 4172 # older versions
4173 4173 fromfile = branchmap.read
4174 4174
4175 4175 currentfilter = filter
4176 4176 # try once without timer, the filter may not be cached
4177 4177 while fromfile(repo) is None:
4178 4178 currentfilter = subsettable.get(currentfilter)
4179 4179 if currentfilter is None:
4180 4180 raise error.Abort(
4181 4181 b'No branchmap cached for %s repo' % (filter or b'unfiltered')
4182 4182 )
4183 4183 repo = repo.filtered(currentfilter)
4184 4184 timer, fm = gettimer(ui, opts)
4185 4185
4186 4186 def setup():
4187 4187 if clearrevlogs:
4188 4188 clearchangelog(repo)
4189 4189
4190 4190 def bench():
4191 4191 fromfile(repo)
4192 4192
4193 4193 timer(bench, setup=setup)
4194 4194 fm.end()
4195 4195
4196 4196
4197 4197 @command(b'perf::loadmarkers|perfloadmarkers')
4198 4198 def perfloadmarkers(ui, repo):
4199 4199 """benchmark the time to parse the on-disk markers for a repo
4200 4200
4201 4201 Result is the number of markers in the repo."""
4202 4202 timer, fm = gettimer(ui)
4203 4203 svfs = getsvfs(repo)
4204 4204 timer(lambda: len(obsolete.obsstore(repo, svfs)))
4205 4205 fm.end()
4206 4206
4207 4207
4208 4208 @command(
4209 4209 b'perf::lrucachedict|perflrucachedict',
4210 4210 formatteropts
4211 4211 + [
4212 4212 (b'', b'costlimit', 0, b'maximum total cost of items in cache'),
4213 4213 (b'', b'mincost', 0, b'smallest cost of items in cache'),
4214 4214 (b'', b'maxcost', 100, b'maximum cost of items in cache'),
4215 4215 (b'', b'size', 4, b'size of cache'),
4216 4216 (b'', b'gets', 10000, b'number of key lookups'),
4217 4217 (b'', b'sets', 10000, b'number of key sets'),
4218 4218 (b'', b'mixed', 10000, b'number of mixed mode operations'),
4219 4219 (
4220 4220 b'',
4221 4221 b'mixedgetfreq',
4222 4222 50,
4223 4223 b'frequency of get vs set ops in mixed mode',
4224 4224 ),
4225 4225 ],
4226 4226 norepo=True,
4227 4227 )
4228 4228 def perflrucache(
4229 4229 ui,
4230 4230 mincost=0,
4231 4231 maxcost=100,
4232 4232 costlimit=0,
4233 4233 size=4,
4234 4234 gets=10000,
4235 4235 sets=10000,
4236 4236 mixed=10000,
4237 4237 mixedgetfreq=50,
4238 4238 **opts
4239 4239 ):
4240 4240 opts = _byteskwargs(opts)
4241 4241
4242 4242 def doinit():
4243 4243 for i in _xrange(10000):
4244 4244 util.lrucachedict(size)
4245 4245
4246 4246 costrange = list(range(mincost, maxcost + 1))
4247 4247
4248 4248 values = []
4249 4249 for i in _xrange(size):
4250 4250 values.append(random.randint(0, _maxint))
4251 4251
4252 4252 # Get mode fills the cache and tests raw lookup performance with no
4253 4253 # eviction.
4254 4254 getseq = []
4255 4255 for i in _xrange(gets):
4256 4256 getseq.append(random.choice(values))
4257 4257
4258 4258 def dogets():
4259 4259 d = util.lrucachedict(size)
4260 4260 for v in values:
4261 4261 d[v] = v
4262 4262 for key in getseq:
4263 4263 value = d[key]
4264 4264 value # silence pyflakes warning
4265 4265
4266 4266 def dogetscost():
4267 4267 d = util.lrucachedict(size, maxcost=costlimit)
4268 4268 for i, v in enumerate(values):
4269 4269 d.insert(v, v, cost=costs[i])
4270 4270 for key in getseq:
4271 4271 try:
4272 4272 value = d[key]
4273 4273 value # silence pyflakes warning
4274 4274 except KeyError:
4275 4275 pass
4276 4276
4277 4277 # Set mode tests insertion speed with cache eviction.
4278 4278 setseq = []
4279 4279 costs = []
4280 4280 for i in _xrange(sets):
4281 4281 setseq.append(random.randint(0, _maxint))
4282 4282 costs.append(random.choice(costrange))
4283 4283
4284 4284 def doinserts():
4285 4285 d = util.lrucachedict(size)
4286 4286 for v in setseq:
4287 4287 d.insert(v, v)
4288 4288
4289 4289 def doinsertscost():
4290 4290 d = util.lrucachedict(size, maxcost=costlimit)
4291 4291 for i, v in enumerate(setseq):
4292 4292 d.insert(v, v, cost=costs[i])
4293 4293
4294 4294 def dosets():
4295 4295 d = util.lrucachedict(size)
4296 4296 for v in setseq:
4297 4297 d[v] = v
4298 4298
4299 4299 # Mixed mode randomly performs gets and sets with eviction.
4300 4300 mixedops = []
4301 4301 for i in _xrange(mixed):
4302 4302 r = random.randint(0, 100)
4303 4303 if r < mixedgetfreq:
4304 4304 op = 0
4305 4305 else:
4306 4306 op = 1
4307 4307
4308 4308 mixedops.append(
4309 4309 (op, random.randint(0, size * 2), random.choice(costrange))
4310 4310 )
4311 4311
4312 4312 def domixed():
4313 4313 d = util.lrucachedict(size)
4314 4314
4315 4315 for op, v, cost in mixedops:
4316 4316 if op == 0:
4317 4317 try:
4318 4318 d[v]
4319 4319 except KeyError:
4320 4320 pass
4321 4321 else:
4322 4322 d[v] = v
4323 4323
4324 4324 def domixedcost():
4325 4325 d = util.lrucachedict(size, maxcost=costlimit)
4326 4326
4327 4327 for op, v, cost in mixedops:
4328 4328 if op == 0:
4329 4329 try:
4330 4330 d[v]
4331 4331 except KeyError:
4332 4332 pass
4333 4333 else:
4334 4334 d.insert(v, v, cost=cost)
4335 4335
4336 4336 benches = [
4337 4337 (doinit, b'init'),
4338 4338 ]
4339 4339
4340 4340 if costlimit:
4341 4341 benches.extend(
4342 4342 [
4343 4343 (dogetscost, b'gets w/ cost limit'),
4344 4344 (doinsertscost, b'inserts w/ cost limit'),
4345 4345 (domixedcost, b'mixed w/ cost limit'),
4346 4346 ]
4347 4347 )
4348 4348 else:
4349 4349 benches.extend(
4350 4350 [
4351 4351 (dogets, b'gets'),
4352 4352 (doinserts, b'inserts'),
4353 4353 (dosets, b'sets'),
4354 4354 (domixed, b'mixed'),
4355 4355 ]
4356 4356 )
4357 4357
4358 4358 for fn, title in benches:
4359 4359 timer, fm = gettimer(ui, opts)
4360 4360 timer(fn, title=title)
4361 4361 fm.end()
4362 4362
4363 4363
4364 4364 @command(
4365 4365 b'perf::write|perfwrite',
4366 4366 formatteropts
4367 4367 + [
4368 4368 (b'', b'write-method', b'write', b'ui write method'),
4369 4369 (b'', b'nlines', 100, b'number of lines'),
4370 4370 (b'', b'nitems', 100, b'number of items (per line)'),
4371 4371 (b'', b'item', b'x', b'item that is written'),
4372 4372 (b'', b'batch-line', None, b'pass whole line to write method at once'),
4373 4373 (b'', b'flush-line', None, b'flush after each line'),
4374 4374 ],
4375 4375 )
4376 4376 def perfwrite(ui, repo, **opts):
4377 4377 """microbenchmark ui.write (and others)"""
4378 4378 opts = _byteskwargs(opts)
4379 4379
4380 4380 write = getattr(ui, _sysstr(opts[b'write_method']))
4381 4381 nlines = int(opts[b'nlines'])
4382 4382 nitems = int(opts[b'nitems'])
4383 4383 item = opts[b'item']
4384 4384 batch_line = opts.get(b'batch_line')
4385 4385 flush_line = opts.get(b'flush_line')
4386 4386
4387 4387 if batch_line:
4388 4388 line = item * nitems + b'\n'
4389 4389
4390 4390 def benchmark():
4391 4391 for i in pycompat.xrange(nlines):
4392 4392 if batch_line:
4393 4393 write(line)
4394 4394 else:
4395 4395 for i in pycompat.xrange(nitems):
4396 4396 write(item)
4397 4397 write(b'\n')
4398 4398 if flush_line:
4399 4399 ui.flush()
4400 4400 ui.flush()
4401 4401
4402 4402 timer, fm = gettimer(ui, opts)
4403 4403 timer(benchmark)
4404 4404 fm.end()
4405 4405
4406 4406
4407 4407 def uisetup(ui):
4408 4408 if util.safehasattr(cmdutil, b'openrevlog') and not util.safehasattr(
4409 4409 commands, b'debugrevlogopts'
4410 4410 ):
4411 4411 # for "historical portability":
4412 4412 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
4413 4413 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
4414 4414 # openrevlog() should cause failure, because it has been
4415 4415 # available since 3.5 (or 49c583ca48c4).
4416 4416 def openrevlog(orig, repo, cmd, file_, opts):
4417 4417 if opts.get(b'dir') and not util.safehasattr(repo, b'dirlog'):
4418 4418 raise error.Abort(
4419 4419 b"This version doesn't support --dir option",
4420 4420 hint=b"use 3.5 or later",
4421 4421 )
4422 4422 return orig(repo, cmd, file_, opts)
4423 4423
4424 4424 name = _sysstr(b'openrevlog')
4425 4425 extensions.wrapfunction(cmdutil, name, openrevlog)
4426 4426
4427 4427
4428 4428 @command(
4429 4429 b'perf::progress|perfprogress',
4430 4430 formatteropts
4431 4431 + [
4432 4432 (b'', b'topic', b'topic', b'topic for progress messages'),
4433 4433 (b'c', b'total', 1000000, b'total value we are progressing to'),
4434 4434 ],
4435 4435 norepo=True,
4436 4436 )
4437 4437 def perfprogress(ui, topic=None, total=None, **opts):
4438 4438 """printing of progress bars"""
4439 4439 opts = _byteskwargs(opts)
4440 4440
4441 4441 timer, fm = gettimer(ui, opts)
4442 4442
4443 4443 def doprogress():
4444 4444 with ui.makeprogress(topic, total=total) as progress:
4445 4445 for i in _xrange(total):
4446 4446 progress.increment()
4447 4447
4448 4448 timer(doprogress)
4449 4449 fm.end()
@@ -1,574 +1,576 b''
1 1 # common.py - common code for the convert extension
2 2 #
3 3 # Copyright 2005-2009 Olivia Mackall <olivia@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import base64
9 9 import datetime
10 10 import os
11 11 import pickle
12 12 import re
13 13 import shlex
14 14 import subprocess
15 15
16 16 from mercurial.i18n import _
17 17 from mercurial.pycompat import open
18 18 from mercurial import (
19 19 encoding,
20 20 error,
21 21 phases,
22 22 pycompat,
23 23 util,
24 24 )
25 25 from mercurial.utils import procutil
26 26
27 27 propertycache = util.propertycache
28 28
29 29
30 30 def _encodeornone(d):
31 31 if d is None:
32 32 return
33 33 return d.encode('latin1')
34 34
35 35
36 36 class _shlexpy3proxy:
37 37 def __init__(self, l):
38 38 self._l = l
39 39
40 40 def __iter__(self):
41 41 return (_encodeornone(v) for v in self._l)
42 42
43 43 def get_token(self):
44 44 return _encodeornone(self._l.get_token())
45 45
46 46 @property
47 47 def infile(self):
48 48 return self._l.infile or b'<unknown>'
49 49
50 50 @property
51 51 def lineno(self):
52 52 return self._l.lineno
53 53
54 54
55 55 def shlexer(data=None, filepath=None, wordchars=None, whitespace=None):
56 56 if data is None:
57 57 data = open(filepath, b'r', encoding='latin1')
58 58 else:
59 59 if filepath is not None:
60 60 raise error.ProgrammingError(
61 61 b'shlexer only accepts data or filepath, not both'
62 62 )
63 63 data = data.decode('latin1')
64 64 l = shlex.shlex(data, infile=filepath, posix=True)
65 65 if whitespace is not None:
66 66 l.whitespace_split = True
67 67 l.whitespace += whitespace.decode('latin1')
68 68 if wordchars is not None:
69 69 l.wordchars += wordchars.decode('latin1')
70 70 return _shlexpy3proxy(l)
71 71
72 72
73 73 def encodeargs(args):
74 74 def encodearg(s):
75 75 lines = base64.encodebytes(s)
76 76 lines = [l.splitlines()[0] for l in pycompat.iterbytestr(lines)]
77 77 return b''.join(lines)
78 78
79 79 s = pickle.dumps(args)
80 80 return encodearg(s)
81 81
82 82
83 83 def decodeargs(s):
84 84 s = base64.decodebytes(s)
85 85 return pickle.loads(s)
86 86
87 87
88 88 class MissingTool(Exception):
89 89 pass
90 90
91 91
92 92 def checktool(exe, name=None, abort=True):
93 93 name = name or exe
94 94 if not procutil.findexe(exe):
95 95 if abort:
96 96 exc = error.Abort
97 97 else:
98 98 exc = MissingTool
99 99 raise exc(_(b'cannot find required "%s" tool') % name)
100 100
101 101
102 102 class NoRepo(Exception):
103 103 pass
104 104
105 105
106 106 SKIPREV = b'SKIP'
107 107
108 108
109 109 class commit:
110 110 def __init__(
111 111 self,
112 112 author,
113 113 date,
114 114 desc,
115 115 parents,
116 116 branch=None,
117 117 rev=None,
118 118 extra=None,
119 119 sortkey=None,
120 120 saverev=True,
121 121 phase=phases.draft,
122 122 optparents=None,
123 123 ctx=None,
124 124 ):
125 125 self.author = author or b'unknown'
126 126 self.date = date or b'0 0'
127 127 self.desc = desc
128 128 self.parents = parents # will be converted and used as parents
129 129 self.optparents = optparents or [] # will be used if already converted
130 130 self.branch = branch
131 131 self.rev = rev
132 132 self.extra = extra or {}
133 133 self.sortkey = sortkey
134 134 self.saverev = saverev
135 135 self.phase = phase
136 136 self.ctx = ctx # for hg to hg conversions
137 137
138 138
139 139 class converter_source:
140 140 """Conversion source interface"""
141 141
142 142 def __init__(self, ui, repotype, path=None, revs=None):
143 143 """Initialize conversion source (or raise NoRepo("message")
144 144 exception if path is not a valid repository)"""
145 145 self.ui = ui
146 146 self.path = path
147 147 self.revs = revs
148 148 self.repotype = repotype
149 149
150 150 self.encoding = b'utf-8'
151 151
152 152 def checkhexformat(self, revstr, mapname=b'splicemap'):
153 153 """fails if revstr is not a 40 byte hex. mercurial and git both uses
154 154 such format for their revision numbering
155 155 """
156 156 if not re.match(br'[0-9a-fA-F]{40,40}$', revstr):
157 157 raise error.Abort(
158 158 _(b'%s entry %s is not a valid revision identifier')
159 159 % (mapname, revstr)
160 160 )
161 161
162 162 def before(self):
163 163 pass
164 164
165 165 def after(self):
166 166 pass
167 167
168 168 def targetfilebelongstosource(self, targetfilename):
169 169 """Returns true if the given targetfile belongs to the source repo. This
170 170 is useful when only a subdirectory of the target belongs to the source
171 171 repo."""
172 172 # For normal full repo converts, this is always True.
173 173 return True
174 174
175 175 def setrevmap(self, revmap):
176 176 """set the map of already-converted revisions"""
177 177
178 178 def getheads(self):
179 179 """Return a list of this repository's heads"""
180 180 raise NotImplementedError
181 181
182 182 def getfile(self, name, rev):
183 183 """Return a pair (data, mode) where data is the file content
184 184 as a string and mode one of '', 'x' or 'l'. rev is the
185 185 identifier returned by a previous call to getchanges().
186 186 Data is None if file is missing/deleted in rev.
187 187 """
188 188 raise NotImplementedError
189 189
190 190 def getchanges(self, version, full):
191 191 """Returns a tuple of (files, copies, cleanp2).
192 192
193 193 files is a sorted list of (filename, id) tuples for all files
194 194 changed between version and its first parent returned by
195 195 getcommit(). If full, all files in that revision is returned.
196 196 id is the source revision id of the file.
197 197
198 198 copies is a dictionary of dest: source
199 199
200 200 cleanp2 is the set of files filenames that are clean against p2.
201 201 (Files that are clean against p1 are already not in files (unless
202 202 full). This makes it possible to handle p2 clean files similarly.)
203 203 """
204 204 raise NotImplementedError
205 205
206 206 def getcommit(self, version):
207 207 """Return the commit object for version"""
208 208 raise NotImplementedError
209 209
210 210 def numcommits(self):
211 211 """Return the number of commits in this source.
212 212
213 213 If unknown, return None.
214 214 """
215 215 return None
216 216
217 217 def gettags(self):
218 218 """Return the tags as a dictionary of name: revision
219 219
220 220 Tag names must be UTF-8 strings.
221 221 """
222 222 raise NotImplementedError
223 223
224 224 def recode(self, s, encoding=None):
225 225 if not encoding:
226 226 encoding = self.encoding or b'utf-8'
227 227
228 228 if isinstance(s, str):
229 229 return s.encode("utf-8")
230 230 try:
231 231 return s.decode(pycompat.sysstr(encoding)).encode("utf-8")
232 232 except UnicodeError:
233 233 try:
234 234 return s.decode("latin-1").encode("utf-8")
235 235 except UnicodeError:
236 236 return s.decode(pycompat.sysstr(encoding), "replace").encode(
237 237 "utf-8"
238 238 )
239 239
240 240 def getchangedfiles(self, rev, i):
241 241 """Return the files changed by rev compared to parent[i].
242 242
243 243 i is an index selecting one of the parents of rev. The return
244 244 value should be the list of files that are different in rev and
245 245 this parent.
246 246
247 247 If rev has no parents, i is None.
248 248
249 249 This function is only needed to support --filemap
250 250 """
251 251 raise NotImplementedError
252 252
253 253 def converted(self, rev, sinkrev):
254 254 '''Notify the source that a revision has been converted.'''
255 255
256 256 def hasnativeorder(self):
257 257 """Return true if this source has a meaningful, native revision
258 258 order. For instance, Mercurial revisions are store sequentially
259 259 while there is no such global ordering with Darcs.
260 260 """
261 261 return False
262 262
263 263 def hasnativeclose(self):
264 264 """Return true if this source has ability to close branch."""
265 265 return False
266 266
267 267 def lookuprev(self, rev):
268 268 """If rev is a meaningful revision reference in source, return
269 269 the referenced identifier in the same format used by getcommit().
270 270 return None otherwise.
271 271 """
272 272 return None
273 273
274 274 def getbookmarks(self):
275 275 """Return the bookmarks as a dictionary of name: revision
276 276
277 277 Bookmark names are to be UTF-8 strings.
278 278 """
279 279 return {}
280 280
281 281 def checkrevformat(self, revstr, mapname=b'splicemap'):
282 282 """revstr is a string that describes a revision in the given
283 283 source control system. Return true if revstr has correct
284 284 format.
285 285 """
286 286 return True
287 287
288 288
289 289 class converter_sink:
290 290 """Conversion sink (target) interface"""
291 291
292 292 def __init__(self, ui, repotype, path):
293 293 """Initialize conversion sink (or raise NoRepo("message")
294 294 exception if path is not a valid repository)
295 295
296 296 created is a list of paths to remove if a fatal error occurs
297 297 later"""
298 298 self.ui = ui
299 299 self.path = path
300 300 self.created = []
301 301 self.repotype = repotype
302 302
303 303 def revmapfile(self):
304 304 """Path to a file that will contain lines
305 305 source_rev_id sink_rev_id
306 306 mapping equivalent revision identifiers for each system."""
307 307 raise NotImplementedError
308 308
309 309 def authorfile(self):
310 310 """Path to a file that will contain lines
311 311 srcauthor=dstauthor
312 312 mapping equivalent authors identifiers for each system."""
313 313 return None
314 314
315 315 def putcommit(
316 316 self, files, copies, parents, commit, source, revmap, full, cleanp2
317 317 ):
318 318 """Create a revision with all changed files listed in 'files'
319 319 and having listed parents. 'commit' is a commit object
320 320 containing at a minimum the author, date, and message for this
321 321 changeset. 'files' is a list of (path, version) tuples,
322 322 'copies' is a dictionary mapping destinations to sources,
323 323 'source' is the source repository, and 'revmap' is a mapfile
324 324 of source revisions to converted revisions. Only getfile() and
325 325 lookuprev() should be called on 'source'. 'full' means that 'files'
326 326 is complete and all other files should be removed.
327 327 'cleanp2' is a set of the filenames that are unchanged from p2
328 328 (only in the common merge case where there two parents).
329 329
330 330 Note that the sink repository is not told to update itself to
331 331 a particular revision (or even what that revision would be)
332 332 before it receives the file data.
333 333 """
334 334 raise NotImplementedError
335 335
336 336 def puttags(self, tags):
337 337 """Put tags into sink.
338 338
339 339 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
340 340 Return a pair (tag_revision, tag_parent_revision), or (None, None)
341 341 if nothing was changed.
342 342 """
343 343 raise NotImplementedError
344 344
345 345 def setbranch(self, branch, pbranches):
346 346 """Set the current branch name. Called before the first putcommit
347 347 on the branch.
348 348 branch: branch name for subsequent commits
349 349 pbranches: (converted parent revision, parent branch) tuples"""
350 350
351 351 def setfilemapmode(self, active):
352 352 """Tell the destination that we're using a filemap
353 353
354 354 Some converter_sources (svn in particular) can claim that a file
355 355 was changed in a revision, even if there was no change. This method
356 356 tells the destination that we're using a filemap and that it should
357 357 filter empty revisions.
358 358 """
359 359
360 360 def before(self):
361 361 pass
362 362
363 363 def after(self):
364 364 pass
365 365
366 366 def putbookmarks(self, bookmarks):
367 367 """Put bookmarks into sink.
368 368
369 369 bookmarks: {bookmarkname: sink_rev_id, ...}
370 370 where bookmarkname is an UTF-8 string.
371 371 """
372 372
373 373 def hascommitfrommap(self, rev):
374 374 """Return False if a rev mentioned in a filemap is known to not be
375 375 present."""
376 376 raise NotImplementedError
377 377
378 378 def hascommitforsplicemap(self, rev):
379 379 """This method is for the special needs for splicemap handling and not
380 380 for general use. Returns True if the sink contains rev, aborts on some
381 381 special cases."""
382 382 raise NotImplementedError
383 383
384 384
385 385 class commandline:
386 386 def __init__(self, ui, command):
387 387 self.ui = ui
388 388 self.command = command
389 389
390 390 def prerun(self):
391 391 pass
392 392
393 393 def postrun(self):
394 394 pass
395 395
396 396 def _cmdline(self, cmd, *args, **kwargs):
397 397 kwargs = pycompat.byteskwargs(kwargs)
398 398 cmdline = [self.command, cmd] + list(args)
399 399 for k, v in kwargs.items():
400 400 if len(k) == 1:
401 401 cmdline.append(b'-' + k)
402 402 else:
403 403 cmdline.append(b'--' + k.replace(b'_', b'-'))
404 404 try:
405 405 if len(k) == 1:
406 406 cmdline.append(b'' + v)
407 407 else:
408 408 cmdline[-1] += b'=' + v
409 409 except TypeError:
410 410 pass
411 411 cmdline = [procutil.shellquote(arg) for arg in cmdline]
412 412 if not self.ui.debugflag:
413 413 cmdline += [b'2>', pycompat.bytestr(os.devnull)]
414 414 cmdline = b' '.join(cmdline)
415 415 return cmdline
416 416
417 417 def _run(self, cmd, *args, **kwargs):
418 418 def popen(cmdline):
419 419 p = subprocess.Popen(
420 420 procutil.tonativestr(cmdline),
421 421 shell=True,
422 422 bufsize=-1,
423 423 close_fds=procutil.closefds,
424 424 stdout=subprocess.PIPE,
425 425 )
426 426 return p
427 427
428 428 return self._dorun(popen, cmd, *args, **kwargs)
429 429
430 430 def _run2(self, cmd, *args, **kwargs):
431 431 return self._dorun(procutil.popen2, cmd, *args, **kwargs)
432 432
433 433 def _run3(self, cmd, *args, **kwargs):
434 434 return self._dorun(procutil.popen3, cmd, *args, **kwargs)
435 435
436 436 def _dorun(self, openfunc, cmd, *args, **kwargs):
437 437 cmdline = self._cmdline(cmd, *args, **kwargs)
438 438 self.ui.debug(b'running: %s\n' % (cmdline,))
439 439 self.prerun()
440 440 try:
441 441 return openfunc(cmdline)
442 442 finally:
443 443 self.postrun()
444 444
445 445 def run(self, cmd, *args, **kwargs):
446 446 p = self._run(cmd, *args, **kwargs)
447 447 output = p.communicate()[0]
448 448 self.ui.debug(output)
449 449 return output, p.returncode
450 450
451 451 def runlines(self, cmd, *args, **kwargs):
452 452 p = self._run(cmd, *args, **kwargs)
453 453 output = p.stdout.readlines()
454 454 p.wait()
455 455 self.ui.debug(b''.join(output))
456 456 return output, p.returncode
457 457
458 458 def checkexit(self, status, output=b''):
459 459 if status:
460 460 if output:
461 461 self.ui.warn(_(b'%s error:\n') % self.command)
462 462 self.ui.warn(output)
463 463 msg = procutil.explainexit(status)
464 464 raise error.Abort(b'%s %s' % (self.command, msg))
465 465
466 466 def run0(self, cmd, *args, **kwargs):
467 467 output, status = self.run(cmd, *args, **kwargs)
468 468 self.checkexit(status, output)
469 469 return output
470 470
471 471 def runlines0(self, cmd, *args, **kwargs):
472 472 output, status = self.runlines(cmd, *args, **kwargs)
473 473 self.checkexit(status, b''.join(output))
474 474 return output
475 475
476 476 @propertycache
477 477 def argmax(self):
478 478 # POSIX requires at least 4096 bytes for ARG_MAX
479 479 argmax = 4096
480 480 try:
481 481 argmax = os.sysconf("SC_ARG_MAX")
482 482 except (AttributeError, ValueError):
483 483 pass
484 484
485 485 # Windows shells impose their own limits on command line length,
486 486 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
487 487 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
488 488 # details about cmd.exe limitations.
489 489
490 490 # Since ARG_MAX is for command line _and_ environment, lower our limit
491 491 # (and make happy Windows shells while doing this).
492 492 return argmax // 2 - 1
493 493
494 494 def _limit_arglist(self, arglist, cmd, *args, **kwargs):
495 495 cmdlen = len(self._cmdline(cmd, *args, **kwargs))
496 496 limit = self.argmax - cmdlen
497 497 numbytes = 0
498 498 fl = []
499 499 for fn in arglist:
500 500 b = len(fn) + 3
501 501 if numbytes + b < limit or len(fl) == 0:
502 502 fl.append(fn)
503 503 numbytes += b
504 504 else:
505 505 yield fl
506 506 fl = [fn]
507 507 numbytes = b
508 508 if fl:
509 509 yield fl
510 510
511 511 def xargs(self, arglist, cmd, *args, **kwargs):
512 512 for l in self._limit_arglist(arglist, cmd, *args, **kwargs):
513 513 self.run0(cmd, *(list(args) + l), **kwargs)
514 514
515 515
516 516 class mapfile(dict):
517 517 def __init__(self, ui, path):
518 518 super(mapfile, self).__init__()
519 519 self.ui = ui
520 520 self.path = path
521 521 self.fp = None
522 522 self.order = []
523 523 self._read()
524 524
525 525 def _read(self):
526 526 if not self.path:
527 527 return
528 528 try:
529 529 fp = open(self.path, b'rb')
530 530 except FileNotFoundError:
531 531 return
532 532 for i, line in enumerate(fp):
533 533 line = line.splitlines()[0].rstrip()
534 534 if not line:
535 535 # Ignore blank lines
536 536 continue
537 537 try:
538 538 key, value = line.rsplit(b' ', 1)
539 539 except ValueError:
540 540 raise error.Abort(
541 541 _(b'syntax error in %s(%d): key/value pair expected')
542 542 % (self.path, i + 1)
543 543 )
544 544 if key not in self:
545 545 self.order.append(key)
546 546 super(mapfile, self).__setitem__(key, value)
547 547 fp.close()
548 548
549 549 def __setitem__(self, key, value):
550 550 if self.fp is None:
551 551 try:
552 552 self.fp = open(self.path, b'ab')
553 553 except IOError as err:
554 554 raise error.Abort(
555 555 _(b'could not open map file %r: %s')
556 556 % (self.path, encoding.strtolocal(err.strerror))
557 557 )
558 558 self.fp.write(util.tonativeeol(b'%s %s\n' % (key, value)))
559 559 self.fp.flush()
560 560 super(mapfile, self).__setitem__(key, value)
561 561
562 562 def close(self):
563 563 if self.fp:
564 564 self.fp.close()
565 565 self.fp = None
566 566
567 567
568 568 def makedatetimestamp(t):
569 569 """Like dateutil.makedate() but for time t instead of current time"""
570 delta = datetime.datetime.utcfromtimestamp(
570 tz = round(
571 571 t
572 ) - datetime.datetime.fromtimestamp(t)
573 tz = delta.days * 86400 + delta.seconds
572 - datetime.datetime.fromtimestamp(t)
573 .replace(tzinfo=datetime.timezone.utc)
574 .timestamp()
575 )
574 576 return t, tz
@@ -1,996 +1,1010 b''
1 1 # extensions.py - extension handling for mercurial
2 2 #
3 3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8
9 9 import ast
10 10 import collections
11 11 import functools
12 import imp
12 import importlib
13 13 import inspect
14 14 import os
15 import sys
15 16
16 17 from .i18n import (
17 18 _,
18 19 gettext,
19 20 )
20 21 from .pycompat import (
21 22 getattr,
22 23 open,
23 24 setattr,
24 25 )
25 26
26 27 from . import (
27 28 cmdutil,
28 29 configitems,
29 30 error,
30 31 pycompat,
31 32 util,
32 33 )
33 34
34 35 from .utils import stringutil
35 36
36 37 _extensions = {}
37 38 _disabledextensions = {}
38 39 _aftercallbacks = {}
39 40 _order = []
40 41 _builtin = {
41 42 b'hbisect',
42 43 b'bookmarks',
43 44 b'color',
44 45 b'parentrevspec',
45 46 b'progress',
46 47 b'interhg',
47 48 b'inotify',
48 49 b'hgcia',
49 50 b'shelve',
50 51 }
51 52
52 53
53 54 def extensions(ui=None):
54 55 if ui:
55 56
56 57 def enabled(name):
57 58 for format in [b'%s', b'hgext.%s']:
58 59 conf = ui.config(b'extensions', format % name)
59 60 if conf is not None and not conf.startswith(b'!'):
60 61 return True
61 62
62 63 else:
63 64 enabled = lambda name: True
64 65 for name in _order:
65 66 module = _extensions[name]
66 67 if module and enabled(name):
67 68 yield name, module
68 69
69 70
70 71 def find(name):
71 72 '''return module with given extension name'''
72 73 mod = None
73 74 try:
74 75 mod = _extensions[name]
75 76 except KeyError:
76 77 for k, v in _extensions.items():
77 78 if k.endswith(b'.' + name) or k.endswith(b'/' + name):
78 79 mod = v
79 80 break
80 81 if not mod:
81 82 raise KeyError(name)
82 83 return mod
83 84
84 85
85 86 def loadpath(path, module_name):
86 87 module_name = module_name.replace(b'.', b'_')
87 88 path = util.normpath(util.expandpath(path))
88 89 module_name = pycompat.fsdecode(module_name)
89 90 path = pycompat.fsdecode(path)
90 91 if os.path.isdir(path):
91 92 # module/__init__.py style
92 d, f = os.path.split(path)
93 fd, fpath, desc = imp.find_module(f, [d])
94 # When https://github.com/python/typeshed/issues/3466 is fixed
95 # and in a pytype release we can drop this disable.
96 return imp.load_module(
97 module_name, fd, fpath, desc # pytype: disable=wrong-arg-types
98 )
99 else:
100 try:
101 return imp.load_source(module_name, path)
102 except IOError as exc:
103 if not exc.filename:
104 exc.filename = path # python does not fill this
105 raise
93 init_py_path = os.path.join(path, '__init__.py')
94 if not os.path.exists(init_py_path):
95 raise ImportError("No module named '%s'" % os.path.basename(path))
96 path = init_py_path
97
98 loader = importlib.machinery.SourceFileLoader(module_name, path)
99 spec = importlib.util.spec_from_file_location(module_name, loader=loader)
100 assert spec is not None # help Pytype
101 module = importlib.util.module_from_spec(spec)
102 sys.modules[module_name] = module
103 spec.loader.exec_module(module)
104 return module
106 105
107 106
108 107 def _importh(name):
109 108 """import and return the <name> module"""
110 109 mod = __import__(pycompat.sysstr(name))
111 110 components = name.split(b'.')
112 111 for comp in components[1:]:
113 112 mod = getattr(mod, comp)
114 113 return mod
115 114
116 115
117 116 def _importext(name, path=None, reportfunc=None):
118 117 if path:
119 118 # the module will be loaded in sys.modules
120 119 # choose an unique name so that it doesn't
121 120 # conflicts with other modules
122 121 mod = loadpath(path, b'hgext.%s' % name)
123 122 else:
124 123 try:
125 124 mod = _importh(b"hgext.%s" % name)
126 125 except ImportError as err:
127 126 if reportfunc:
128 127 reportfunc(err, b"hgext.%s" % name, b"hgext3rd.%s" % name)
129 128 try:
130 129 mod = _importh(b"hgext3rd.%s" % name)
131 130 except ImportError as err:
132 131 if reportfunc:
133 132 reportfunc(err, b"hgext3rd.%s" % name, name)
134 133 mod = _importh(name)
135 134 return mod
136 135
137 136
138 137 def _reportimporterror(ui, err, failed, next):
139 138 # note: this ui.log happens before --debug is processed,
140 139 # Use --config ui.debug=1 to see them.
141 140 ui.log(
142 141 b'extension',
143 142 b' - could not import %s (%s): trying %s\n',
144 143 failed,
145 144 stringutil.forcebytestr(err),
146 145 next,
147 146 )
148 147 if ui.debugflag and ui.configbool(b'devel', b'debug.extensions'):
149 148 ui.traceback()
150 149
151 150
152 151 def _rejectunicode(name, xs):
153 152 if isinstance(xs, (list, set, tuple)):
154 153 for x in xs:
155 154 _rejectunicode(name, x)
156 155 elif isinstance(xs, dict):
157 156 for k, v in xs.items():
158 157 _rejectunicode(name, k)
159 158 _rejectunicode(b'%s.%s' % (name, stringutil.forcebytestr(k)), v)
160 159 elif isinstance(xs, type(u'')):
161 160 raise error.ProgrammingError(
162 161 b"unicode %r found in %s" % (xs, name),
163 162 hint=b"use b'' to make it byte string",
164 163 )
165 164
166 165
167 166 # attributes set by registrar.command
168 167 _cmdfuncattrs = (b'norepo', b'optionalrepo', b'inferrepo')
169 168
170 169
171 170 def _validatecmdtable(ui, cmdtable):
172 171 """Check if extension commands have required attributes"""
173 172 for c, e in cmdtable.items():
174 173 f = e[0]
175 174 missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
176 175 if not missing:
177 176 continue
178 177 raise error.ProgrammingError(
179 178 b'missing attributes: %s' % b', '.join(missing),
180 179 hint=b"use @command decorator to register '%s'" % c,
181 180 )
182 181
183 182
184 183 def _validatetables(ui, mod):
185 184 """Sanity check for loadable tables provided by extension module"""
186 185 for t in [b'cmdtable', b'colortable', b'configtable']:
187 186 _rejectunicode(t, getattr(mod, t, {}))
188 187 for t in [
189 188 b'filesetpredicate',
190 189 b'internalmerge',
191 190 b'revsetpredicate',
192 191 b'templatefilter',
193 192 b'templatefunc',
194 193 b'templatekeyword',
195 194 ]:
196 195 o = getattr(mod, t, None)
197 196 if o:
198 197 _rejectunicode(t, o._table)
199 198 _validatecmdtable(ui, getattr(mod, 'cmdtable', {}))
200 199
201 200
202 201 def load(ui, name, path, loadingtime=None):
203 202 if name.startswith(b'hgext.') or name.startswith(b'hgext/'):
204 203 shortname = name[6:]
205 204 else:
206 205 shortname = name
207 206 if shortname in _builtin:
208 207 return None
209 208 if shortname in _extensions:
210 209 return _extensions[shortname]
211 210 ui.log(b'extension', b' - loading extension: %s\n', shortname)
212 211 _extensions[shortname] = None
213 212 with util.timedcm('load extension %s', shortname) as stats:
214 213 mod = _importext(name, path, bind(_reportimporterror, ui))
215 214 ui.log(b'extension', b' > %s extension loaded in %s\n', shortname, stats)
216 215 if loadingtime is not None:
217 216 loadingtime[shortname] += stats.elapsed
218 217
219 218 # Before we do anything with the extension, check against minimum stated
220 219 # compatibility. This gives extension authors a mechanism to have their
221 220 # extensions short circuit when loaded with a known incompatible version
222 221 # of Mercurial.
223 222 minver = getattr(mod, 'minimumhgversion', None)
224 223 if minver:
225 224 curver = util.versiontuple(n=2)
226 225 extmin = util.versiontuple(stringutil.forcebytestr(minver), 2)
227 226
228 227 if None in extmin:
229 228 extmin = (extmin[0] or 0, extmin[1] or 0)
230 229
231 230 if None in curver or extmin > curver:
232 231 msg = _(
233 232 b'(third party extension %s requires version %s or newer '
234 233 b'of Mercurial (current: %s); disabling)\n'
235 234 )
236 235 ui.warn(msg % (shortname, minver, util.version()))
237 236 return
238 237 ui.log(b'extension', b' - validating extension tables: %s\n', shortname)
239 238 _validatetables(ui, mod)
240 239
241 240 _extensions[shortname] = mod
242 241 _order.append(shortname)
243 242 ui.log(
244 243 b'extension', b' - invoking registered callbacks: %s\n', shortname
245 244 )
246 245 with util.timedcm('callbacks extension %s', shortname) as stats:
247 246 for fn in _aftercallbacks.get(shortname, []):
248 247 fn(loaded=True)
249 248 ui.log(b'extension', b' > callbacks completed in %s\n', stats)
250 249 return mod
251 250
252 251
253 252 def _runuisetup(name, ui):
254 253 uisetup = getattr(_extensions[name], 'uisetup', None)
255 254 if uisetup:
256 255 try:
257 256 uisetup(ui)
258 257 except Exception as inst:
259 258 ui.traceback(force=True)
260 259 msg = stringutil.forcebytestr(inst)
261 260 ui.warn(_(b"*** failed to set up extension %s: %s\n") % (name, msg))
262 261 return False
263 262 return True
264 263
265 264
266 265 def _runextsetup(name, ui):
267 266 extsetup = getattr(_extensions[name], 'extsetup', None)
268 267 if extsetup:
269 268 try:
270 269 extsetup(ui)
271 270 except Exception as inst:
272 271 ui.traceback(force=True)
273 272 msg = stringutil.forcebytestr(inst)
274 273 ui.warn(_(b"*** failed to set up extension %s: %s\n") % (name, msg))
275 274 return False
276 275 return True
277 276
278 277
279 278 def loadall(ui, whitelist=None):
280 279 loadingtime = collections.defaultdict(int)
281 280 result = ui.configitems(b"extensions")
282 281 if whitelist is not None:
283 282 result = [(k, v) for (k, v) in result if k in whitelist]
284 283 result = [(k, v) for (k, v) in result if b':' not in k]
285 284 newindex = len(_order)
286 285 ui.log(
287 286 b'extension',
288 287 b'loading %sextensions\n',
289 288 b'additional ' if newindex else b'',
290 289 )
291 290 ui.log(b'extension', b'- processing %d entries\n', len(result))
292 291 with util.timedcm('load all extensions') as stats:
293 292 default_sub_options = ui.configsuboptions(b"extensions", b"*")[1]
294 293
295 294 for (name, path) in result:
296 295 if path:
297 296 if path[0:1] == b'!':
298 297 if name not in _disabledextensions:
299 298 ui.log(
300 299 b'extension',
301 300 b' - skipping disabled extension: %s\n',
302 301 name,
303 302 )
304 303 _disabledextensions[name] = path[1:]
305 304 continue
306 305 try:
307 306 load(ui, name, path, loadingtime)
308 307 except Exception as inst:
309 308 msg = stringutil.forcebytestr(inst)
310 309 if path:
311 310 error_msg = _(
312 311 b'failed to import extension "%s" from %s: %s'
313 312 )
314 313 error_msg %= (name, path, msg)
315 314 else:
316 315 error_msg = _(b'failed to import extension "%s": %s')
317 316 error_msg %= (name, msg)
318 317
319 318 options = default_sub_options.copy()
320 319 ext_options = ui.configsuboptions(b"extensions", name)[1]
321 320 options.update(ext_options)
322 321 if stringutil.parsebool(options.get(b"required", b'no')):
323 322 hint = None
324 323 if isinstance(inst, error.Hint) and inst.hint:
325 324 hint = inst.hint
326 325 if hint is None:
327 326 hint = _(
328 327 b"loading of this extension was required, "
329 328 b"see `hg help config.extensions` for details"
330 329 )
331 330 raise error.Abort(error_msg, hint=hint)
332 331 else:
333 332 ui.warn((b"*** %s\n") % error_msg)
334 333 if isinstance(inst, error.Hint) and inst.hint:
335 334 ui.warn(_(b"*** (%s)\n") % inst.hint)
336 335 ui.traceback()
337 336
338 337 ui.log(
339 338 b'extension',
340 339 b'> loaded %d extensions, total time %s\n',
341 340 len(_order) - newindex,
342 341 stats,
343 342 )
344 343 # list of (objname, loadermod, loadername) tuple:
345 344 # - objname is the name of an object in extension module,
346 345 # from which extra information is loaded
347 346 # - loadermod is the module where loader is placed
348 347 # - loadername is the name of the function,
349 348 # which takes (ui, extensionname, extraobj) arguments
350 349 #
351 350 # This one is for the list of item that must be run before running any setup
352 351 earlyextraloaders = [
353 352 (b'configtable', configitems, b'loadconfigtable'),
354 353 ]
355 354
356 355 ui.log(b'extension', b'- loading configtable attributes\n')
357 356 _loadextra(ui, newindex, earlyextraloaders)
358 357
359 358 broken = set()
360 359 ui.log(b'extension', b'- executing uisetup hooks\n')
361 360 with util.timedcm('all uisetup') as alluisetupstats:
362 361 for name in _order[newindex:]:
363 362 ui.log(b'extension', b' - running uisetup for %s\n', name)
364 363 with util.timedcm('uisetup %s', name) as stats:
365 364 if not _runuisetup(name, ui):
366 365 ui.log(
367 366 b'extension',
368 367 b' - the %s extension uisetup failed\n',
369 368 name,
370 369 )
371 370 broken.add(name)
372 371 ui.log(b'extension', b' > uisetup for %s took %s\n', name, stats)
373 372 loadingtime[name] += stats.elapsed
374 373 ui.log(b'extension', b'> all uisetup took %s\n', alluisetupstats)
375 374
376 375 ui.log(b'extension', b'- executing extsetup hooks\n')
377 376 with util.timedcm('all extsetup') as allextetupstats:
378 377 for name in _order[newindex:]:
379 378 if name in broken:
380 379 continue
381 380 ui.log(b'extension', b' - running extsetup for %s\n', name)
382 381 with util.timedcm('extsetup %s', name) as stats:
383 382 if not _runextsetup(name, ui):
384 383 ui.log(
385 384 b'extension',
386 385 b' - the %s extension extsetup failed\n',
387 386 name,
388 387 )
389 388 broken.add(name)
390 389 ui.log(b'extension', b' > extsetup for %s took %s\n', name, stats)
391 390 loadingtime[name] += stats.elapsed
392 391 ui.log(b'extension', b'> all extsetup took %s\n', allextetupstats)
393 392
394 393 for name in broken:
395 394 ui.log(b'extension', b' - disabling broken %s extension\n', name)
396 395 _extensions[name] = None
397 396
398 397 # Call aftercallbacks that were never met.
399 398 ui.log(b'extension', b'- executing remaining aftercallbacks\n')
400 399 with util.timedcm('aftercallbacks') as stats:
401 400 for shortname in _aftercallbacks:
402 401 if shortname in _extensions:
403 402 continue
404 403
405 404 for fn in _aftercallbacks[shortname]:
406 405 ui.log(
407 406 b'extension',
408 407 b' - extension %s not loaded, notify callbacks\n',
409 408 shortname,
410 409 )
411 410 fn(loaded=False)
412 411 ui.log(b'extension', b'> remaining aftercallbacks completed in %s\n', stats)
413 412
414 413 # loadall() is called multiple times and lingering _aftercallbacks
415 414 # entries could result in double execution. See issue4646.
416 415 _aftercallbacks.clear()
417 416
418 417 # delay importing avoids cyclic dependency (especially commands)
419 418 from . import (
420 419 color,
421 420 commands,
422 421 filemerge,
423 422 fileset,
424 423 revset,
425 424 templatefilters,
426 425 templatefuncs,
427 426 templatekw,
428 427 )
429 428
430 429 # list of (objname, loadermod, loadername) tuple:
431 430 # - objname is the name of an object in extension module,
432 431 # from which extra information is loaded
433 432 # - loadermod is the module where loader is placed
434 433 # - loadername is the name of the function,
435 434 # which takes (ui, extensionname, extraobj) arguments
436 435 ui.log(b'extension', b'- loading extension registration objects\n')
437 436 extraloaders = [
438 437 (b'cmdtable', commands, b'loadcmdtable'),
439 438 (b'colortable', color, b'loadcolortable'),
440 439 (b'filesetpredicate', fileset, b'loadpredicate'),
441 440 (b'internalmerge', filemerge, b'loadinternalmerge'),
442 441 (b'revsetpredicate', revset, b'loadpredicate'),
443 442 (b'templatefilter', templatefilters, b'loadfilter'),
444 443 (b'templatefunc', templatefuncs, b'loadfunction'),
445 444 (b'templatekeyword', templatekw, b'loadkeyword'),
446 445 ]
447 446 with util.timedcm('load registration objects') as stats:
448 447 _loadextra(ui, newindex, extraloaders)
449 448 ui.log(
450 449 b'extension',
451 450 b'> extension registration object loading took %s\n',
452 451 stats,
453 452 )
454 453
455 454 # Report per extension loading time (except reposetup)
456 455 for name in sorted(loadingtime):
457 456 ui.log(
458 457 b'extension',
459 458 b'> extension %s take a total of %s to load\n',
460 459 name,
461 460 util.timecount(loadingtime[name]),
462 461 )
463 462
464 463 ui.log(b'extension', b'extension loading complete\n')
465 464
466 465
467 466 def _loadextra(ui, newindex, extraloaders):
468 467 for name in _order[newindex:]:
469 468 module = _extensions[name]
470 469 if not module:
471 470 continue # loading this module failed
472 471
473 472 for objname, loadermod, loadername in extraloaders:
474 473 extraobj = getattr(module, objname, None)
475 474 if extraobj is not None:
476 475 getattr(loadermod, loadername)(ui, name, extraobj)
477 476
478 477
479 478 def afterloaded(extension, callback):
480 479 """Run the specified function after a named extension is loaded.
481 480
482 481 If the named extension is already loaded, the callback will be called
483 482 immediately.
484 483
485 484 If the named extension never loads, the callback will be called after
486 485 all extensions have been loaded.
487 486
488 487 The callback receives the named argument ``loaded``, which is a boolean
489 488 indicating whether the dependent extension actually loaded.
490 489 """
491 490
492 491 if extension in _extensions:
493 492 # Report loaded as False if the extension is disabled
494 493 loaded = _extensions[extension] is not None
495 494 callback(loaded=loaded)
496 495 else:
497 496 _aftercallbacks.setdefault(extension, []).append(callback)
498 497
499 498
500 499 def populateui(ui):
501 500 """Run extension hooks on the given ui to populate additional members,
502 501 extend the class dynamically, etc.
503 502
504 503 This will be called after the configuration is loaded, and/or extensions
505 504 are loaded. In general, it's once per ui instance, but in command-server
506 505 and hgweb, this may be called more than once with the same ui.
507 506 """
508 507 for name, mod in extensions(ui):
509 508 hook = getattr(mod, 'uipopulate', None)
510 509 if not hook:
511 510 continue
512 511 try:
513 512 hook(ui)
514 513 except Exception as inst:
515 514 ui.traceback(force=True)
516 515 ui.warn(
517 516 _(b'*** failed to populate ui by extension %s: %s\n')
518 517 % (name, stringutil.forcebytestr(inst))
519 518 )
520 519
521 520
522 521 def bind(func, *args):
523 522 """Partial function application
524 523
525 524 Returns a new function that is the partial application of args and kwargs
526 525 to func. For example,
527 526
528 527 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)"""
529 528 assert callable(func)
530 529
531 530 def closure(*a, **kw):
532 531 return func(*(args + a), **kw)
533 532
534 533 return closure
535 534
536 535
537 536 def _updatewrapper(wrap, origfn, unboundwrapper):
538 537 '''Copy and add some useful attributes to wrapper'''
539 538 try:
540 539 wrap.__name__ = origfn.__name__
541 540 except AttributeError:
542 541 pass
543 542 wrap.__module__ = getattr(origfn, '__module__')
544 543 wrap.__doc__ = getattr(origfn, '__doc__')
545 544 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
546 545 wrap._origfunc = origfn
547 546 wrap._unboundwrapper = unboundwrapper
548 547
549 548
550 549 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
551 550 '''Wrap the command named `command' in table
552 551
553 552 Replace command in the command table with wrapper. The wrapped command will
554 553 be inserted into the command table specified by the table argument.
555 554
556 555 The wrapper will be called like
557 556
558 557 wrapper(orig, *args, **kwargs)
559 558
560 559 where orig is the original (wrapped) function, and *args, **kwargs
561 560 are the arguments passed to it.
562 561
563 562 Optionally append to the command synopsis and docstring, used for help.
564 563 For example, if your extension wraps the ``bookmarks`` command to add the
565 564 flags ``--remote`` and ``--all`` you might call this function like so:
566 565
567 566 synopsis = ' [-a] [--remote]'
568 567 docstring = """
569 568
570 569 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
571 570 flags to the bookmarks command. Either flag will show the remote bookmarks
572 571 known to the repository; ``--remote`` will also suppress the output of the
573 572 local bookmarks.
574 573 """
575 574
576 575 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
577 576 synopsis, docstring)
578 577 '''
579 578 assert callable(wrapper)
580 579 aliases, entry = cmdutil.findcmd(command, table)
581 580 for alias, e in table.items():
582 581 if e is entry:
583 582 key = alias
584 583 break
585 584
586 585 origfn = entry[0]
587 586 wrap = functools.partial(
588 587 util.checksignature(wrapper), util.checksignature(origfn)
589 588 )
590 589 _updatewrapper(wrap, origfn, wrapper)
591 590 if docstring is not None:
592 591 wrap.__doc__ += docstring
593 592
594 593 newentry = list(entry)
595 594 newentry[0] = wrap
596 595 if synopsis is not None:
597 596 newentry[2] += synopsis
598 597 table[key] = tuple(newentry)
599 598 return entry
600 599
601 600
602 601 def wrapfilecache(cls, propname, wrapper):
603 602 """Wraps a filecache property.
604 603
605 604 These can't be wrapped using the normal wrapfunction.
606 605 """
607 606 propname = pycompat.sysstr(propname)
608 607 assert callable(wrapper)
609 608 for currcls in cls.__mro__:
610 609 if propname in currcls.__dict__:
611 610 origfn = currcls.__dict__[propname].func
612 611 assert callable(origfn)
613 612
614 613 def wrap(*args, **kwargs):
615 614 return wrapper(origfn, *args, **kwargs)
616 615
617 616 currcls.__dict__[propname].func = wrap
618 617 break
619 618
620 619 if currcls is object:
621 620 raise AttributeError("type '%s' has no property '%s'" % (cls, propname))
622 621
623 622
624 623 class wrappedfunction:
625 624 '''context manager for temporarily wrapping a function'''
626 625
627 626 def __init__(self, container, funcname, wrapper):
628 627 assert callable(wrapper)
629 628 if not isinstance(funcname, str):
630 629 msg = b"pass wrappedfunction target name as `str`, not `bytes`"
631 630 util.nouideprecwarn(msg, b"6.6", stacklevel=2)
632 631 funcname = pycompat.sysstr(funcname)
633 632 self._container = container
634 633 self._funcname = funcname
635 634 self._wrapper = wrapper
636 635
637 636 def __enter__(self):
638 637 wrapfunction(self._container, self._funcname, self._wrapper)
639 638
640 639 def __exit__(self, exctype, excvalue, traceback):
641 640 unwrapfunction(self._container, self._funcname, self._wrapper)
642 641
643 642
644 643 def wrapfunction(container, funcname, wrapper):
645 644 """Wrap the function named funcname in container
646 645
647 646 Replace the funcname member in the given container with the specified
648 647 wrapper. The container is typically a module, class, or instance.
649 648
650 649 The wrapper will be called like
651 650
652 651 wrapper(orig, *args, **kwargs)
653 652
654 653 where orig is the original (wrapped) function, and *args, **kwargs
655 654 are the arguments passed to it.
656 655
657 656 Wrapping methods of the repository object is not recommended since
658 657 it conflicts with extensions that extend the repository by
659 658 subclassing. All extensions that need to extend methods of
660 659 localrepository should use this subclassing trick: namely,
661 660 reposetup() should look like
662 661
663 662 def reposetup(ui, repo):
664 663 class myrepo(repo.__class__):
665 664 def whatever(self, *args, **kwargs):
666 665 [...extension stuff...]
667 666 super(myrepo, self).whatever(*args, **kwargs)
668 667 [...extension stuff...]
669 668
670 669 repo.__class__ = myrepo
671 670
672 671 In general, combining wrapfunction() with subclassing does not
673 672 work. Since you cannot control what other extensions are loaded by
674 673 your end users, you should play nicely with others by using the
675 674 subclass trick.
676 675 """
677 676 assert callable(wrapper)
678 677
679 678 if not isinstance(funcname, str):
680 679 msg = b"pass wrapfunction target name as `str`, not `bytes`"
681 680 util.nouideprecwarn(msg, b"6.6", stacklevel=2)
682 681 funcname = pycompat.sysstr(funcname)
683 682
684 683 origfn = getattr(container, funcname)
685 684 assert callable(origfn)
686 685 if inspect.ismodule(container):
687 686 # origfn is not an instance or class method. "partial" can be used.
688 687 # "partial" won't insert a frame in traceback.
689 688 wrap = functools.partial(wrapper, origfn)
690 689 else:
691 690 # "partial" cannot be safely used. Emulate its effect by using "bind".
692 691 # The downside is one more frame in traceback.
693 692 wrap = bind(wrapper, origfn)
694 693 _updatewrapper(wrap, origfn, wrapper)
695 694 setattr(container, funcname, wrap)
696 695 return origfn
697 696
698 697
699 698 def unwrapfunction(container, funcname, wrapper=None):
700 699 """undo wrapfunction
701 700
702 701 If wrappers is None, undo the last wrap. Otherwise removes the wrapper
703 702 from the chain of wrappers.
704 703
705 704 Return the removed wrapper.
706 705 Raise IndexError if wrapper is None and nothing to unwrap; ValueError if
707 706 wrapper is not None but is not found in the wrapper chain.
708 707 """
709 708 chain = getwrapperchain(container, funcname)
710 709 origfn = chain.pop()
711 710 if wrapper is None:
712 711 wrapper = chain[0]
713 712 chain.remove(wrapper)
714 713 setattr(container, funcname, origfn)
715 714 for w in reversed(chain):
716 715 wrapfunction(container, funcname, w)
717 716 return wrapper
718 717
719 718
720 719 def getwrapperchain(container, funcname):
721 720 """get a chain of wrappers of a function
722 721
723 722 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc]
724 723
725 724 The wrapper functions are the ones passed to wrapfunction, whose first
726 725 argument is origfunc.
727 726 """
728 727 result = []
729 728 fn = getattr(container, funcname)
730 729 while fn:
731 730 assert callable(fn)
732 731 result.append(getattr(fn, '_unboundwrapper', fn))
733 732 fn = getattr(fn, '_origfunc', None)
734 733 return result
735 734
736 735
737 736 def _disabledpaths():
738 737 '''find paths of disabled extensions. returns a dict of {name: path}'''
739 738 import hgext
740 739
741 740 exts = {}
742 741
743 742 # The hgext might not have a __file__ attribute (e.g. in PyOxidizer) and
744 743 # it might not be on a filesystem even if it does.
745 744 if util.safehasattr(hgext, '__file__'):
746 745 extpath = os.path.dirname(
747 746 util.abspath(pycompat.fsencode(hgext.__file__))
748 747 )
749 748 try:
750 749 files = os.listdir(extpath)
751 750 except OSError:
752 751 pass
753 752 else:
754 753 for e in files:
755 754 if e.endswith(b'.py'):
756 755 name = e.rsplit(b'.', 1)[0]
757 756 path = os.path.join(extpath, e)
758 757 else:
759 758 name = e
760 759 path = os.path.join(extpath, e, b'__init__.py')
761 760 if not os.path.exists(path):
762 761 continue
763 762 if name in exts or name in _order or name == b'__init__':
764 763 continue
765 764 exts[name] = path
766 765
767 766 for name, path in _disabledextensions.items():
768 767 # If no path was provided for a disabled extension (e.g. "color=!"),
769 768 # don't replace the path we already found by the scan above.
770 769 if path:
771 770 exts[name] = path
772 771 return exts
773 772
774 773
775 774 def _moduledoc(file):
776 775 """return the top-level python documentation for the given file
777 776
778 777 Loosely inspired by pydoc.source_synopsis(), but rewritten to
779 778 handle triple quotes and to return the whole text instead of just
780 779 the synopsis"""
781 780 result = []
782 781
783 782 line = file.readline()
784 783 while line[:1] == b'#' or not line.strip():
785 784 line = file.readline()
786 785 if not line:
787 786 break
788 787
789 788 start = line[:3]
790 789 if start == b'"""' or start == b"'''":
791 790 line = line[3:]
792 791 while line:
793 792 if line.rstrip().endswith(start):
794 793 line = line.split(start)[0]
795 794 if line:
796 795 result.append(line)
797 796 break
798 797 elif not line:
799 798 return None # unmatched delimiter
800 799 result.append(line)
801 800 line = file.readline()
802 801 else:
803 802 return None
804 803
805 804 return b''.join(result)
806 805
807 806
808 807 def _disabledhelp(path):
809 808 '''retrieve help synopsis of a disabled extension (without importing)'''
810 809 try:
811 810 with open(path, b'rb') as src:
812 811 doc = _moduledoc(src)
813 812 except IOError:
814 813 return
815 814
816 815 if doc: # extracting localized synopsis
817 816 return gettext(doc)
818 817 else:
819 818 return _(b'(no help text available)')
820 819
821 820
822 821 def disabled():
823 822 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
824 823 try:
825 824 from hgext import __index__ # pytype: disable=import-error
826 825
827 826 return {
828 827 name: gettext(desc)
829 828 for name, desc in __index__.docs.items()
830 829 if name not in _order
831 830 }
832 831 except (ImportError, AttributeError):
833 832 pass
834 833
835 834 paths = _disabledpaths()
836 835 if not paths:
837 836 return {}
838 837
839 838 exts = {}
840 839 for name, path in paths.items():
841 840 doc = _disabledhelp(path)
842 841 if doc and name != b'__index__':
843 842 exts[name] = stringutil.firstline(doc)
844 843
845 844 return exts
846 845
847 846
848 847 def disabled_help(name):
849 848 """Obtain the full help text for a disabled extension, or None."""
850 849 paths = _disabledpaths()
851 850 if name in paths:
852 851 return _disabledhelp(paths[name])
853 852 else:
854 853 try:
855 854 import hgext
856 855 from hgext import __index__ # pytype: disable=import-error
857 856
858 857 # The extensions are filesystem based, so either an error occurred
859 858 # or all are enabled.
860 859 if util.safehasattr(hgext, '__file__'):
861 860 return
862 861
863 862 if name in _order: # enabled
864 863 return
865 864 else:
866 865 return gettext(__index__.docs.get(name))
867 866 except (ImportError, AttributeError):
868 867 pass
869 868
870 869
871 870 def _walkcommand(node):
872 871 """Scan @command() decorators in the tree starting at node"""
873 872 todo = collections.deque([node])
874 873 while todo:
875 874 node = todo.popleft()
876 875 if not isinstance(node, ast.FunctionDef):
877 876 todo.extend(ast.iter_child_nodes(node))
878 877 continue
879 878 for d in node.decorator_list:
880 879 if not isinstance(d, ast.Call):
881 880 continue
882 881 if not isinstance(d.func, ast.Name):
883 882 continue
884 883 if d.func.id != 'command':
885 884 continue
886 885 yield d
887 886
888 887
889 888 def _disabledcmdtable(path):
890 889 """Construct a dummy command table without loading the extension module
891 890
892 891 This may raise IOError or SyntaxError.
893 892 """
894 893 with open(path, b'rb') as src:
895 894 root = ast.parse(src.read(), path)
896 895 cmdtable = {}
896
897 # Python 3.12 started removing Bytes and Str and deprecate harder
898 use_constant = 'Bytes' not in vars(ast)
899
897 900 for node in _walkcommand(root):
898 901 if not node.args:
899 902 continue
900 903 a = node.args[0]
901 if isinstance(a, ast.Str):
902 name = pycompat.sysbytes(a.s)
903 elif isinstance(a, ast.Bytes):
904 name = a.s
905 else:
906 continue
904 if use_constant: # Valid since Python 3.8
905 if isinstance(a, ast.Constant):
906 if isinstance(a.value, str):
907 name = pycompat.sysbytes(a.value)
908 elif isinstance(a.value, bytes):
909 name = a.value
910 else:
911 continue
912 else:
913 continue
914 else: # Valid until 3.11
915 if isinstance(a, ast.Str):
916 name = pycompat.sysbytes(a.s)
917 elif isinstance(a, ast.Bytes):
918 name = a.s
919 else:
920 continue
907 921 cmdtable[name] = (None, [], b'')
908 922 return cmdtable
909 923
910 924
911 925 def _finddisabledcmd(ui, cmd, name, path, strict):
912 926 try:
913 927 cmdtable = _disabledcmdtable(path)
914 928 except (IOError, SyntaxError):
915 929 return
916 930 try:
917 931 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
918 932 except (error.AmbiguousCommand, error.UnknownCommand):
919 933 return
920 934 for c in aliases:
921 935 if c.startswith(cmd):
922 936 cmd = c
923 937 break
924 938 else:
925 939 cmd = aliases[0]
926 940 doc = _disabledhelp(path)
927 941 return (cmd, name, doc)
928 942
929 943
930 944 def disabledcmd(ui, cmd, strict=False):
931 945 """find cmd from disabled extensions without importing.
932 946 returns (cmdname, extname, doc)"""
933 947
934 948 paths = _disabledpaths()
935 949 if not paths:
936 950 raise error.UnknownCommand(cmd)
937 951
938 952 ext = None
939 953 # first, search for an extension with the same name as the command
940 954 path = paths.pop(cmd, None)
941 955 if path:
942 956 ext = _finddisabledcmd(ui, cmd, cmd, path, strict=strict)
943 957 if not ext:
944 958 # otherwise, interrogate each extension until there's a match
945 959 for name, path in paths.items():
946 960 ext = _finddisabledcmd(ui, cmd, name, path, strict=strict)
947 961 if ext:
948 962 break
949 963 if ext:
950 964 return ext
951 965
952 966 raise error.UnknownCommand(cmd)
953 967
954 968
955 969 def enabled(shortname=True):
956 970 '''return a dict of {name: desc} of extensions'''
957 971 exts = {}
958 972 for ename, ext in extensions():
959 973 doc = gettext(ext.__doc__) or _(b'(no help text available)')
960 974 assert doc is not None # help pytype
961 975 if shortname:
962 976 ename = ename.split(b'.')[-1]
963 977 exts[ename] = stringutil.firstline(doc).strip()
964 978
965 979 return exts
966 980
967 981
968 982 def notloaded():
969 983 '''return short names of extensions that failed to load'''
970 984 return [name for name, mod in _extensions.items() if mod is None]
971 985
972 986
973 987 def moduleversion(module):
974 988 '''return version information from given module as a string'''
975 989 if util.safehasattr(module, b'getversion') and callable(module.getversion):
976 990 try:
977 991 version = module.getversion()
978 992 except Exception:
979 993 version = b'unknown'
980 994
981 995 elif util.safehasattr(module, b'__version__'):
982 996 version = module.__version__
983 997 else:
984 998 version = b''
985 999 if isinstance(version, (list, tuple)):
986 1000 version = b'.'.join(pycompat.bytestr(o) for o in version)
987 1001 else:
988 1002 # version data should be bytes, but not all extensions are ported
989 1003 # to py3.
990 1004 version = stringutil.forcebytestr(version)
991 1005 return version
992 1006
993 1007
994 1008 def ismoduleinternal(module):
995 1009 exttestedwith = getattr(module, 'testedwith', None)
996 1010 return exttestedwith == b"ships-with-hg-core"
@@ -1,486 +1,488 b''
1 1 # repoview.py - Filtered view of a localrepo object
2 2 #
3 3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
4 4 # Logilab SA <contact@logilab.fr>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9
10 10 import copy
11 11 import weakref
12 12
13 13 from .i18n import _
14 14 from .node import (
15 15 hex,
16 16 nullrev,
17 17 )
18 18 from .pycompat import (
19 19 delattr,
20 20 getattr,
21 21 setattr,
22 22 )
23 23 from . import (
24 24 error,
25 25 obsolete,
26 26 phases,
27 27 pycompat,
28 28 tags as tagsmod,
29 29 util,
30 30 )
31 31 from .utils import repoviewutil
32 32
33 33
34 34 def hideablerevs(repo):
35 35 """Revision candidates to be hidden
36 36
37 37 This is a standalone function to allow extensions to wrap it.
38 38
39 39 Because we use the set of immutable changesets as a fallback subset in
40 40 branchmap (see mercurial.utils.repoviewutils.subsettable), you cannot set
41 41 "public" changesets as "hideable". Doing so would break multiple code
42 42 assertions and lead to crashes."""
43 43 obsoletes = obsolete.getrevs(repo, b'obsolete')
44 44 internals = repo._phasecache.getrevset(repo, phases.localhiddenphases)
45 45 internals = frozenset(internals)
46 46 return obsoletes | internals
47 47
48 48
49 49 def pinnedrevs(repo):
50 50 """revisions blocking hidden changesets from being filtered"""
51 51
52 52 cl = repo.changelog
53 53 pinned = set()
54 54 pinned.update([par.rev() for par in repo[None].parents()])
55 55 pinned.update([cl.rev(bm) for bm in repo._bookmarks.values()])
56 56
57 57 tags = {}
58 58 tagsmod.readlocaltags(repo.ui, repo, tags, {})
59 59 if tags:
60 60 rev = cl.index.get_rev
61 61 pinned.update(rev(t[0]) for t in tags.values())
62 62 pinned.discard(None)
63 63
64 64 # Avoid cycle: mercurial.filemerge -> mercurial.templater ->
65 65 # mercurial.templatefuncs -> mercurial.revset -> mercurial.repoview ->
66 66 # mercurial.mergestate -> mercurial.filemerge
67 67 from . import mergestate
68 68
69 69 ms = mergestate.mergestate.read(repo)
70 70 if ms.active() and ms.unresolvedcount():
71 71 for node in (ms.local, ms.other):
72 72 rev = cl.index.get_rev(node)
73 73 if rev is not None:
74 74 pinned.add(rev)
75 75
76 76 return pinned
77 77
78 78
79 79 def _revealancestors(pfunc, hidden, revs):
80 80 """reveals contiguous chains of hidden ancestors of 'revs' by removing them
81 81 from 'hidden'
82 82
83 83 - pfunc(r): a funtion returning parent of 'r',
84 84 - hidden: the (preliminary) hidden revisions, to be updated
85 85 - revs: iterable of revnum,
86 86
87 87 (Ancestors are revealed exclusively, i.e. the elements in 'revs' are
88 88 *not* revealed)
89 89 """
90 90 stack = list(revs)
91 91 while stack:
92 92 for p in pfunc(stack.pop()):
93 93 if p != nullrev and p in hidden:
94 94 hidden.remove(p)
95 95 stack.append(p)
96 96
97 97
98 98 def computehidden(repo, visibilityexceptions=None):
99 99 """compute the set of hidden revision to filter
100 100
101 101 During most operation hidden should be filtered."""
102 102 assert not repo.changelog.filteredrevs
103 103
104 104 hidden = hideablerevs(repo)
105 105 if hidden:
106 106 hidden = set(hidden - pinnedrevs(repo))
107 107 if visibilityexceptions:
108 108 hidden -= visibilityexceptions
109 109 pfunc = repo.changelog.parentrevs
110 110 mutable = repo._phasecache.getrevset(repo, phases.mutablephases)
111 111
112 112 visible = mutable - hidden
113 113 _revealancestors(pfunc, hidden, visible)
114 114 return frozenset(hidden)
115 115
116 116
117 117 def computesecret(repo, visibilityexceptions=None):
118 118 """compute the set of revision that can never be exposed through hgweb
119 119
120 120 Changeset in the secret phase (or above) should stay unaccessible."""
121 121 assert not repo.changelog.filteredrevs
122 122 secrets = repo._phasecache.getrevset(repo, phases.remotehiddenphases)
123 123 return frozenset(secrets)
124 124
125 125
126 126 def computeunserved(repo, visibilityexceptions=None):
127 127 """compute the set of revision that should be filtered when used a server
128 128
129 129 Secret and hidden changeset should not pretend to be here."""
130 130 assert not repo.changelog.filteredrevs
131 131 # fast path in simple case to avoid impact of non optimised code
132 132 hiddens = filterrevs(repo, b'visible')
133 133 secrets = filterrevs(repo, b'served.hidden')
134 134 if secrets:
135 135 return frozenset(hiddens | secrets)
136 136 else:
137 137 return hiddens
138 138
139 139
140 140 def computemutable(repo, visibilityexceptions=None):
141 141 assert not repo.changelog.filteredrevs
142 142 # fast check to avoid revset call on huge repo
143 143 if repo._phasecache.hasnonpublicphases(repo):
144 144 return frozenset(repo._phasecache.getrevset(repo, phases.mutablephases))
145 145 return frozenset()
146 146
147 147
148 148 def computeimpactable(repo, visibilityexceptions=None):
149 149 """Everything impactable by mutable revision
150 150
151 151 The immutable filter still have some chance to get invalidated. This will
152 152 happen when:
153 153
154 154 - you garbage collect hidden changeset,
155 155 - public phase is moved backward,
156 156 - something is changed in the filtering (this could be fixed)
157 157
158 158 This filter out any mutable changeset and any public changeset that may be
159 159 impacted by something happening to a mutable revision.
160 160
161 161 This is achieved by filtered everything with a revision number equal or
162 162 higher than the first mutable changeset is filtered."""
163 163 assert not repo.changelog.filteredrevs
164 164 cl = repo.changelog
165 165 firstmutable = len(cl)
166 166 roots = repo._phasecache.nonpublicphaseroots(repo)
167 167 if roots:
168 168 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
169 169 # protect from nullrev root
170 170 firstmutable = max(0, firstmutable)
171 171 return frozenset(range(firstmutable, len(cl)))
172 172
173 173
174 174 # function to compute filtered set
175 175 #
176 176 # When adding a new filter you MUST update the table at:
177 177 # mercurial.utils.repoviewutil.subsettable
178 178 # Otherwise your filter will have to recompute all its branches cache
179 179 # from scratch (very slow).
180 180 filtertable = {
181 181 b'visible': computehidden,
182 182 b'visible-hidden': computehidden,
183 183 b'served.hidden': computesecret,
184 184 b'served': computeunserved,
185 185 b'immutable': computemutable,
186 186 b'base': computeimpactable,
187 187 }
188 188
189 189 # set of filter level that will include the working copy parent no matter what.
190 190 filter_has_wc = {b'visible', b'visible-hidden'}
191 191
192 192 _basefiltername = list(filtertable)
193 193
194 194
195 195 def extrafilter(ui):
196 196 """initialize extra filter and return its id
197 197
198 198 If extra filtering is configured, we make sure the associated filtered view
199 199 are declared and return the associated id.
200 200 """
201 201 frevs = ui.config(b'experimental', b'extra-filter-revs')
202 202 if frevs is None:
203 203 return None
204 204
205 205 fid = pycompat.sysbytes(util.DIGESTS[b'sha1'](frevs).hexdigest())[:12]
206 206
207 207 combine = lambda fname: fname + b'%' + fid
208 208
209 209 subsettable = repoviewutil.subsettable
210 210
211 211 if combine(b'base') not in filtertable:
212 for name in _basefiltername:
212 for base_name in _basefiltername:
213 213
214 def extrafilteredrevs(repo, *args, **kwargs):
214 def extrafilteredrevs(repo, *args, name=base_name, **kwargs):
215 215 baserevs = filtertable[name](repo, *args, **kwargs)
216 216 extrarevs = frozenset(repo.revs(frevs))
217 217 return baserevs | extrarevs
218 218
219 filtertable[combine(name)] = extrafilteredrevs
220 if name in subsettable:
221 subsettable[combine(name)] = combine(subsettable[name])
219 filtertable[combine(base_name)] = extrafilteredrevs
220 if base_name in subsettable:
221 subsettable[combine(base_name)] = combine(
222 subsettable[base_name]
223 )
222 224 return fid
223 225
224 226
225 227 def filterrevs(repo, filtername, visibilityexceptions=None):
226 228 """returns set of filtered revision for this filter name
227 229
228 230 visibilityexceptions is a set of revs which must are exceptions for
229 231 hidden-state and must be visible. They are dynamic and hence we should not
230 232 cache it's result"""
231 233 if filtername not in repo.filteredrevcache:
232 234 if repo.ui.configbool(b'devel', b'debug.repo-filters'):
233 235 msg = b'computing revision filter for "%s"'
234 236 msg %= filtername
235 237 if repo.ui.tracebackflag and repo.ui.debugflag:
236 238 # XXX use ui.write_err
237 239 util.debugstacktrace(
238 240 msg,
239 241 f=repo.ui._fout,
240 242 otherf=repo.ui._ferr,
241 243 prefix=b'debug.filters: ',
242 244 )
243 245 else:
244 246 repo.ui.debug(b'debug.filters: %s\n' % msg)
245 247 func = filtertable[filtername]
246 248 if visibilityexceptions:
247 249 return func(repo.unfiltered, visibilityexceptions)
248 250 repo.filteredrevcache[filtername] = func(repo.unfiltered())
249 251 return repo.filteredrevcache[filtername]
250 252
251 253
252 254 def wrapchangelog(unfichangelog, filteredrevs):
253 255 cl = copy.copy(unfichangelog)
254 256 cl.filteredrevs = filteredrevs
255 257
256 258 class filteredchangelog(filteredchangelogmixin, cl.__class__):
257 259 pass
258 260
259 261 cl.__class__ = filteredchangelog
260 262
261 263 return cl
262 264
263 265
264 266 class filteredchangelogmixin:
265 267 def tiprev(self):
266 268 """filtered version of revlog.tiprev"""
267 269 for i in range(len(self) - 1, -2, -1):
268 270 if i not in self.filteredrevs:
269 271 return i
270 272
271 273 def __contains__(self, rev):
272 274 """filtered version of revlog.__contains__"""
273 275 return 0 <= rev < len(self) and rev not in self.filteredrevs
274 276
275 277 def __iter__(self):
276 278 """filtered version of revlog.__iter__"""
277 279
278 280 def filterediter():
279 281 for i in range(len(self)):
280 282 if i not in self.filteredrevs:
281 283 yield i
282 284
283 285 return filterediter()
284 286
285 287 def revs(self, start=0, stop=None):
286 288 """filtered version of revlog.revs"""
287 289 for i in super(filteredchangelogmixin, self).revs(start, stop):
288 290 if i not in self.filteredrevs:
289 291 yield i
290 292
291 293 def _checknofilteredinrevs(self, revs):
292 294 """raise the appropriate error if 'revs' contains a filtered revision
293 295
294 296 This returns a version of 'revs' to be used thereafter by the caller.
295 297 In particular, if revs is an iterator, it is converted into a set.
296 298 """
297 299 safehasattr = util.safehasattr
298 300 if safehasattr(revs, '__next__'):
299 301 # Note that inspect.isgenerator() is not true for iterators,
300 302 revs = set(revs)
301 303
302 304 filteredrevs = self.filteredrevs
303 305 if safehasattr(revs, 'first'): # smartset
304 306 offenders = revs & filteredrevs
305 307 else:
306 308 offenders = filteredrevs.intersection(revs)
307 309
308 310 for rev in offenders:
309 311 raise error.FilteredIndexError(rev)
310 312 return revs
311 313
312 314 def headrevs(self, revs=None):
313 315 if revs is None:
314 316 try:
315 317 return self.index.headrevsfiltered(self.filteredrevs)
316 318 # AttributeError covers non-c-extension environments and
317 319 # old c extensions without filter handling.
318 320 except AttributeError:
319 321 return self._headrevs()
320 322
321 323 revs = self._checknofilteredinrevs(revs)
322 324 return super(filteredchangelogmixin, self).headrevs(revs)
323 325
324 326 def strip(self, *args, **kwargs):
325 327 # XXX make something better than assert
326 328 # We can't expect proper strip behavior if we are filtered.
327 329 assert not self.filteredrevs
328 330 super(filteredchangelogmixin, self).strip(*args, **kwargs)
329 331
330 332 def rev(self, node):
331 333 """filtered version of revlog.rev"""
332 334 r = super(filteredchangelogmixin, self).rev(node)
333 335 if r in self.filteredrevs:
334 336 raise error.FilteredLookupError(
335 337 hex(node), self.display_id, _(b'filtered node')
336 338 )
337 339 return r
338 340
339 341 def node(self, rev):
340 342 """filtered version of revlog.node"""
341 343 if rev in self.filteredrevs:
342 344 raise error.FilteredIndexError(rev)
343 345 return super(filteredchangelogmixin, self).node(rev)
344 346
345 347 def linkrev(self, rev):
346 348 """filtered version of revlog.linkrev"""
347 349 if rev in self.filteredrevs:
348 350 raise error.FilteredIndexError(rev)
349 351 return super(filteredchangelogmixin, self).linkrev(rev)
350 352
351 353 def parentrevs(self, rev):
352 354 """filtered version of revlog.parentrevs"""
353 355 if rev in self.filteredrevs:
354 356 raise error.FilteredIndexError(rev)
355 357 return super(filteredchangelogmixin, self).parentrevs(rev)
356 358
357 359 def flags(self, rev):
358 360 """filtered version of revlog.flags"""
359 361 if rev in self.filteredrevs:
360 362 raise error.FilteredIndexError(rev)
361 363 return super(filteredchangelogmixin, self).flags(rev)
362 364
363 365
364 366 class repoview:
365 367 """Provide a read/write view of a repo through a filtered changelog
366 368
367 369 This object is used to access a filtered version of a repository without
368 370 altering the original repository object itself. We can not alter the
369 371 original object for two main reasons:
370 372 - It prevents the use of a repo with multiple filters at the same time. In
371 373 particular when multiple threads are involved.
372 374 - It makes scope of the filtering harder to control.
373 375
374 376 This object behaves very closely to the original repository. All attribute
375 377 operations are done on the original repository:
376 378 - An access to `repoview.someattr` actually returns `repo.someattr`,
377 379 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
378 380 - A deletion of `repoview.someattr` actually drops `someattr`
379 381 from `repo.__dict__`.
380 382
381 383 The only exception is the `changelog` property. It is overridden to return
382 384 a (surface) copy of `repo.changelog` with some revisions filtered. The
383 385 `filtername` attribute of the view control the revisions that need to be
384 386 filtered. (the fact the changelog is copied is an implementation detail).
385 387
386 388 Unlike attributes, this object intercepts all method calls. This means that
387 389 all methods are run on the `repoview` object with the filtered `changelog`
388 390 property. For this purpose the simple `repoview` class must be mixed with
389 391 the actual class of the repository. This ensures that the resulting
390 392 `repoview` object have the very same methods than the repo object. This
391 393 leads to the property below.
392 394
393 395 repoview.method() --> repo.__class__.method(repoview)
394 396
395 397 The inheritance has to be done dynamically because `repo` can be of any
396 398 subclasses of `localrepo`. Eg: `bundlerepo` or `statichttprepo`.
397 399 """
398 400
399 401 def __init__(self, repo, filtername, visibilityexceptions=None):
400 402 object.__setattr__(self, '_unfilteredrepo', repo)
401 403 object.__setattr__(self, 'filtername', filtername)
402 404 object.__setattr__(self, '_clcachekey', None)
403 405 object.__setattr__(self, '_clcache', None)
404 406 # revs which are exceptions and must not be hidden
405 407 object.__setattr__(self, '_visibilityexceptions', visibilityexceptions)
406 408
407 409 # not a propertycache on purpose we shall implement a proper cache later
408 410 @property
409 411 def changelog(self):
410 412 """return a filtered version of the changeset
411 413
412 414 this changelog must not be used for writing"""
413 415 # some cache may be implemented later
414 416 unfi = self._unfilteredrepo
415 417 unfichangelog = unfi.changelog
416 418 # bypass call to changelog.method
417 419 unfiindex = unfichangelog.index
418 420 unfilen = len(unfiindex)
419 421 unfinode = unfiindex[unfilen - 1][7]
420 422 with util.timedcm('repo filter for %s', self.filtername):
421 423 revs = filterrevs(unfi, self.filtername, self._visibilityexceptions)
422 424 cl = self._clcache
423 425 newkey = (unfilen, unfinode, hash(revs), unfichangelog._delayed)
424 426 # if cl.index is not unfiindex, unfi.changelog would be
425 427 # recreated, and our clcache refers to garbage object
426 428 if cl is not None and (
427 429 cl.index is not unfiindex or newkey != self._clcachekey
428 430 ):
429 431 cl = None
430 432 # could have been made None by the previous if
431 433 if cl is None:
432 434 # Only filter if there's something to filter
433 435 cl = wrapchangelog(unfichangelog, revs) if revs else unfichangelog
434 436 object.__setattr__(self, '_clcache', cl)
435 437 object.__setattr__(self, '_clcachekey', newkey)
436 438 return cl
437 439
438 440 def unfiltered(self):
439 441 """Return an unfiltered version of a repo"""
440 442 return self._unfilteredrepo
441 443
442 444 def filtered(self, name, visibilityexceptions=None):
443 445 """Return a filtered version of a repository"""
444 446 if name == self.filtername and not visibilityexceptions:
445 447 return self
446 448 return self.unfiltered().filtered(name, visibilityexceptions)
447 449
448 450 def __repr__(self):
449 451 return '<%s:%s %r>' % (
450 452 self.__class__.__name__,
451 453 pycompat.sysstr(self.filtername),
452 454 self.unfiltered(),
453 455 )
454 456
455 457 # everything access are forwarded to the proxied repo
456 458 def __getattr__(self, attr):
457 459 return getattr(self._unfilteredrepo, attr)
458 460
459 461 def __setattr__(self, attr, value):
460 462 return setattr(self._unfilteredrepo, attr, value)
461 463
462 464 def __delattr__(self, attr):
463 465 return delattr(self._unfilteredrepo, attr)
464 466
465 467
466 468 # Dynamically created classes introduce memory cycles via __mro__. See
467 469 # https://bugs.python.org/issue17950.
468 470 # This need of the garbage collector can turn into memory leak in
469 471 # Python <3.4, which is the first version released with PEP 442.
470 472 _filteredrepotypes = weakref.WeakKeyDictionary()
471 473
472 474
473 475 def newtype(base):
474 476 """Create a new type with the repoview mixin and the given base class"""
475 477 ref = _filteredrepotypes.get(base)
476 478 if ref is not None:
477 479 cls = ref()
478 480 if cls is not None:
479 481 return cls
480 482
481 483 class filteredrepo(repoview, base):
482 484 pass
483 485
484 486 _filteredrepotypes[base] = weakref.ref(filteredrepo)
485 487 # do not reread from weakref to be 100% sure not to return None
486 488 return filteredrepo
@@ -1,3527 +1,3530 b''
1 1 # revlog.py - storage back-end for mercurial
2 2 # coding: utf8
3 3 #
4 4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 """Storage back-end for Mercurial.
10 10
11 11 This provides efficient delta storage with O(1) retrieve and append
12 12 and O(changes) merge between branches.
13 13 """
14 14
15 15
16 16 import binascii
17 17 import collections
18 18 import contextlib
19 19 import io
20 20 import os
21 21 import struct
22 22 import weakref
23 23 import zlib
24 24
25 25 # import stuff from node for others to import from revlog
26 26 from .node import (
27 27 bin,
28 28 hex,
29 29 nullrev,
30 30 sha1nodeconstants,
31 31 short,
32 32 wdirrev,
33 33 )
34 34 from .i18n import _
35 35 from .pycompat import getattr
36 36 from .revlogutils.constants import (
37 37 ALL_KINDS,
38 38 CHANGELOGV2,
39 39 COMP_MODE_DEFAULT,
40 40 COMP_MODE_INLINE,
41 41 COMP_MODE_PLAIN,
42 42 DELTA_BASE_REUSE_NO,
43 43 DELTA_BASE_REUSE_TRY,
44 44 ENTRY_RANK,
45 45 FEATURES_BY_VERSION,
46 46 FLAG_GENERALDELTA,
47 47 FLAG_INLINE_DATA,
48 48 INDEX_HEADER,
49 49 KIND_CHANGELOG,
50 50 KIND_FILELOG,
51 51 RANK_UNKNOWN,
52 52 REVLOGV0,
53 53 REVLOGV1,
54 54 REVLOGV1_FLAGS,
55 55 REVLOGV2,
56 56 REVLOGV2_FLAGS,
57 57 REVLOG_DEFAULT_FLAGS,
58 58 REVLOG_DEFAULT_FORMAT,
59 59 REVLOG_DEFAULT_VERSION,
60 60 SUPPORTED_FLAGS,
61 61 )
62 62 from .revlogutils.flagutil import (
63 63 REVIDX_DEFAULT_FLAGS,
64 64 REVIDX_ELLIPSIS,
65 65 REVIDX_EXTSTORED,
66 66 REVIDX_FLAGS_ORDER,
67 67 REVIDX_HASCOPIESINFO,
68 68 REVIDX_ISCENSORED,
69 69 REVIDX_RAWTEXT_CHANGING_FLAGS,
70 70 )
71 71 from .thirdparty import attr
72 72 from . import (
73 73 ancestor,
74 74 dagop,
75 75 error,
76 76 mdiff,
77 77 policy,
78 78 pycompat,
79 79 revlogutils,
80 80 templatefilters,
81 81 util,
82 82 )
83 83 from .interfaces import (
84 84 repository,
85 85 util as interfaceutil,
86 86 )
87 87 from .revlogutils import (
88 88 deltas as deltautil,
89 89 docket as docketutil,
90 90 flagutil,
91 91 nodemap as nodemaputil,
92 92 randomaccessfile,
93 93 revlogv0,
94 94 rewrite,
95 95 sidedata as sidedatautil,
96 96 )
97 97 from .utils import (
98 98 storageutil,
99 99 stringutil,
100 100 )
101 101
102 102 # blanked usage of all the name to prevent pyflakes constraints
103 103 # We need these name available in the module for extensions.
104 104
105 105 REVLOGV0
106 106 REVLOGV1
107 107 REVLOGV2
108 108 CHANGELOGV2
109 109 FLAG_INLINE_DATA
110 110 FLAG_GENERALDELTA
111 111 REVLOG_DEFAULT_FLAGS
112 112 REVLOG_DEFAULT_FORMAT
113 113 REVLOG_DEFAULT_VERSION
114 114 REVLOGV1_FLAGS
115 115 REVLOGV2_FLAGS
116 116 REVIDX_ISCENSORED
117 117 REVIDX_ELLIPSIS
118 118 REVIDX_HASCOPIESINFO
119 119 REVIDX_EXTSTORED
120 120 REVIDX_DEFAULT_FLAGS
121 121 REVIDX_FLAGS_ORDER
122 122 REVIDX_RAWTEXT_CHANGING_FLAGS
123 123
124 124 parsers = policy.importmod('parsers')
125 125 rustancestor = policy.importrust('ancestor')
126 126 rustdagop = policy.importrust('dagop')
127 127 rustrevlog = policy.importrust('revlog')
128 128
129 129 # Aliased for performance.
130 130 _zlibdecompress = zlib.decompress
131 131
132 132 # max size of inline data embedded into a revlog
133 133 _maxinline = 131072
134 134
135 135 # Flag processors for REVIDX_ELLIPSIS.
136 136 def ellipsisreadprocessor(rl, text):
137 137 return text, False
138 138
139 139
140 140 def ellipsiswriteprocessor(rl, text):
141 141 return text, False
142 142
143 143
144 144 def ellipsisrawprocessor(rl, text):
145 145 return False
146 146
147 147
148 148 ellipsisprocessor = (
149 149 ellipsisreadprocessor,
150 150 ellipsiswriteprocessor,
151 151 ellipsisrawprocessor,
152 152 )
153 153
154 154
155 155 def _verify_revision(rl, skipflags, state, node):
156 156 """Verify the integrity of the given revlog ``node`` while providing a hook
157 157 point for extensions to influence the operation."""
158 158 if skipflags:
159 159 state[b'skipread'].add(node)
160 160 else:
161 161 # Side-effect: read content and verify hash.
162 162 rl.revision(node)
163 163
164 164
165 165 # True if a fast implementation for persistent-nodemap is available
166 166 #
167 167 # We also consider we have a "fast" implementation in "pure" python because
168 168 # people using pure don't really have performance consideration (and a
169 169 # wheelbarrow of other slowness source)
170 170 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or util.safehasattr(
171 171 parsers, 'BaseIndexObject'
172 172 )
173 173
174 174
175 175 @interfaceutil.implementer(repository.irevisiondelta)
176 176 @attr.s(slots=True)
177 177 class revlogrevisiondelta:
178 178 node = attr.ib()
179 179 p1node = attr.ib()
180 180 p2node = attr.ib()
181 181 basenode = attr.ib()
182 182 flags = attr.ib()
183 183 baserevisionsize = attr.ib()
184 184 revision = attr.ib()
185 185 delta = attr.ib()
186 186 sidedata = attr.ib()
187 187 protocol_flags = attr.ib()
188 188 linknode = attr.ib(default=None)
189 189
190 190
191 191 @interfaceutil.implementer(repository.iverifyproblem)
192 192 @attr.s(frozen=True)
193 193 class revlogproblem:
194 194 warning = attr.ib(default=None)
195 195 error = attr.ib(default=None)
196 196 node = attr.ib(default=None)
197 197
198 198
199 199 def parse_index_v1(data, inline):
200 200 # call the C implementation to parse the index data
201 201 index, cache = parsers.parse_index2(data, inline)
202 202 return index, cache
203 203
204 204
205 205 def parse_index_v2(data, inline):
206 206 # call the C implementation to parse the index data
207 207 index, cache = parsers.parse_index2(data, inline, format=REVLOGV2)
208 208 return index, cache
209 209
210 210
211 211 def parse_index_cl_v2(data, inline):
212 212 # call the C implementation to parse the index data
213 213 index, cache = parsers.parse_index2(data, inline, format=CHANGELOGV2)
214 214 return index, cache
215 215
216 216
217 217 if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
218 218
219 219 def parse_index_v1_nodemap(data, inline):
220 220 index, cache = parsers.parse_index_devel_nodemap(data, inline)
221 221 return index, cache
222 222
223 223
224 224 else:
225 225 parse_index_v1_nodemap = None
226 226
227 227
228 228 def parse_index_v1_mixed(data, inline):
229 229 index, cache = parse_index_v1(data, inline)
230 230 return rustrevlog.MixedIndex(index), cache
231 231
232 232
233 233 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
234 234 # signed integer)
235 235 _maxentrysize = 0x7FFFFFFF
236 236
237 237 FILE_TOO_SHORT_MSG = _(
238 238 b'cannot read from revlog %s;'
239 239 b' expected %d bytes from offset %d, data size is %d'
240 240 )
241 241
242 242 hexdigits = b'0123456789abcdefABCDEF'
243 243
244 244
245 245 class revlog:
246 246 """
247 247 the underlying revision storage object
248 248
249 249 A revlog consists of two parts, an index and the revision data.
250 250
251 251 The index is a file with a fixed record size containing
252 252 information on each revision, including its nodeid (hash), the
253 253 nodeids of its parents, the position and offset of its data within
254 254 the data file, and the revision it's based on. Finally, each entry
255 255 contains a linkrev entry that can serve as a pointer to external
256 256 data.
257 257
258 258 The revision data itself is a linear collection of data chunks.
259 259 Each chunk represents a revision and is usually represented as a
260 260 delta against the previous chunk. To bound lookup time, runs of
261 261 deltas are limited to about 2 times the length of the original
262 262 version data. This makes retrieval of a version proportional to
263 263 its size, or O(1) relative to the number of revisions.
264 264
265 265 Both pieces of the revlog are written to in an append-only
266 266 fashion, which means we never need to rewrite a file to insert or
267 267 remove data, and can use some simple techniques to avoid the need
268 268 for locking while reading.
269 269
270 270 If checkambig, indexfile is opened with checkambig=True at
271 271 writing, to avoid file stat ambiguity.
272 272
273 273 If mmaplargeindex is True, and an mmapindexthreshold is set, the
274 274 index will be mmapped rather than read if it is larger than the
275 275 configured threshold.
276 276
277 277 If censorable is True, the revlog can have censored revisions.
278 278
279 279 If `upperboundcomp` is not None, this is the expected maximal gain from
280 280 compression for the data content.
281 281
282 282 `concurrencychecker` is an optional function that receives 3 arguments: a
283 283 file handle, a filename, and an expected position. It should check whether
284 284 the current position in the file handle is valid, and log/warn/fail (by
285 285 raising).
286 286
287 287 See mercurial/revlogutils/contants.py for details about the content of an
288 288 index entry.
289 289 """
290 290
291 291 _flagserrorclass = error.RevlogError
292 292
293 293 @staticmethod
294 294 def is_inline_index(header_bytes):
295 295 header = INDEX_HEADER.unpack(header_bytes)[0]
296 296
297 297 _format_flags = header & ~0xFFFF
298 298 _format_version = header & 0xFFFF
299 299
300 300 features = FEATURES_BY_VERSION[_format_version]
301 301 return features[b'inline'](_format_flags)
302 302
303 303 def __init__(
304 304 self,
305 305 opener,
306 306 target,
307 307 radix,
308 308 postfix=None, # only exist for `tmpcensored` now
309 309 checkambig=False,
310 310 mmaplargeindex=False,
311 311 censorable=False,
312 312 upperboundcomp=None,
313 313 persistentnodemap=False,
314 314 concurrencychecker=None,
315 315 trypending=False,
316 316 try_split=False,
317 317 canonical_parent_order=True,
318 318 ):
319 319 """
320 320 create a revlog object
321 321
322 322 opener is a function that abstracts the file opening operation
323 323 and can be used to implement COW semantics or the like.
324 324
325 325 `target`: a (KIND, ID) tuple that identify the content stored in
326 326 this revlog. It help the rest of the code to understand what the revlog
327 327 is about without having to resort to heuristic and index filename
328 328 analysis. Note: that this must be reliably be set by normal code, but
329 329 that test, debug, or performance measurement code might not set this to
330 330 accurate value.
331 331 """
332 332 self.upperboundcomp = upperboundcomp
333 333
334 334 self.radix = radix
335 335
336 336 self._docket_file = None
337 337 self._indexfile = None
338 338 self._datafile = None
339 339 self._sidedatafile = None
340 340 self._nodemap_file = None
341 341 self.postfix = postfix
342 342 self._trypending = trypending
343 343 self._try_split = try_split
344 344 self.opener = opener
345 345 if persistentnodemap:
346 346 self._nodemap_file = nodemaputil.get_nodemap_file(self)
347 347
348 348 assert target[0] in ALL_KINDS
349 349 assert len(target) == 2
350 350 self.target = target
351 351 # When True, indexfile is opened with checkambig=True at writing, to
352 352 # avoid file stat ambiguity.
353 353 self._checkambig = checkambig
354 354 self._mmaplargeindex = mmaplargeindex
355 355 self._censorable = censorable
356 356 # 3-tuple of (node, rev, text) for a raw revision.
357 357 self._revisioncache = None
358 358 # Maps rev to chain base rev.
359 359 self._chainbasecache = util.lrucachedict(100)
360 360 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
361 361 self._chunkcache = (0, b'')
362 362 # How much data to read and cache into the raw revlog data cache.
363 363 self._chunkcachesize = 65536
364 364 self._maxchainlen = None
365 365 self._deltabothparents = True
366 366 self._candidate_group_chunk_size = 0
367 367 self._debug_delta = False
368 368 self.index = None
369 369 self._docket = None
370 370 self._nodemap_docket = None
371 371 # Mapping of partial identifiers to full nodes.
372 372 self._pcache = {}
373 373 # Mapping of revision integer to full node.
374 374 self._compengine = b'zlib'
375 375 self._compengineopts = {}
376 376 self._maxdeltachainspan = -1
377 377 self._withsparseread = False
378 378 self._sparserevlog = False
379 379 self.hassidedata = False
380 380 self._srdensitythreshold = 0.50
381 381 self._srmingapsize = 262144
382 382
383 383 # other optionnals features
384 384
385 385 # might remove rank configuration once the computation has no impact
386 386 self._compute_rank = False
387 387
388 388 # Make copy of flag processors so each revlog instance can support
389 389 # custom flags.
390 390 self._flagprocessors = dict(flagutil.flagprocessors)
391 391
392 392 # 3-tuple of file handles being used for active writing.
393 393 self._writinghandles = None
394 394 # prevent nesting of addgroup
395 395 self._adding_group = None
396 396
397 397 self._loadindex()
398 398
399 399 self._concurrencychecker = concurrencychecker
400 400
401 401 # parent order is supposed to be semantically irrelevant, so we
402 402 # normally resort parents to ensure that the first parent is non-null,
403 403 # if there is a non-null parent at all.
404 404 # filelog abuses the parent order as flag to mark some instances of
405 405 # meta-encoded files, so allow it to disable this behavior.
406 406 self.canonical_parent_order = canonical_parent_order
407 407
408 408 def _init_opts(self):
409 409 """process options (from above/config) to setup associated default revlog mode
410 410
411 411 These values might be affected when actually reading on disk information.
412 412
413 413 The relevant values are returned for use in _loadindex().
414 414
415 415 * newversionflags:
416 416 version header to use if we need to create a new revlog
417 417
418 418 * mmapindexthreshold:
419 419 minimal index size for start to use mmap
420 420
421 421 * force_nodemap:
422 422 force the usage of a "development" version of the nodemap code
423 423 """
424 424 mmapindexthreshold = None
425 425 opts = self.opener.options
426 426
427 427 if b'changelogv2' in opts and self.revlog_kind == KIND_CHANGELOG:
428 428 new_header = CHANGELOGV2
429 429 self._compute_rank = opts.get(b'changelogv2.compute-rank', True)
430 430 elif b'revlogv2' in opts:
431 431 new_header = REVLOGV2
432 432 elif b'revlogv1' in opts:
433 433 new_header = REVLOGV1 | FLAG_INLINE_DATA
434 434 if b'generaldelta' in opts:
435 435 new_header |= FLAG_GENERALDELTA
436 436 elif b'revlogv0' in self.opener.options:
437 437 new_header = REVLOGV0
438 438 else:
439 439 new_header = REVLOG_DEFAULT_VERSION
440 440
441 441 if b'chunkcachesize' in opts:
442 442 self._chunkcachesize = opts[b'chunkcachesize']
443 443 if b'maxchainlen' in opts:
444 444 self._maxchainlen = opts[b'maxchainlen']
445 445 if b'deltabothparents' in opts:
446 446 self._deltabothparents = opts[b'deltabothparents']
447 447 dps_cgds = opts.get(b'delta-parent-search.candidate-group-chunk-size')
448 448 if dps_cgds:
449 449 self._candidate_group_chunk_size = dps_cgds
450 450 self._lazydelta = bool(opts.get(b'lazydelta', True))
451 451 self._lazydeltabase = False
452 452 if self._lazydelta:
453 453 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
454 454 if b'debug-delta' in opts:
455 455 self._debug_delta = opts[b'debug-delta']
456 456 if b'compengine' in opts:
457 457 self._compengine = opts[b'compengine']
458 458 if b'zlib.level' in opts:
459 459 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
460 460 if b'zstd.level' in opts:
461 461 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
462 462 if b'maxdeltachainspan' in opts:
463 463 self._maxdeltachainspan = opts[b'maxdeltachainspan']
464 464 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
465 465 mmapindexthreshold = opts[b'mmapindexthreshold']
466 466 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
467 467 withsparseread = bool(opts.get(b'with-sparse-read', False))
468 468 # sparse-revlog forces sparse-read
469 469 self._withsparseread = self._sparserevlog or withsparseread
470 470 if b'sparse-read-density-threshold' in opts:
471 471 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
472 472 if b'sparse-read-min-gap-size' in opts:
473 473 self._srmingapsize = opts[b'sparse-read-min-gap-size']
474 474 if opts.get(b'enableellipsis'):
475 475 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
476 476
477 477 # revlog v0 doesn't have flag processors
478 478 for flag, processor in opts.get(b'flagprocessors', {}).items():
479 479 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
480 480
481 481 if self._chunkcachesize <= 0:
482 482 raise error.RevlogError(
483 483 _(b'revlog chunk cache size %r is not greater than 0')
484 484 % self._chunkcachesize
485 485 )
486 486 elif self._chunkcachesize & (self._chunkcachesize - 1):
487 487 raise error.RevlogError(
488 488 _(b'revlog chunk cache size %r is not a power of 2')
489 489 % self._chunkcachesize
490 490 )
491 491 force_nodemap = opts.get(b'devel-force-nodemap', False)
492 492 return new_header, mmapindexthreshold, force_nodemap
493 493
494 494 def _get_data(self, filepath, mmap_threshold, size=None):
495 495 """return a file content with or without mmap
496 496
497 497 If the file is missing return the empty string"""
498 498 try:
499 499 with self.opener(filepath) as fp:
500 500 if mmap_threshold is not None:
501 501 file_size = self.opener.fstat(fp).st_size
502 502 if file_size >= mmap_threshold:
503 503 if size is not None:
504 504 # avoid potentiel mmap crash
505 505 size = min(file_size, size)
506 506 # TODO: should .close() to release resources without
507 507 # relying on Python GC
508 508 if size is None:
509 509 return util.buffer(util.mmapread(fp))
510 510 else:
511 511 return util.buffer(util.mmapread(fp, size))
512 512 if size is None:
513 513 return fp.read()
514 514 else:
515 515 return fp.read(size)
516 516 except FileNotFoundError:
517 517 return b''
518 518
519 519 def get_streams(self, max_linkrev, force_inline=False):
520 520 n = len(self)
521 521 index = self.index
522 522 while n > 0:
523 523 linkrev = index[n - 1][4]
524 524 if linkrev < max_linkrev:
525 525 break
526 526 # note: this loop will rarely go through multiple iterations, since
527 527 # it only traverses commits created during the current streaming
528 528 # pull operation.
529 529 #
530 530 # If this become a problem, using a binary search should cap the
531 531 # runtime of this.
532 532 n = n - 1
533 533 if n == 0:
534 534 # no data to send
535 535 return []
536 536 index_size = n * index.entry_size
537 537 data_size = self.end(n - 1)
538 538
539 539 # XXX we might have been split (or stripped) since the object
540 540 # initialization, We need to close this race too, but having a way to
541 541 # pre-open the file we feed to the revlog and never closing them before
542 542 # we are done streaming.
543 543
544 544 if self._inline:
545 545
546 546 def get_stream():
547 547 with self._indexfp() as fp:
548 548 yield None
549 549 size = index_size + data_size
550 550 if size <= 65536:
551 551 yield fp.read(size)
552 552 else:
553 553 yield from util.filechunkiter(fp, limit=size)
554 554
555 555 inline_stream = get_stream()
556 556 next(inline_stream)
557 557 return [
558 558 (self._indexfile, inline_stream, index_size + data_size),
559 559 ]
560 560 elif force_inline:
561 561
562 562 def get_stream():
563 563 with self._datafp() as fp_d:
564 564 yield None
565 565
566 566 for rev in range(n):
567 567 idx = self.index.entry_binary(rev)
568 568 if rev == 0 and self._docket is None:
569 569 # re-inject the inline flag
570 570 header = self._format_flags
571 571 header |= self._format_version
572 572 header |= FLAG_INLINE_DATA
573 573 header = self.index.pack_header(header)
574 574 idx = header + idx
575 575 yield idx
576 576 yield self._getsegmentforrevs(rev, rev, df=fp_d)[1]
577 577
578 578 inline_stream = get_stream()
579 579 next(inline_stream)
580 580 return [
581 581 (self._indexfile, inline_stream, index_size + data_size),
582 582 ]
583 583 else:
584 584
585 585 def get_index_stream():
586 586 with self._indexfp() as fp:
587 587 yield None
588 588 if index_size <= 65536:
589 589 yield fp.read(index_size)
590 590 else:
591 591 yield from util.filechunkiter(fp, limit=index_size)
592 592
593 593 def get_data_stream():
594 594 with self._datafp() as fp:
595 595 yield None
596 596 if data_size <= 65536:
597 597 yield fp.read(data_size)
598 598 else:
599 599 yield from util.filechunkiter(fp, limit=data_size)
600 600
601 601 index_stream = get_index_stream()
602 602 next(index_stream)
603 603 data_stream = get_data_stream()
604 604 next(data_stream)
605 605 return [
606 606 (self._datafile, data_stream, data_size),
607 607 (self._indexfile, index_stream, index_size),
608 608 ]
609 609
610 610 def _loadindex(self, docket=None):
611 611
612 612 new_header, mmapindexthreshold, force_nodemap = self._init_opts()
613 613
614 614 if self.postfix is not None:
615 615 entry_point = b'%s.i.%s' % (self.radix, self.postfix)
616 616 elif self._trypending and self.opener.exists(b'%s.i.a' % self.radix):
617 617 entry_point = b'%s.i.a' % self.radix
618 618 elif self._try_split and self.opener.exists(self._split_index_file):
619 619 entry_point = self._split_index_file
620 620 else:
621 621 entry_point = b'%s.i' % self.radix
622 622
623 623 if docket is not None:
624 624 self._docket = docket
625 625 self._docket_file = entry_point
626 626 else:
627 627 self._initempty = True
628 628 entry_data = self._get_data(entry_point, mmapindexthreshold)
629 629 if len(entry_data) > 0:
630 630 header = INDEX_HEADER.unpack(entry_data[:4])[0]
631 631 self._initempty = False
632 632 else:
633 633 header = new_header
634 634
635 635 self._format_flags = header & ~0xFFFF
636 636 self._format_version = header & 0xFFFF
637 637
638 638 supported_flags = SUPPORTED_FLAGS.get(self._format_version)
639 639 if supported_flags is None:
640 640 msg = _(b'unknown version (%d) in revlog %s')
641 641 msg %= (self._format_version, self.display_id)
642 642 raise error.RevlogError(msg)
643 643 elif self._format_flags & ~supported_flags:
644 644 msg = _(b'unknown flags (%#04x) in version %d revlog %s')
645 645 display_flag = self._format_flags >> 16
646 646 msg %= (display_flag, self._format_version, self.display_id)
647 647 raise error.RevlogError(msg)
648 648
649 649 features = FEATURES_BY_VERSION[self._format_version]
650 650 self._inline = features[b'inline'](self._format_flags)
651 651 self._generaldelta = features[b'generaldelta'](self._format_flags)
652 652 self.hassidedata = features[b'sidedata']
653 653
654 654 if not features[b'docket']:
655 655 self._indexfile = entry_point
656 656 index_data = entry_data
657 657 else:
658 658 self._docket_file = entry_point
659 659 if self._initempty:
660 660 self._docket = docketutil.default_docket(self, header)
661 661 else:
662 662 self._docket = docketutil.parse_docket(
663 663 self, entry_data, use_pending=self._trypending
664 664 )
665 665
666 666 if self._docket is not None:
667 667 self._indexfile = self._docket.index_filepath()
668 668 index_data = b''
669 669 index_size = self._docket.index_end
670 670 if index_size > 0:
671 671 index_data = self._get_data(
672 672 self._indexfile, mmapindexthreshold, size=index_size
673 673 )
674 674 if len(index_data) < index_size:
675 675 msg = _(b'too few index data for %s: got %d, expected %d')
676 676 msg %= (self.display_id, len(index_data), index_size)
677 677 raise error.RevlogError(msg)
678 678
679 679 self._inline = False
680 680 # generaldelta implied by version 2 revlogs.
681 681 self._generaldelta = True
682 682 # the logic for persistent nodemap will be dealt with within the
683 683 # main docket, so disable it for now.
684 684 self._nodemap_file = None
685 685
686 686 if self._docket is not None:
687 687 self._datafile = self._docket.data_filepath()
688 688 self._sidedatafile = self._docket.sidedata_filepath()
689 689 elif self.postfix is None:
690 690 self._datafile = b'%s.d' % self.radix
691 691 else:
692 692 self._datafile = b'%s.d.%s' % (self.radix, self.postfix)
693 693
694 694 self.nodeconstants = sha1nodeconstants
695 695 self.nullid = self.nodeconstants.nullid
696 696
697 697 # sparse-revlog can't be on without general-delta (issue6056)
698 698 if not self._generaldelta:
699 699 self._sparserevlog = False
700 700
701 701 self._storedeltachains = True
702 702
703 703 devel_nodemap = (
704 704 self._nodemap_file
705 705 and force_nodemap
706 706 and parse_index_v1_nodemap is not None
707 707 )
708 708
709 709 use_rust_index = False
710 710 if rustrevlog is not None:
711 711 if self._nodemap_file is not None:
712 712 use_rust_index = True
713 713 else:
714 714 use_rust_index = self.opener.options.get(b'rust.index')
715 715
716 716 self._parse_index = parse_index_v1
717 717 if self._format_version == REVLOGV0:
718 718 self._parse_index = revlogv0.parse_index_v0
719 719 elif self._format_version == REVLOGV2:
720 720 self._parse_index = parse_index_v2
721 721 elif self._format_version == CHANGELOGV2:
722 722 self._parse_index = parse_index_cl_v2
723 723 elif devel_nodemap:
724 724 self._parse_index = parse_index_v1_nodemap
725 725 elif use_rust_index:
726 726 self._parse_index = parse_index_v1_mixed
727 727 try:
728 728 d = self._parse_index(index_data, self._inline)
729 729 index, chunkcache = d
730 730 use_nodemap = (
731 731 not self._inline
732 732 and self._nodemap_file is not None
733 733 and util.safehasattr(index, 'update_nodemap_data')
734 734 )
735 735 if use_nodemap:
736 736 nodemap_data = nodemaputil.persisted_data(self)
737 737 if nodemap_data is not None:
738 738 docket = nodemap_data[0]
739 739 if (
740 740 len(d[0]) > docket.tip_rev
741 741 and d[0][docket.tip_rev][7] == docket.tip_node
742 742 ):
743 743 # no changelog tampering
744 744 self._nodemap_docket = docket
745 745 index.update_nodemap_data(*nodemap_data)
746 746 except (ValueError, IndexError):
747 747 raise error.RevlogError(
748 748 _(b"index %s is corrupted") % self.display_id
749 749 )
750 750 self.index = index
751 751 self._segmentfile = randomaccessfile.randomaccessfile(
752 752 self.opener,
753 753 (self._indexfile if self._inline else self._datafile),
754 754 self._chunkcachesize,
755 755 chunkcache,
756 756 )
757 757 self._segmentfile_sidedata = randomaccessfile.randomaccessfile(
758 758 self.opener,
759 759 self._sidedatafile,
760 760 self._chunkcachesize,
761 761 )
762 762 # revnum -> (chain-length, sum-delta-length)
763 763 self._chaininfocache = util.lrucachedict(500)
764 764 # revlog header -> revlog compressor
765 765 self._decompressors = {}
766 766
767 767 def get_revlog(self):
768 768 """simple function to mirror API of other not-really-revlog API"""
769 769 return self
770 770
771 771 @util.propertycache
772 772 def revlog_kind(self):
773 773 return self.target[0]
774 774
775 775 @util.propertycache
776 776 def display_id(self):
777 777 """The public facing "ID" of the revlog that we use in message"""
778 778 if self.revlog_kind == KIND_FILELOG:
779 779 # Reference the file without the "data/" prefix, so it is familiar
780 780 # to the user.
781 781 return self.target[1]
782 782 else:
783 783 return self.radix
784 784
785 785 def _get_decompressor(self, t):
786 786 try:
787 787 compressor = self._decompressors[t]
788 788 except KeyError:
789 789 try:
790 790 engine = util.compengines.forrevlogheader(t)
791 791 compressor = engine.revlogcompressor(self._compengineopts)
792 792 self._decompressors[t] = compressor
793 793 except KeyError:
794 794 raise error.RevlogError(
795 795 _(b'unknown compression type %s') % binascii.hexlify(t)
796 796 )
797 797 return compressor
798 798
799 799 @util.propertycache
800 800 def _compressor(self):
801 801 engine = util.compengines[self._compengine]
802 802 return engine.revlogcompressor(self._compengineopts)
803 803
804 804 @util.propertycache
805 805 def _decompressor(self):
806 806 """the default decompressor"""
807 807 if self._docket is None:
808 808 return None
809 809 t = self._docket.default_compression_header
810 810 c = self._get_decompressor(t)
811 811 return c.decompress
812 812
813 813 def _indexfp(self):
814 814 """file object for the revlog's index file"""
815 815 return self.opener(self._indexfile, mode=b"r")
816 816
817 817 def __index_write_fp(self):
818 818 # You should not use this directly and use `_writing` instead
819 819 try:
820 820 f = self.opener(
821 821 self._indexfile, mode=b"r+", checkambig=self._checkambig
822 822 )
823 823 if self._docket is None:
824 824 f.seek(0, os.SEEK_END)
825 825 else:
826 826 f.seek(self._docket.index_end, os.SEEK_SET)
827 827 return f
828 828 except FileNotFoundError:
829 829 return self.opener(
830 830 self._indexfile, mode=b"w+", checkambig=self._checkambig
831 831 )
832 832
833 833 def __index_new_fp(self):
834 834 # You should not use this unless you are upgrading from inline revlog
835 835 return self.opener(
836 836 self._indexfile,
837 837 mode=b"w",
838 838 checkambig=self._checkambig,
839 839 atomictemp=True,
840 840 )
841 841
842 842 def _datafp(self, mode=b'r'):
843 843 """file object for the revlog's data file"""
844 844 return self.opener(self._datafile, mode=mode)
845 845
846 846 @contextlib.contextmanager
847 847 def _sidedatareadfp(self):
848 848 """file object suitable to read sidedata"""
849 849 if self._writinghandles:
850 850 yield self._writinghandles[2]
851 851 else:
852 852 with self.opener(self._sidedatafile) as fp:
853 853 yield fp
854 854
855 855 def tiprev(self):
856 856 return len(self.index) - 1
857 857
858 858 def tip(self):
859 859 return self.node(self.tiprev())
860 860
861 861 def __contains__(self, rev):
862 862 return 0 <= rev < len(self)
863 863
864 864 def __len__(self):
865 865 return len(self.index)
866 866
867 867 def __iter__(self):
868 868 return iter(range(len(self)))
869 869
870 870 def revs(self, start=0, stop=None):
871 871 """iterate over all rev in this revlog (from start to stop)"""
872 872 return storageutil.iterrevs(len(self), start=start, stop=stop)
873 873
874 874 def hasnode(self, node):
875 875 try:
876 876 self.rev(node)
877 877 return True
878 878 except KeyError:
879 879 return False
880 880
881 881 def candelta(self, baserev, rev):
882 882 """whether two revisions (baserev, rev) can be delta-ed or not"""
883 883 # Disable delta if either rev requires a content-changing flag
884 884 # processor (ex. LFS). This is because such flag processor can alter
885 885 # the rawtext content that the delta will be based on, and two clients
886 886 # could have a same revlog node with different flags (i.e. different
887 887 # rawtext contents) and the delta could be incompatible.
888 888 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
889 889 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
890 890 ):
891 891 return False
892 892 return True
893 893
894 894 def update_caches(self, transaction):
895 895 if self._nodemap_file is not None:
896 896 if transaction is None:
897 897 nodemaputil.update_persistent_nodemap(self)
898 898 else:
899 899 nodemaputil.setup_persistent_nodemap(transaction, self)
900 900
901 901 def clearcaches(self):
902 902 self._revisioncache = None
903 903 self._chainbasecache.clear()
904 904 self._segmentfile.clear_cache()
905 905 self._segmentfile_sidedata.clear_cache()
906 906 self._pcache = {}
907 907 self._nodemap_docket = None
908 908 self.index.clearcaches()
909 909 # The python code is the one responsible for validating the docket, we
910 910 # end up having to refresh it here.
911 911 use_nodemap = (
912 912 not self._inline
913 913 and self._nodemap_file is not None
914 914 and util.safehasattr(self.index, 'update_nodemap_data')
915 915 )
916 916 if use_nodemap:
917 917 nodemap_data = nodemaputil.persisted_data(self)
918 918 if nodemap_data is not None:
919 919 self._nodemap_docket = nodemap_data[0]
920 920 self.index.update_nodemap_data(*nodemap_data)
921 921
922 922 def rev(self, node):
923 923 try:
924 924 return self.index.rev(node)
925 925 except TypeError:
926 926 raise
927 927 except error.RevlogError:
928 928 # parsers.c radix tree lookup failed
929 929 if (
930 930 node == self.nodeconstants.wdirid
931 931 or node in self.nodeconstants.wdirfilenodeids
932 932 ):
933 933 raise error.WdirUnsupported
934 934 raise error.LookupError(node, self.display_id, _(b'no node'))
935 935
936 936 # Accessors for index entries.
937 937
938 938 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
939 939 # are flags.
940 940 def start(self, rev):
941 941 return int(self.index[rev][0] >> 16)
942 942
943 943 def sidedata_cut_off(self, rev):
944 944 sd_cut_off = self.index[rev][8]
945 945 if sd_cut_off != 0:
946 946 return sd_cut_off
947 947 # This is some annoying dance, because entries without sidedata
948 948 # currently use 0 as their ofsset. (instead of previous-offset +
949 949 # previous-size)
950 950 #
951 951 # We should reconsider this sidedata → 0 sidata_offset policy.
952 952 # In the meantime, we need this.
953 953 while 0 <= rev:
954 954 e = self.index[rev]
955 955 if e[9] != 0:
956 956 return e[8] + e[9]
957 957 rev -= 1
958 958 return 0
959 959
960 960 def flags(self, rev):
961 961 return self.index[rev][0] & 0xFFFF
962 962
963 963 def length(self, rev):
964 964 return self.index[rev][1]
965 965
966 966 def sidedata_length(self, rev):
967 967 if not self.hassidedata:
968 968 return 0
969 969 return self.index[rev][9]
970 970
971 971 def rawsize(self, rev):
972 972 """return the length of the uncompressed text for a given revision"""
973 973 l = self.index[rev][2]
974 974 if l >= 0:
975 975 return l
976 976
977 977 t = self.rawdata(rev)
978 978 return len(t)
979 979
980 980 def size(self, rev):
981 981 """length of non-raw text (processed by a "read" flag processor)"""
982 982 # fast path: if no "read" flag processor could change the content,
983 983 # size is rawsize. note: ELLIPSIS is known to not change the content.
984 984 flags = self.flags(rev)
985 985 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
986 986 return self.rawsize(rev)
987 987
988 988 return len(self.revision(rev))
989 989
990 990 def fast_rank(self, rev):
991 991 """Return the rank of a revision if already known, or None otherwise.
992 992
993 993 The rank of a revision is the size of the sub-graph it defines as a
994 994 head. Equivalently, the rank of a revision `r` is the size of the set
995 995 `ancestors(r)`, `r` included.
996 996
997 997 This method returns the rank retrieved from the revlog in constant
998 998 time. It makes no attempt at computing unknown values for versions of
999 999 the revlog which do not persist the rank.
1000 1000 """
1001 1001 rank = self.index[rev][ENTRY_RANK]
1002 1002 if self._format_version != CHANGELOGV2 or rank == RANK_UNKNOWN:
1003 1003 return None
1004 1004 if rev == nullrev:
1005 1005 return 0 # convention
1006 1006 return rank
1007 1007
1008 1008 def chainbase(self, rev):
1009 1009 base = self._chainbasecache.get(rev)
1010 1010 if base is not None:
1011 1011 return base
1012 1012
1013 1013 index = self.index
1014 1014 iterrev = rev
1015 1015 base = index[iterrev][3]
1016 1016 while base != iterrev:
1017 1017 iterrev = base
1018 1018 base = index[iterrev][3]
1019 1019
1020 1020 self._chainbasecache[rev] = base
1021 1021 return base
1022 1022
1023 1023 def linkrev(self, rev):
1024 1024 return self.index[rev][4]
1025 1025
1026 1026 def parentrevs(self, rev):
1027 1027 try:
1028 1028 entry = self.index[rev]
1029 1029 except IndexError:
1030 1030 if rev == wdirrev:
1031 1031 raise error.WdirUnsupported
1032 1032 raise
1033 1033
1034 1034 if self.canonical_parent_order and entry[5] == nullrev:
1035 1035 return entry[6], entry[5]
1036 1036 else:
1037 1037 return entry[5], entry[6]
1038 1038
1039 1039 # fast parentrevs(rev) where rev isn't filtered
1040 1040 _uncheckedparentrevs = parentrevs
1041 1041
1042 1042 def node(self, rev):
1043 1043 try:
1044 1044 return self.index[rev][7]
1045 1045 except IndexError:
1046 1046 if rev == wdirrev:
1047 1047 raise error.WdirUnsupported
1048 1048 raise
1049 1049
1050 1050 # Derived from index values.
1051 1051
1052 1052 def end(self, rev):
1053 1053 return self.start(rev) + self.length(rev)
1054 1054
1055 1055 def parents(self, node):
1056 1056 i = self.index
1057 1057 d = i[self.rev(node)]
1058 1058 # inline node() to avoid function call overhead
1059 1059 if self.canonical_parent_order and d[5] == self.nullid:
1060 1060 return i[d[6]][7], i[d[5]][7]
1061 1061 else:
1062 1062 return i[d[5]][7], i[d[6]][7]
1063 1063
1064 1064 def chainlen(self, rev):
1065 1065 return self._chaininfo(rev)[0]
1066 1066
1067 1067 def _chaininfo(self, rev):
1068 1068 chaininfocache = self._chaininfocache
1069 1069 if rev in chaininfocache:
1070 1070 return chaininfocache[rev]
1071 1071 index = self.index
1072 1072 generaldelta = self._generaldelta
1073 1073 iterrev = rev
1074 1074 e = index[iterrev]
1075 1075 clen = 0
1076 1076 compresseddeltalen = 0
1077 1077 while iterrev != e[3]:
1078 1078 clen += 1
1079 1079 compresseddeltalen += e[1]
1080 1080 if generaldelta:
1081 1081 iterrev = e[3]
1082 1082 else:
1083 1083 iterrev -= 1
1084 1084 if iterrev in chaininfocache:
1085 1085 t = chaininfocache[iterrev]
1086 1086 clen += t[0]
1087 1087 compresseddeltalen += t[1]
1088 1088 break
1089 1089 e = index[iterrev]
1090 1090 else:
1091 1091 # Add text length of base since decompressing that also takes
1092 1092 # work. For cache hits the length is already included.
1093 1093 compresseddeltalen += e[1]
1094 1094 r = (clen, compresseddeltalen)
1095 1095 chaininfocache[rev] = r
1096 1096 return r
1097 1097
1098 1098 def _deltachain(self, rev, stoprev=None):
1099 1099 """Obtain the delta chain for a revision.
1100 1100
1101 1101 ``stoprev`` specifies a revision to stop at. If not specified, we
1102 1102 stop at the base of the chain.
1103 1103
1104 1104 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
1105 1105 revs in ascending order and ``stopped`` is a bool indicating whether
1106 1106 ``stoprev`` was hit.
1107 1107 """
1108 1108 # Try C implementation.
1109 1109 try:
1110 1110 return self.index.deltachain(rev, stoprev, self._generaldelta)
1111 1111 except AttributeError:
1112 1112 pass
1113 1113
1114 1114 chain = []
1115 1115
1116 1116 # Alias to prevent attribute lookup in tight loop.
1117 1117 index = self.index
1118 1118 generaldelta = self._generaldelta
1119 1119
1120 1120 iterrev = rev
1121 1121 e = index[iterrev]
1122 1122 while iterrev != e[3] and iterrev != stoprev:
1123 1123 chain.append(iterrev)
1124 1124 if generaldelta:
1125 1125 iterrev = e[3]
1126 1126 else:
1127 1127 iterrev -= 1
1128 1128 e = index[iterrev]
1129 1129
1130 1130 if iterrev == stoprev:
1131 1131 stopped = True
1132 1132 else:
1133 1133 chain.append(iterrev)
1134 1134 stopped = False
1135 1135
1136 1136 chain.reverse()
1137 1137 return chain, stopped
1138 1138
1139 1139 def ancestors(self, revs, stoprev=0, inclusive=False):
1140 1140 """Generate the ancestors of 'revs' in reverse revision order.
1141 1141 Does not generate revs lower than stoprev.
1142 1142
1143 1143 See the documentation for ancestor.lazyancestors for more details."""
1144 1144
1145 1145 # first, make sure start revisions aren't filtered
1146 1146 revs = list(revs)
1147 1147 checkrev = self.node
1148 1148 for r in revs:
1149 1149 checkrev(r)
1150 1150 # and we're sure ancestors aren't filtered as well
1151 1151
1152 1152 if rustancestor is not None and self.index.rust_ext_compat:
1153 1153 lazyancestors = rustancestor.LazyAncestors
1154 1154 arg = self.index
1155 1155 else:
1156 1156 lazyancestors = ancestor.lazyancestors
1157 1157 arg = self._uncheckedparentrevs
1158 1158 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
1159 1159
1160 1160 def descendants(self, revs):
1161 1161 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
1162 1162
1163 1163 def findcommonmissing(self, common=None, heads=None):
1164 1164 """Return a tuple of the ancestors of common and the ancestors of heads
1165 1165 that are not ancestors of common. In revset terminology, we return the
1166 1166 tuple:
1167 1167
1168 1168 ::common, (::heads) - (::common)
1169 1169
1170 1170 The list is sorted by revision number, meaning it is
1171 1171 topologically sorted.
1172 1172
1173 1173 'heads' and 'common' are both lists of node IDs. If heads is
1174 1174 not supplied, uses all of the revlog's heads. If common is not
1175 1175 supplied, uses nullid."""
1176 1176 if common is None:
1177 1177 common = [self.nullid]
1178 1178 if heads is None:
1179 1179 heads = self.heads()
1180 1180
1181 1181 common = [self.rev(n) for n in common]
1182 1182 heads = [self.rev(n) for n in heads]
1183 1183
1184 1184 # we want the ancestors, but inclusive
1185 1185 class lazyset:
1186 1186 def __init__(self, lazyvalues):
1187 1187 self.addedvalues = set()
1188 1188 self.lazyvalues = lazyvalues
1189 1189
1190 1190 def __contains__(self, value):
1191 1191 return value in self.addedvalues or value in self.lazyvalues
1192 1192
1193 1193 def __iter__(self):
1194 1194 added = self.addedvalues
1195 1195 for r in added:
1196 1196 yield r
1197 1197 for r in self.lazyvalues:
1198 1198 if not r in added:
1199 1199 yield r
1200 1200
1201 1201 def add(self, value):
1202 1202 self.addedvalues.add(value)
1203 1203
1204 1204 def update(self, values):
1205 1205 self.addedvalues.update(values)
1206 1206
1207 1207 has = lazyset(self.ancestors(common))
1208 1208 has.add(nullrev)
1209 1209 has.update(common)
1210 1210
1211 1211 # take all ancestors from heads that aren't in has
1212 1212 missing = set()
1213 1213 visit = collections.deque(r for r in heads if r not in has)
1214 1214 while visit:
1215 1215 r = visit.popleft()
1216 1216 if r in missing:
1217 1217 continue
1218 1218 else:
1219 1219 missing.add(r)
1220 1220 for p in self.parentrevs(r):
1221 1221 if p not in has:
1222 1222 visit.append(p)
1223 1223 missing = list(missing)
1224 1224 missing.sort()
1225 1225 return has, [self.node(miss) for miss in missing]
1226 1226
1227 1227 def incrementalmissingrevs(self, common=None):
1228 1228 """Return an object that can be used to incrementally compute the
1229 1229 revision numbers of the ancestors of arbitrary sets that are not
1230 1230 ancestors of common. This is an ancestor.incrementalmissingancestors
1231 1231 object.
1232 1232
1233 1233 'common' is a list of revision numbers. If common is not supplied, uses
1234 1234 nullrev.
1235 1235 """
1236 1236 if common is None:
1237 1237 common = [nullrev]
1238 1238
1239 1239 if rustancestor is not None and self.index.rust_ext_compat:
1240 1240 return rustancestor.MissingAncestors(self.index, common)
1241 1241 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1242 1242
1243 1243 def findmissingrevs(self, common=None, heads=None):
1244 1244 """Return the revision numbers of the ancestors of heads that
1245 1245 are not ancestors of common.
1246 1246
1247 1247 More specifically, return a list of revision numbers corresponding to
1248 1248 nodes N such that every N satisfies the following constraints:
1249 1249
1250 1250 1. N is an ancestor of some node in 'heads'
1251 1251 2. N is not an ancestor of any node in 'common'
1252 1252
1253 1253 The list is sorted by revision number, meaning it is
1254 1254 topologically sorted.
1255 1255
1256 1256 'heads' and 'common' are both lists of revision numbers. If heads is
1257 1257 not supplied, uses all of the revlog's heads. If common is not
1258 1258 supplied, uses nullid."""
1259 1259 if common is None:
1260 1260 common = [nullrev]
1261 1261 if heads is None:
1262 1262 heads = self.headrevs()
1263 1263
1264 1264 inc = self.incrementalmissingrevs(common=common)
1265 1265 return inc.missingancestors(heads)
1266 1266
1267 1267 def findmissing(self, common=None, heads=None):
1268 1268 """Return the ancestors of heads that are not ancestors of common.
1269 1269
1270 1270 More specifically, return a list of nodes N such that every N
1271 1271 satisfies the following constraints:
1272 1272
1273 1273 1. N is an ancestor of some node in 'heads'
1274 1274 2. N is not an ancestor of any node in 'common'
1275 1275
1276 1276 The list is sorted by revision number, meaning it is
1277 1277 topologically sorted.
1278 1278
1279 1279 'heads' and 'common' are both lists of node IDs. If heads is
1280 1280 not supplied, uses all of the revlog's heads. If common is not
1281 1281 supplied, uses nullid."""
1282 1282 if common is None:
1283 1283 common = [self.nullid]
1284 1284 if heads is None:
1285 1285 heads = self.heads()
1286 1286
1287 1287 common = [self.rev(n) for n in common]
1288 1288 heads = [self.rev(n) for n in heads]
1289 1289
1290 1290 inc = self.incrementalmissingrevs(common=common)
1291 1291 return [self.node(r) for r in inc.missingancestors(heads)]
1292 1292
1293 1293 def nodesbetween(self, roots=None, heads=None):
1294 1294 """Return a topological path from 'roots' to 'heads'.
1295 1295
1296 1296 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1297 1297 topologically sorted list of all nodes N that satisfy both of
1298 1298 these constraints:
1299 1299
1300 1300 1. N is a descendant of some node in 'roots'
1301 1301 2. N is an ancestor of some node in 'heads'
1302 1302
1303 1303 Every node is considered to be both a descendant and an ancestor
1304 1304 of itself, so every reachable node in 'roots' and 'heads' will be
1305 1305 included in 'nodes'.
1306 1306
1307 1307 'outroots' is the list of reachable nodes in 'roots', i.e., the
1308 1308 subset of 'roots' that is returned in 'nodes'. Likewise,
1309 1309 'outheads' is the subset of 'heads' that is also in 'nodes'.
1310 1310
1311 1311 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1312 1312 unspecified, uses nullid as the only root. If 'heads' is
1313 1313 unspecified, uses list of all of the revlog's heads."""
1314 1314 nonodes = ([], [], [])
1315 1315 if roots is not None:
1316 1316 roots = list(roots)
1317 1317 if not roots:
1318 1318 return nonodes
1319 1319 lowestrev = min([self.rev(n) for n in roots])
1320 1320 else:
1321 1321 roots = [self.nullid] # Everybody's a descendant of nullid
1322 1322 lowestrev = nullrev
1323 1323 if (lowestrev == nullrev) and (heads is None):
1324 1324 # We want _all_ the nodes!
1325 1325 return (
1326 1326 [self.node(r) for r in self],
1327 1327 [self.nullid],
1328 1328 list(self.heads()),
1329 1329 )
1330 1330 if heads is None:
1331 1331 # All nodes are ancestors, so the latest ancestor is the last
1332 1332 # node.
1333 1333 highestrev = len(self) - 1
1334 1334 # Set ancestors to None to signal that every node is an ancestor.
1335 1335 ancestors = None
1336 1336 # Set heads to an empty dictionary for later discovery of heads
1337 1337 heads = {}
1338 1338 else:
1339 1339 heads = list(heads)
1340 1340 if not heads:
1341 1341 return nonodes
1342 1342 ancestors = set()
1343 1343 # Turn heads into a dictionary so we can remove 'fake' heads.
1344 1344 # Also, later we will be using it to filter out the heads we can't
1345 1345 # find from roots.
1346 1346 heads = dict.fromkeys(heads, False)
1347 1347 # Start at the top and keep marking parents until we're done.
1348 1348 nodestotag = set(heads)
1349 1349 # Remember where the top was so we can use it as a limit later.
1350 1350 highestrev = max([self.rev(n) for n in nodestotag])
1351 1351 while nodestotag:
1352 1352 # grab a node to tag
1353 1353 n = nodestotag.pop()
1354 1354 # Never tag nullid
1355 1355 if n == self.nullid:
1356 1356 continue
1357 1357 # A node's revision number represents its place in a
1358 1358 # topologically sorted list of nodes.
1359 1359 r = self.rev(n)
1360 1360 if r >= lowestrev:
1361 1361 if n not in ancestors:
1362 1362 # If we are possibly a descendant of one of the roots
1363 1363 # and we haven't already been marked as an ancestor
1364 1364 ancestors.add(n) # Mark as ancestor
1365 1365 # Add non-nullid parents to list of nodes to tag.
1366 1366 nodestotag.update(
1367 1367 [p for p in self.parents(n) if p != self.nullid]
1368 1368 )
1369 1369 elif n in heads: # We've seen it before, is it a fake head?
1370 1370 # So it is, real heads should not be the ancestors of
1371 1371 # any other heads.
1372 1372 heads.pop(n)
1373 1373 if not ancestors:
1374 1374 return nonodes
1375 1375 # Now that we have our set of ancestors, we want to remove any
1376 1376 # roots that are not ancestors.
1377 1377
1378 1378 # If one of the roots was nullid, everything is included anyway.
1379 1379 if lowestrev > nullrev:
1380 1380 # But, since we weren't, let's recompute the lowest rev to not
1381 1381 # include roots that aren't ancestors.
1382 1382
1383 1383 # Filter out roots that aren't ancestors of heads
1384 1384 roots = [root for root in roots if root in ancestors]
1385 1385 # Recompute the lowest revision
1386 1386 if roots:
1387 1387 lowestrev = min([self.rev(root) for root in roots])
1388 1388 else:
1389 1389 # No more roots? Return empty list
1390 1390 return nonodes
1391 1391 else:
1392 1392 # We are descending from nullid, and don't need to care about
1393 1393 # any other roots.
1394 1394 lowestrev = nullrev
1395 1395 roots = [self.nullid]
1396 1396 # Transform our roots list into a set.
1397 1397 descendants = set(roots)
1398 1398 # Also, keep the original roots so we can filter out roots that aren't
1399 1399 # 'real' roots (i.e. are descended from other roots).
1400 1400 roots = descendants.copy()
1401 1401 # Our topologically sorted list of output nodes.
1402 1402 orderedout = []
1403 1403 # Don't start at nullid since we don't want nullid in our output list,
1404 1404 # and if nullid shows up in descendants, empty parents will look like
1405 1405 # they're descendants.
1406 1406 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1407 1407 n = self.node(r)
1408 1408 isdescendant = False
1409 1409 if lowestrev == nullrev: # Everybody is a descendant of nullid
1410 1410 isdescendant = True
1411 1411 elif n in descendants:
1412 1412 # n is already a descendant
1413 1413 isdescendant = True
1414 1414 # This check only needs to be done here because all the roots
1415 1415 # will start being marked is descendants before the loop.
1416 1416 if n in roots:
1417 1417 # If n was a root, check if it's a 'real' root.
1418 1418 p = tuple(self.parents(n))
1419 1419 # If any of its parents are descendants, it's not a root.
1420 1420 if (p[0] in descendants) or (p[1] in descendants):
1421 1421 roots.remove(n)
1422 1422 else:
1423 1423 p = tuple(self.parents(n))
1424 1424 # A node is a descendant if either of its parents are
1425 1425 # descendants. (We seeded the dependents list with the roots
1426 1426 # up there, remember?)
1427 1427 if (p[0] in descendants) or (p[1] in descendants):
1428 1428 descendants.add(n)
1429 1429 isdescendant = True
1430 1430 if isdescendant and ((ancestors is None) or (n in ancestors)):
1431 1431 # Only include nodes that are both descendants and ancestors.
1432 1432 orderedout.append(n)
1433 1433 if (ancestors is not None) and (n in heads):
1434 1434 # We're trying to figure out which heads are reachable
1435 1435 # from roots.
1436 1436 # Mark this head as having been reached
1437 1437 heads[n] = True
1438 1438 elif ancestors is None:
1439 1439 # Otherwise, we're trying to discover the heads.
1440 1440 # Assume this is a head because if it isn't, the next step
1441 1441 # will eventually remove it.
1442 1442 heads[n] = True
1443 1443 # But, obviously its parents aren't.
1444 1444 for p in self.parents(n):
1445 1445 heads.pop(p, None)
1446 1446 heads = [head for head, flag in heads.items() if flag]
1447 1447 roots = list(roots)
1448 1448 assert orderedout
1449 1449 assert roots
1450 1450 assert heads
1451 1451 return (orderedout, roots, heads)
1452 1452
1453 1453 def headrevs(self, revs=None):
1454 1454 if revs is None:
1455 1455 try:
1456 1456 return self.index.headrevs()
1457 1457 except AttributeError:
1458 1458 return self._headrevs()
1459 1459 if rustdagop is not None and self.index.rust_ext_compat:
1460 1460 return rustdagop.headrevs(self.index, revs)
1461 1461 return dagop.headrevs(revs, self._uncheckedparentrevs)
1462 1462
1463 1463 def computephases(self, roots):
1464 1464 return self.index.computephasesmapsets(roots)
1465 1465
1466 1466 def _headrevs(self):
1467 1467 count = len(self)
1468 1468 if not count:
1469 1469 return [nullrev]
1470 1470 # we won't iter over filtered rev so nobody is a head at start
1471 1471 ishead = [0] * (count + 1)
1472 1472 index = self.index
1473 1473 for r in self:
1474 1474 ishead[r] = 1 # I may be an head
1475 1475 e = index[r]
1476 1476 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1477 1477 return [r for r, val in enumerate(ishead) if val]
1478 1478
1479 1479 def heads(self, start=None, stop=None):
1480 1480 """return the list of all nodes that have no children
1481 1481
1482 1482 if start is specified, only heads that are descendants of
1483 1483 start will be returned
1484 1484 if stop is specified, it will consider all the revs from stop
1485 1485 as if they had no children
1486 1486 """
1487 1487 if start is None and stop is None:
1488 1488 if not len(self):
1489 1489 return [self.nullid]
1490 1490 return [self.node(r) for r in self.headrevs()]
1491 1491
1492 1492 if start is None:
1493 1493 start = nullrev
1494 1494 else:
1495 1495 start = self.rev(start)
1496 1496
1497 1497 stoprevs = {self.rev(n) for n in stop or []}
1498 1498
1499 1499 revs = dagop.headrevssubset(
1500 1500 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1501 1501 )
1502 1502
1503 1503 return [self.node(rev) for rev in revs]
1504 1504
1505 1505 def children(self, node):
1506 1506 """find the children of a given node"""
1507 1507 c = []
1508 1508 p = self.rev(node)
1509 1509 for r in self.revs(start=p + 1):
1510 1510 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1511 1511 if prevs:
1512 1512 for pr in prevs:
1513 1513 if pr == p:
1514 1514 c.append(self.node(r))
1515 1515 elif p == nullrev:
1516 1516 c.append(self.node(r))
1517 1517 return c
1518 1518
1519 1519 def commonancestorsheads(self, a, b):
1520 1520 """calculate all the heads of the common ancestors of nodes a and b"""
1521 1521 a, b = self.rev(a), self.rev(b)
1522 1522 ancs = self._commonancestorsheads(a, b)
1523 1523 return pycompat.maplist(self.node, ancs)
1524 1524
1525 1525 def _commonancestorsheads(self, *revs):
1526 1526 """calculate all the heads of the common ancestors of revs"""
1527 1527 try:
1528 1528 ancs = self.index.commonancestorsheads(*revs)
1529 1529 except (AttributeError, OverflowError): # C implementation failed
1530 1530 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1531 1531 return ancs
1532 1532
1533 1533 def isancestor(self, a, b):
1534 1534 """return True if node a is an ancestor of node b
1535 1535
1536 1536 A revision is considered an ancestor of itself."""
1537 1537 a, b = self.rev(a), self.rev(b)
1538 1538 return self.isancestorrev(a, b)
1539 1539
1540 1540 def isancestorrev(self, a, b):
1541 1541 """return True if revision a is an ancestor of revision b
1542 1542
1543 1543 A revision is considered an ancestor of itself.
1544 1544
1545 1545 The implementation of this is trivial but the use of
1546 1546 reachableroots is not."""
1547 1547 if a == nullrev:
1548 1548 return True
1549 1549 elif a == b:
1550 1550 return True
1551 1551 elif a > b:
1552 1552 return False
1553 1553 return bool(self.reachableroots(a, [b], [a], includepath=False))
1554 1554
1555 1555 def reachableroots(self, minroot, heads, roots, includepath=False):
1556 1556 """return (heads(::(<roots> and <roots>::<heads>)))
1557 1557
1558 1558 If includepath is True, return (<roots>::<heads>)."""
1559 1559 try:
1560 1560 return self.index.reachableroots2(
1561 1561 minroot, heads, roots, includepath
1562 1562 )
1563 1563 except AttributeError:
1564 1564 return dagop._reachablerootspure(
1565 1565 self.parentrevs, minroot, roots, heads, includepath
1566 1566 )
1567 1567
1568 1568 def ancestor(self, a, b):
1569 1569 """calculate the "best" common ancestor of nodes a and b"""
1570 1570
1571 1571 a, b = self.rev(a), self.rev(b)
1572 1572 try:
1573 1573 ancs = self.index.ancestors(a, b)
1574 1574 except (AttributeError, OverflowError):
1575 1575 ancs = ancestor.ancestors(self.parentrevs, a, b)
1576 1576 if ancs:
1577 1577 # choose a consistent winner when there's a tie
1578 1578 return min(map(self.node, ancs))
1579 1579 return self.nullid
1580 1580
1581 1581 def _match(self, id):
1582 1582 if isinstance(id, int):
1583 1583 # rev
1584 1584 return self.node(id)
1585 1585 if len(id) == self.nodeconstants.nodelen:
1586 1586 # possibly a binary node
1587 1587 # odds of a binary node being all hex in ASCII are 1 in 10**25
1588 1588 try:
1589 1589 node = id
1590 1590 self.rev(node) # quick search the index
1591 1591 return node
1592 1592 except error.LookupError:
1593 1593 pass # may be partial hex id
1594 1594 try:
1595 1595 # str(rev)
1596 1596 rev = int(id)
1597 1597 if b"%d" % rev != id:
1598 1598 raise ValueError
1599 1599 if rev < 0:
1600 1600 rev = len(self) + rev
1601 1601 if rev < 0 or rev >= len(self):
1602 1602 raise ValueError
1603 1603 return self.node(rev)
1604 1604 except (ValueError, OverflowError):
1605 1605 pass
1606 1606 if len(id) == 2 * self.nodeconstants.nodelen:
1607 1607 try:
1608 1608 # a full hex nodeid?
1609 1609 node = bin(id)
1610 1610 self.rev(node)
1611 1611 return node
1612 1612 except (binascii.Error, error.LookupError):
1613 1613 pass
1614 1614
1615 1615 def _partialmatch(self, id):
1616 1616 # we don't care wdirfilenodeids as they should be always full hash
1617 1617 maybewdir = self.nodeconstants.wdirhex.startswith(id)
1618 1618 ambiguous = False
1619 1619 try:
1620 1620 partial = self.index.partialmatch(id)
1621 1621 if partial and self.hasnode(partial):
1622 1622 if maybewdir:
1623 1623 # single 'ff...' match in radix tree, ambiguous with wdir
1624 1624 ambiguous = True
1625 1625 else:
1626 1626 return partial
1627 1627 elif maybewdir:
1628 1628 # no 'ff...' match in radix tree, wdir identified
1629 1629 raise error.WdirUnsupported
1630 1630 else:
1631 1631 return None
1632 1632 except error.RevlogError:
1633 1633 # parsers.c radix tree lookup gave multiple matches
1634 1634 # fast path: for unfiltered changelog, radix tree is accurate
1635 1635 if not getattr(self, 'filteredrevs', None):
1636 1636 ambiguous = True
1637 1637 # fall through to slow path that filters hidden revisions
1638 1638 except (AttributeError, ValueError):
1639 1639 # we are pure python, or key is not hex
1640 1640 pass
1641 1641 if ambiguous:
1642 1642 raise error.AmbiguousPrefixLookupError(
1643 1643 id, self.display_id, _(b'ambiguous identifier')
1644 1644 )
1645 1645
1646 1646 if id in self._pcache:
1647 1647 return self._pcache[id]
1648 1648
1649 1649 if len(id) <= 40:
1650 1650 # hex(node)[:...]
1651 1651 l = len(id) // 2 * 2 # grab an even number of digits
1652 1652 try:
1653 1653 # we're dropping the last digit, so let's check that it's hex,
1654 1654 # to avoid the expensive computation below if it's not
1655 1655 if len(id) % 2 > 0:
1656 1656 if not (id[-1] in hexdigits):
1657 1657 return None
1658 1658 prefix = bin(id[:l])
1659 1659 except binascii.Error:
1660 1660 pass
1661 1661 else:
1662 1662 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1663 1663 nl = [
1664 1664 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1665 1665 ]
1666 1666 if self.nodeconstants.nullhex.startswith(id):
1667 1667 nl.append(self.nullid)
1668 1668 if len(nl) > 0:
1669 1669 if len(nl) == 1 and not maybewdir:
1670 1670 self._pcache[id] = nl[0]
1671 1671 return nl[0]
1672 1672 raise error.AmbiguousPrefixLookupError(
1673 1673 id, self.display_id, _(b'ambiguous identifier')
1674 1674 )
1675 1675 if maybewdir:
1676 1676 raise error.WdirUnsupported
1677 1677 return None
1678 1678
1679 1679 def lookup(self, id):
1680 1680 """locate a node based on:
1681 1681 - revision number or str(revision number)
1682 1682 - nodeid or subset of hex nodeid
1683 1683 """
1684 1684 n = self._match(id)
1685 1685 if n is not None:
1686 1686 return n
1687 1687 n = self._partialmatch(id)
1688 1688 if n:
1689 1689 return n
1690 1690
1691 1691 raise error.LookupError(id, self.display_id, _(b'no match found'))
1692 1692
1693 1693 def shortest(self, node, minlength=1):
1694 1694 """Find the shortest unambiguous prefix that matches node."""
1695 1695
1696 1696 def isvalid(prefix):
1697 1697 try:
1698 1698 matchednode = self._partialmatch(prefix)
1699 1699 except error.AmbiguousPrefixLookupError:
1700 1700 return False
1701 1701 except error.WdirUnsupported:
1702 1702 # single 'ff...' match
1703 1703 return True
1704 1704 if matchednode is None:
1705 1705 raise error.LookupError(node, self.display_id, _(b'no node'))
1706 1706 return True
1707 1707
1708 1708 def maybewdir(prefix):
1709 1709 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1710 1710
1711 1711 hexnode = hex(node)
1712 1712
1713 1713 def disambiguate(hexnode, minlength):
1714 1714 """Disambiguate against wdirid."""
1715 1715 for length in range(minlength, len(hexnode) + 1):
1716 1716 prefix = hexnode[:length]
1717 1717 if not maybewdir(prefix):
1718 1718 return prefix
1719 1719
1720 1720 if not getattr(self, 'filteredrevs', None):
1721 1721 try:
1722 1722 length = max(self.index.shortest(node), minlength)
1723 1723 return disambiguate(hexnode, length)
1724 1724 except error.RevlogError:
1725 1725 if node != self.nodeconstants.wdirid:
1726 1726 raise error.LookupError(
1727 1727 node, self.display_id, _(b'no node')
1728 1728 )
1729 1729 except AttributeError:
1730 1730 # Fall through to pure code
1731 1731 pass
1732 1732
1733 1733 if node == self.nodeconstants.wdirid:
1734 1734 for length in range(minlength, len(hexnode) + 1):
1735 1735 prefix = hexnode[:length]
1736 1736 if isvalid(prefix):
1737 1737 return prefix
1738 1738
1739 1739 for length in range(minlength, len(hexnode) + 1):
1740 1740 prefix = hexnode[:length]
1741 1741 if isvalid(prefix):
1742 1742 return disambiguate(hexnode, length)
1743 1743
1744 1744 def cmp(self, node, text):
1745 1745 """compare text with a given file revision
1746 1746
1747 1747 returns True if text is different than what is stored.
1748 1748 """
1749 1749 p1, p2 = self.parents(node)
1750 1750 return storageutil.hashrevisionsha1(text, p1, p2) != node
1751 1751
1752 1752 def _getsegmentforrevs(self, startrev, endrev, df=None):
1753 1753 """Obtain a segment of raw data corresponding to a range of revisions.
1754 1754
1755 1755 Accepts the start and end revisions and an optional already-open
1756 1756 file handle to be used for reading. If the file handle is read, its
1757 1757 seek position will not be preserved.
1758 1758
1759 1759 Requests for data may be satisfied by a cache.
1760 1760
1761 1761 Returns a 2-tuple of (offset, data) for the requested range of
1762 1762 revisions. Offset is the integer offset from the beginning of the
1763 1763 revlog and data is a str or buffer of the raw byte data.
1764 1764
1765 1765 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1766 1766 to determine where each revision's data begins and ends.
1767 1767 """
1768 1768 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1769 1769 # (functions are expensive).
1770 1770 index = self.index
1771 1771 istart = index[startrev]
1772 1772 start = int(istart[0] >> 16)
1773 1773 if startrev == endrev:
1774 1774 end = start + istart[1]
1775 1775 else:
1776 1776 iend = index[endrev]
1777 1777 end = int(iend[0] >> 16) + iend[1]
1778 1778
1779 1779 if self._inline:
1780 1780 start += (startrev + 1) * self.index.entry_size
1781 1781 end += (endrev + 1) * self.index.entry_size
1782 1782 length = end - start
1783 1783
1784 1784 return start, self._segmentfile.read_chunk(start, length, df)
1785 1785
1786 1786 def _chunk(self, rev, df=None):
1787 1787 """Obtain a single decompressed chunk for a revision.
1788 1788
1789 1789 Accepts an integer revision and an optional already-open file handle
1790 1790 to be used for reading. If used, the seek position of the file will not
1791 1791 be preserved.
1792 1792
1793 1793 Returns a str holding uncompressed data for the requested revision.
1794 1794 """
1795 1795 compression_mode = self.index[rev][10]
1796 1796 data = self._getsegmentforrevs(rev, rev, df=df)[1]
1797 1797 if compression_mode == COMP_MODE_PLAIN:
1798 1798 return data
1799 1799 elif compression_mode == COMP_MODE_DEFAULT:
1800 1800 return self._decompressor(data)
1801 1801 elif compression_mode == COMP_MODE_INLINE:
1802 1802 return self.decompress(data)
1803 1803 else:
1804 1804 msg = b'unknown compression mode %d'
1805 1805 msg %= compression_mode
1806 1806 raise error.RevlogError(msg)
1807 1807
1808 1808 def _chunks(self, revs, df=None, targetsize=None):
1809 1809 """Obtain decompressed chunks for the specified revisions.
1810 1810
1811 1811 Accepts an iterable of numeric revisions that are assumed to be in
1812 1812 ascending order. Also accepts an optional already-open file handle
1813 1813 to be used for reading. If used, the seek position of the file will
1814 1814 not be preserved.
1815 1815
1816 1816 This function is similar to calling ``self._chunk()`` multiple times,
1817 1817 but is faster.
1818 1818
1819 1819 Returns a list with decompressed data for each requested revision.
1820 1820 """
1821 1821 if not revs:
1822 1822 return []
1823 1823 start = self.start
1824 1824 length = self.length
1825 1825 inline = self._inline
1826 1826 iosize = self.index.entry_size
1827 1827 buffer = util.buffer
1828 1828
1829 1829 l = []
1830 1830 ladd = l.append
1831 1831
1832 1832 if not self._withsparseread:
1833 1833 slicedchunks = (revs,)
1834 1834 else:
1835 1835 slicedchunks = deltautil.slicechunk(
1836 1836 self, revs, targetsize=targetsize
1837 1837 )
1838 1838
1839 1839 for revschunk in slicedchunks:
1840 1840 firstrev = revschunk[0]
1841 1841 # Skip trailing revisions with empty diff
1842 1842 for lastrev in revschunk[::-1]:
1843 1843 if length(lastrev) != 0:
1844 1844 break
1845 1845
1846 1846 try:
1847 1847 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1848 1848 except OverflowError:
1849 1849 # issue4215 - we can't cache a run of chunks greater than
1850 1850 # 2G on Windows
1851 1851 return [self._chunk(rev, df=df) for rev in revschunk]
1852 1852
1853 1853 decomp = self.decompress
1854 1854 # self._decompressor might be None, but will not be used in that case
1855 1855 def_decomp = self._decompressor
1856 1856 for rev in revschunk:
1857 1857 chunkstart = start(rev)
1858 1858 if inline:
1859 1859 chunkstart += (rev + 1) * iosize
1860 1860 chunklength = length(rev)
1861 1861 comp_mode = self.index[rev][10]
1862 1862 c = buffer(data, chunkstart - offset, chunklength)
1863 1863 if comp_mode == COMP_MODE_PLAIN:
1864 1864 ladd(c)
1865 1865 elif comp_mode == COMP_MODE_INLINE:
1866 1866 ladd(decomp(c))
1867 1867 elif comp_mode == COMP_MODE_DEFAULT:
1868 1868 ladd(def_decomp(c))
1869 1869 else:
1870 1870 msg = b'unknown compression mode %d'
1871 1871 msg %= comp_mode
1872 1872 raise error.RevlogError(msg)
1873 1873
1874 1874 return l
1875 1875
1876 1876 def deltaparent(self, rev):
1877 1877 """return deltaparent of the given revision"""
1878 1878 base = self.index[rev][3]
1879 1879 if base == rev:
1880 1880 return nullrev
1881 1881 elif self._generaldelta:
1882 1882 return base
1883 1883 else:
1884 1884 return rev - 1
1885 1885
1886 1886 def issnapshot(self, rev):
1887 1887 """tells whether rev is a snapshot"""
1888 1888 if not self._sparserevlog:
1889 1889 return self.deltaparent(rev) == nullrev
1890 1890 elif util.safehasattr(self.index, 'issnapshot'):
1891 1891 # directly assign the method to cache the testing and access
1892 1892 self.issnapshot = self.index.issnapshot
1893 1893 return self.issnapshot(rev)
1894 1894 if rev == nullrev:
1895 1895 return True
1896 1896 entry = self.index[rev]
1897 1897 base = entry[3]
1898 1898 if base == rev:
1899 1899 return True
1900 1900 if base == nullrev:
1901 1901 return True
1902 1902 p1 = entry[5]
1903 1903 while self.length(p1) == 0:
1904 1904 b = self.deltaparent(p1)
1905 1905 if b == p1:
1906 1906 break
1907 1907 p1 = b
1908 1908 p2 = entry[6]
1909 1909 while self.length(p2) == 0:
1910 1910 b = self.deltaparent(p2)
1911 1911 if b == p2:
1912 1912 break
1913 1913 p2 = b
1914 1914 if base == p1 or base == p2:
1915 1915 return False
1916 1916 return self.issnapshot(base)
1917 1917
1918 1918 def snapshotdepth(self, rev):
1919 1919 """number of snapshot in the chain before this one"""
1920 1920 if not self.issnapshot(rev):
1921 1921 raise error.ProgrammingError(b'revision %d not a snapshot')
1922 1922 return len(self._deltachain(rev)[0]) - 1
1923 1923
1924 1924 def revdiff(self, rev1, rev2):
1925 1925 """return or calculate a delta between two revisions
1926 1926
1927 1927 The delta calculated is in binary form and is intended to be written to
1928 1928 revlog data directly. So this function needs raw revision data.
1929 1929 """
1930 1930 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1931 1931 return bytes(self._chunk(rev2))
1932 1932
1933 1933 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1934 1934
1935 1935 def revision(self, nodeorrev, _df=None):
1936 1936 """return an uncompressed revision of a given node or revision
1937 1937 number.
1938 1938
1939 1939 _df - an existing file handle to read from. (internal-only)
1940 1940 """
1941 1941 return self._revisiondata(nodeorrev, _df)
1942 1942
1943 1943 def sidedata(self, nodeorrev, _df=None):
1944 1944 """a map of extra data related to the changeset but not part of the hash
1945 1945
1946 1946 This function currently return a dictionary. However, more advanced
1947 1947 mapping object will likely be used in the future for a more
1948 1948 efficient/lazy code.
1949 1949 """
1950 1950 # deal with <nodeorrev> argument type
1951 1951 if isinstance(nodeorrev, int):
1952 1952 rev = nodeorrev
1953 1953 else:
1954 1954 rev = self.rev(nodeorrev)
1955 1955 return self._sidedata(rev)
1956 1956
1957 1957 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1958 1958 # deal with <nodeorrev> argument type
1959 1959 if isinstance(nodeorrev, int):
1960 1960 rev = nodeorrev
1961 1961 node = self.node(rev)
1962 1962 else:
1963 1963 node = nodeorrev
1964 1964 rev = None
1965 1965
1966 1966 # fast path the special `nullid` rev
1967 1967 if node == self.nullid:
1968 1968 return b""
1969 1969
1970 1970 # ``rawtext`` is the text as stored inside the revlog. Might be the
1971 1971 # revision or might need to be processed to retrieve the revision.
1972 1972 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1973 1973
1974 1974 if raw and validated:
1975 1975 # if we don't want to process the raw text and that raw
1976 1976 # text is cached, we can exit early.
1977 1977 return rawtext
1978 1978 if rev is None:
1979 1979 rev = self.rev(node)
1980 1980 # the revlog's flag for this revision
1981 1981 # (usually alter its state or content)
1982 1982 flags = self.flags(rev)
1983 1983
1984 1984 if validated and flags == REVIDX_DEFAULT_FLAGS:
1985 1985 # no extra flags set, no flag processor runs, text = rawtext
1986 1986 return rawtext
1987 1987
1988 1988 if raw:
1989 1989 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1990 1990 text = rawtext
1991 1991 else:
1992 1992 r = flagutil.processflagsread(self, rawtext, flags)
1993 1993 text, validatehash = r
1994 1994 if validatehash:
1995 1995 self.checkhash(text, node, rev=rev)
1996 1996 if not validated:
1997 1997 self._revisioncache = (node, rev, rawtext)
1998 1998
1999 1999 return text
2000 2000
2001 2001 def _rawtext(self, node, rev, _df=None):
2002 2002 """return the possibly unvalidated rawtext for a revision
2003 2003
2004 2004 returns (rev, rawtext, validated)
2005 2005 """
2006 2006
2007 2007 # revision in the cache (could be useful to apply delta)
2008 2008 cachedrev = None
2009 2009 # An intermediate text to apply deltas to
2010 2010 basetext = None
2011 2011
2012 2012 # Check if we have the entry in cache
2013 2013 # The cache entry looks like (node, rev, rawtext)
2014 2014 if self._revisioncache:
2015 2015 if self._revisioncache[0] == node:
2016 2016 return (rev, self._revisioncache[2], True)
2017 2017 cachedrev = self._revisioncache[1]
2018 2018
2019 2019 if rev is None:
2020 2020 rev = self.rev(node)
2021 2021
2022 2022 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
2023 2023 if stopped:
2024 2024 basetext = self._revisioncache[2]
2025 2025
2026 2026 # drop cache to save memory, the caller is expected to
2027 2027 # update self._revisioncache after validating the text
2028 2028 self._revisioncache = None
2029 2029
2030 2030 targetsize = None
2031 2031 rawsize = self.index[rev][2]
2032 2032 if 0 <= rawsize:
2033 2033 targetsize = 4 * rawsize
2034 2034
2035 2035 bins = self._chunks(chain, df=_df, targetsize=targetsize)
2036 2036 if basetext is None:
2037 2037 basetext = bytes(bins[0])
2038 2038 bins = bins[1:]
2039 2039
2040 2040 rawtext = mdiff.patches(basetext, bins)
2041 2041 del basetext # let us have a chance to free memory early
2042 2042 return (rev, rawtext, False)
2043 2043
2044 2044 def _sidedata(self, rev):
2045 2045 """Return the sidedata for a given revision number."""
2046 2046 index_entry = self.index[rev]
2047 2047 sidedata_offset = index_entry[8]
2048 2048 sidedata_size = index_entry[9]
2049 2049
2050 2050 if self._inline:
2051 2051 sidedata_offset += self.index.entry_size * (1 + rev)
2052 2052 if sidedata_size == 0:
2053 2053 return {}
2054 2054
2055 2055 if self._docket.sidedata_end < sidedata_offset + sidedata_size:
2056 2056 filename = self._sidedatafile
2057 2057 end = self._docket.sidedata_end
2058 2058 offset = sidedata_offset
2059 2059 length = sidedata_size
2060 2060 m = FILE_TOO_SHORT_MSG % (filename, length, offset, end)
2061 2061 raise error.RevlogError(m)
2062 2062
2063 2063 comp_segment = self._segmentfile_sidedata.read_chunk(
2064 2064 sidedata_offset, sidedata_size
2065 2065 )
2066 2066
2067 2067 comp = self.index[rev][11]
2068 2068 if comp == COMP_MODE_PLAIN:
2069 2069 segment = comp_segment
2070 2070 elif comp == COMP_MODE_DEFAULT:
2071 2071 segment = self._decompressor(comp_segment)
2072 2072 elif comp == COMP_MODE_INLINE:
2073 2073 segment = self.decompress(comp_segment)
2074 2074 else:
2075 2075 msg = b'unknown compression mode %d'
2076 2076 msg %= comp
2077 2077 raise error.RevlogError(msg)
2078 2078
2079 2079 sidedata = sidedatautil.deserialize_sidedata(segment)
2080 2080 return sidedata
2081 2081
2082 2082 def rawdata(self, nodeorrev, _df=None):
2083 2083 """return an uncompressed raw data of a given node or revision number.
2084 2084
2085 2085 _df - an existing file handle to read from. (internal-only)
2086 2086 """
2087 2087 return self._revisiondata(nodeorrev, _df, raw=True)
2088 2088
2089 2089 def hash(self, text, p1, p2):
2090 2090 """Compute a node hash.
2091 2091
2092 2092 Available as a function so that subclasses can replace the hash
2093 2093 as needed.
2094 2094 """
2095 2095 return storageutil.hashrevisionsha1(text, p1, p2)
2096 2096
2097 2097 def checkhash(self, text, node, p1=None, p2=None, rev=None):
2098 2098 """Check node hash integrity.
2099 2099
2100 2100 Available as a function so that subclasses can extend hash mismatch
2101 2101 behaviors as needed.
2102 2102 """
2103 2103 try:
2104 2104 if p1 is None and p2 is None:
2105 2105 p1, p2 = self.parents(node)
2106 2106 if node != self.hash(text, p1, p2):
2107 2107 # Clear the revision cache on hash failure. The revision cache
2108 2108 # only stores the raw revision and clearing the cache does have
2109 2109 # the side-effect that we won't have a cache hit when the raw
2110 2110 # revision data is accessed. But this case should be rare and
2111 2111 # it is extra work to teach the cache about the hash
2112 2112 # verification state.
2113 2113 if self._revisioncache and self._revisioncache[0] == node:
2114 2114 self._revisioncache = None
2115 2115
2116 2116 revornode = rev
2117 2117 if revornode is None:
2118 2118 revornode = templatefilters.short(hex(node))
2119 2119 raise error.RevlogError(
2120 2120 _(b"integrity check failed on %s:%s")
2121 2121 % (self.display_id, pycompat.bytestr(revornode))
2122 2122 )
2123 2123 except error.RevlogError:
2124 2124 if self._censorable and storageutil.iscensoredtext(text):
2125 2125 raise error.CensoredNodeError(self.display_id, node, text)
2126 2126 raise
2127 2127
2128 2128 @property
2129 2129 def _split_index_file(self):
2130 2130 """the path where to expect the index of an ongoing splitting operation
2131 2131
2132 2132 The file will only exist if a splitting operation is in progress, but
2133 2133 it is always expected at the same location."""
2134 parts = os.path.split(self.radix)
2134 parts = self.radix.split(b'/')
2135 2135 if len(parts) > 1:
2136 2136 # adds a '-s' prefix to the ``data/` or `meta/` base
2137 2137 head = parts[0] + b'-s'
2138 return os.path.join(head, *parts[1:])
2138 mids = parts[1:-1]
2139 tail = parts[-1] + b'.i'
2140 pieces = [head] + mids + [tail]
2141 return b'/'.join(pieces)
2139 2142 else:
2140 2143 # the revlog is stored at the root of the store (changelog or
2141 2144 # manifest), no risk of collision.
2142 2145 return self.radix + b'.i.s'
2143 2146
2144 2147 def _enforceinlinesize(self, tr, side_write=True):
2145 2148 """Check if the revlog is too big for inline and convert if so.
2146 2149
2147 2150 This should be called after revisions are added to the revlog. If the
2148 2151 revlog has grown too large to be an inline revlog, it will convert it
2149 2152 to use multiple index and data files.
2150 2153 """
2151 2154 tiprev = len(self) - 1
2152 2155 total_size = self.start(tiprev) + self.length(tiprev)
2153 2156 if not self._inline or total_size < _maxinline:
2154 2157 return
2155 2158
2156 2159 troffset = tr.findoffset(self._indexfile)
2157 2160 if troffset is None:
2158 2161 raise error.RevlogError(
2159 2162 _(b"%s not found in the transaction") % self._indexfile
2160 2163 )
2161 2164 if troffset:
2162 2165 tr.addbackup(self._indexfile, for_offset=True)
2163 2166 tr.add(self._datafile, 0)
2164 2167
2165 2168 existing_handles = False
2166 2169 if self._writinghandles is not None:
2167 2170 existing_handles = True
2168 2171 fp = self._writinghandles[0]
2169 2172 fp.flush()
2170 2173 fp.close()
2171 2174 # We can't use the cached file handle after close(). So prevent
2172 2175 # its usage.
2173 2176 self._writinghandles = None
2174 2177 self._segmentfile.writing_handle = None
2175 2178 # No need to deal with sidedata writing handle as it is only
2176 2179 # relevant with revlog-v2 which is never inline, not reaching
2177 2180 # this code
2178 2181 if side_write:
2179 2182 old_index_file_path = self._indexfile
2180 2183 new_index_file_path = self._split_index_file
2181 2184 opener = self.opener
2182 2185 weak_self = weakref.ref(self)
2183 2186
2184 2187 # the "split" index replace the real index when the transaction is finalized
2185 2188 def finalize_callback(tr):
2186 2189 opener.rename(
2187 2190 new_index_file_path,
2188 2191 old_index_file_path,
2189 2192 checkambig=True,
2190 2193 )
2191 2194 maybe_self = weak_self()
2192 2195 if maybe_self is not None:
2193 2196 maybe_self._indexfile = old_index_file_path
2194 2197
2195 2198 def abort_callback(tr):
2196 2199 maybe_self = weak_self()
2197 2200 if maybe_self is not None:
2198 2201 maybe_self._indexfile = old_index_file_path
2199 2202
2200 2203 tr.registertmp(new_index_file_path)
2201 2204 if self.target[1] is not None:
2202 2205 callback_id = b'000-revlog-split-%d-%s' % self.target
2203 2206 else:
2204 2207 callback_id = b'000-revlog-split-%d' % self.target[0]
2205 2208 tr.addfinalize(callback_id, finalize_callback)
2206 2209 tr.addabort(callback_id, abort_callback)
2207 2210
2208 2211 new_dfh = self._datafp(b'w+')
2209 2212 new_dfh.truncate(0) # drop any potentially existing data
2210 2213 try:
2211 2214 with self._indexfp() as read_ifh:
2212 2215 for r in self:
2213 2216 new_dfh.write(self._getsegmentforrevs(r, r, df=read_ifh)[1])
2214 2217 new_dfh.flush()
2215 2218
2216 2219 if side_write:
2217 2220 self._indexfile = new_index_file_path
2218 2221 with self.__index_new_fp() as fp:
2219 2222 self._format_flags &= ~FLAG_INLINE_DATA
2220 2223 self._inline = False
2221 2224 for i in self:
2222 2225 e = self.index.entry_binary(i)
2223 2226 if i == 0 and self._docket is None:
2224 2227 header = self._format_flags | self._format_version
2225 2228 header = self.index.pack_header(header)
2226 2229 e = header + e
2227 2230 fp.write(e)
2228 2231 if self._docket is not None:
2229 2232 self._docket.index_end = fp.tell()
2230 2233
2231 2234 # If we don't use side-write, the temp file replace the real
2232 2235 # index when we exit the context manager
2233 2236
2234 2237 nodemaputil.setup_persistent_nodemap(tr, self)
2235 2238 self._segmentfile = randomaccessfile.randomaccessfile(
2236 2239 self.opener,
2237 2240 self._datafile,
2238 2241 self._chunkcachesize,
2239 2242 )
2240 2243
2241 2244 if existing_handles:
2242 2245 # switched from inline to conventional reopen the index
2243 2246 ifh = self.__index_write_fp()
2244 2247 self._writinghandles = (ifh, new_dfh, None)
2245 2248 self._segmentfile.writing_handle = new_dfh
2246 2249 new_dfh = None
2247 2250 # No need to deal with sidedata writing handle as it is only
2248 2251 # relevant with revlog-v2 which is never inline, not reaching
2249 2252 # this code
2250 2253 finally:
2251 2254 if new_dfh is not None:
2252 2255 new_dfh.close()
2253 2256
2254 2257 def _nodeduplicatecallback(self, transaction, node):
2255 2258 """called when trying to add a node already stored."""
2256 2259
2257 2260 @contextlib.contextmanager
2258 2261 def reading(self):
2259 2262 """Context manager that keeps data and sidedata files open for reading"""
2260 2263 with self._segmentfile.reading():
2261 2264 with self._segmentfile_sidedata.reading():
2262 2265 yield
2263 2266
2264 2267 @contextlib.contextmanager
2265 2268 def _writing(self, transaction):
2266 2269 if self._trypending:
2267 2270 msg = b'try to write in a `trypending` revlog: %s'
2268 2271 msg %= self.display_id
2269 2272 raise error.ProgrammingError(msg)
2270 2273 if self._writinghandles is not None:
2271 2274 yield
2272 2275 else:
2273 2276 ifh = dfh = sdfh = None
2274 2277 try:
2275 2278 r = len(self)
2276 2279 # opening the data file.
2277 2280 dsize = 0
2278 2281 if r:
2279 2282 dsize = self.end(r - 1)
2280 2283 dfh = None
2281 2284 if not self._inline:
2282 2285 try:
2283 2286 dfh = self._datafp(b"r+")
2284 2287 if self._docket is None:
2285 2288 dfh.seek(0, os.SEEK_END)
2286 2289 else:
2287 2290 dfh.seek(self._docket.data_end, os.SEEK_SET)
2288 2291 except FileNotFoundError:
2289 2292 dfh = self._datafp(b"w+")
2290 2293 transaction.add(self._datafile, dsize)
2291 2294 if self._sidedatafile is not None:
2292 2295 # revlog-v2 does not inline, help Pytype
2293 2296 assert dfh is not None
2294 2297 try:
2295 2298 sdfh = self.opener(self._sidedatafile, mode=b"r+")
2296 2299 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2297 2300 except FileNotFoundError:
2298 2301 sdfh = self.opener(self._sidedatafile, mode=b"w+")
2299 2302 transaction.add(
2300 2303 self._sidedatafile, self._docket.sidedata_end
2301 2304 )
2302 2305
2303 2306 # opening the index file.
2304 2307 isize = r * self.index.entry_size
2305 2308 ifh = self.__index_write_fp()
2306 2309 if self._inline:
2307 2310 transaction.add(self._indexfile, dsize + isize)
2308 2311 else:
2309 2312 transaction.add(self._indexfile, isize)
2310 2313 # exposing all file handle for writing.
2311 2314 self._writinghandles = (ifh, dfh, sdfh)
2312 2315 self._segmentfile.writing_handle = ifh if self._inline else dfh
2313 2316 self._segmentfile_sidedata.writing_handle = sdfh
2314 2317 yield
2315 2318 if self._docket is not None:
2316 2319 self._write_docket(transaction)
2317 2320 finally:
2318 2321 self._writinghandles = None
2319 2322 self._segmentfile.writing_handle = None
2320 2323 self._segmentfile_sidedata.writing_handle = None
2321 2324 if dfh is not None:
2322 2325 dfh.close()
2323 2326 if sdfh is not None:
2324 2327 sdfh.close()
2325 2328 # closing the index file last to avoid exposing referent to
2326 2329 # potential unflushed data content.
2327 2330 if ifh is not None:
2328 2331 ifh.close()
2329 2332
2330 2333 def _write_docket(self, transaction):
2331 2334 """write the current docket on disk
2332 2335
2333 2336 Exist as a method to help changelog to implement transaction logic
2334 2337
2335 2338 We could also imagine using the same transaction logic for all revlog
2336 2339 since docket are cheap."""
2337 2340 self._docket.write(transaction)
2338 2341
2339 2342 def addrevision(
2340 2343 self,
2341 2344 text,
2342 2345 transaction,
2343 2346 link,
2344 2347 p1,
2345 2348 p2,
2346 2349 cachedelta=None,
2347 2350 node=None,
2348 2351 flags=REVIDX_DEFAULT_FLAGS,
2349 2352 deltacomputer=None,
2350 2353 sidedata=None,
2351 2354 ):
2352 2355 """add a revision to the log
2353 2356
2354 2357 text - the revision data to add
2355 2358 transaction - the transaction object used for rollback
2356 2359 link - the linkrev data to add
2357 2360 p1, p2 - the parent nodeids of the revision
2358 2361 cachedelta - an optional precomputed delta
2359 2362 node - nodeid of revision; typically node is not specified, and it is
2360 2363 computed by default as hash(text, p1, p2), however subclasses might
2361 2364 use different hashing method (and override checkhash() in such case)
2362 2365 flags - the known flags to set on the revision
2363 2366 deltacomputer - an optional deltacomputer instance shared between
2364 2367 multiple calls
2365 2368 """
2366 2369 if link == nullrev:
2367 2370 raise error.RevlogError(
2368 2371 _(b"attempted to add linkrev -1 to %s") % self.display_id
2369 2372 )
2370 2373
2371 2374 if sidedata is None:
2372 2375 sidedata = {}
2373 2376 elif sidedata and not self.hassidedata:
2374 2377 raise error.ProgrammingError(
2375 2378 _(b"trying to add sidedata to a revlog who don't support them")
2376 2379 )
2377 2380
2378 2381 if flags:
2379 2382 node = node or self.hash(text, p1, p2)
2380 2383
2381 2384 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
2382 2385
2383 2386 # If the flag processor modifies the revision data, ignore any provided
2384 2387 # cachedelta.
2385 2388 if rawtext != text:
2386 2389 cachedelta = None
2387 2390
2388 2391 if len(rawtext) > _maxentrysize:
2389 2392 raise error.RevlogError(
2390 2393 _(
2391 2394 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2392 2395 )
2393 2396 % (self.display_id, len(rawtext))
2394 2397 )
2395 2398
2396 2399 node = node or self.hash(rawtext, p1, p2)
2397 2400 rev = self.index.get_rev(node)
2398 2401 if rev is not None:
2399 2402 return rev
2400 2403
2401 2404 if validatehash:
2402 2405 self.checkhash(rawtext, node, p1=p1, p2=p2)
2403 2406
2404 2407 return self.addrawrevision(
2405 2408 rawtext,
2406 2409 transaction,
2407 2410 link,
2408 2411 p1,
2409 2412 p2,
2410 2413 node,
2411 2414 flags,
2412 2415 cachedelta=cachedelta,
2413 2416 deltacomputer=deltacomputer,
2414 2417 sidedata=sidedata,
2415 2418 )
2416 2419
2417 2420 def addrawrevision(
2418 2421 self,
2419 2422 rawtext,
2420 2423 transaction,
2421 2424 link,
2422 2425 p1,
2423 2426 p2,
2424 2427 node,
2425 2428 flags,
2426 2429 cachedelta=None,
2427 2430 deltacomputer=None,
2428 2431 sidedata=None,
2429 2432 ):
2430 2433 """add a raw revision with known flags, node and parents
2431 2434 useful when reusing a revision not stored in this revlog (ex: received
2432 2435 over wire, or read from an external bundle).
2433 2436 """
2434 2437 with self._writing(transaction):
2435 2438 return self._addrevision(
2436 2439 node,
2437 2440 rawtext,
2438 2441 transaction,
2439 2442 link,
2440 2443 p1,
2441 2444 p2,
2442 2445 flags,
2443 2446 cachedelta,
2444 2447 deltacomputer=deltacomputer,
2445 2448 sidedata=sidedata,
2446 2449 )
2447 2450
2448 2451 def compress(self, data):
2449 2452 """Generate a possibly-compressed representation of data."""
2450 2453 if not data:
2451 2454 return b'', data
2452 2455
2453 2456 compressed = self._compressor.compress(data)
2454 2457
2455 2458 if compressed:
2456 2459 # The revlog compressor added the header in the returned data.
2457 2460 return b'', compressed
2458 2461
2459 2462 if data[0:1] == b'\0':
2460 2463 return b'', data
2461 2464 return b'u', data
2462 2465
2463 2466 def decompress(self, data):
2464 2467 """Decompress a revlog chunk.
2465 2468
2466 2469 The chunk is expected to begin with a header identifying the
2467 2470 format type so it can be routed to an appropriate decompressor.
2468 2471 """
2469 2472 if not data:
2470 2473 return data
2471 2474
2472 2475 # Revlogs are read much more frequently than they are written and many
2473 2476 # chunks only take microseconds to decompress, so performance is
2474 2477 # important here.
2475 2478 #
2476 2479 # We can make a few assumptions about revlogs:
2477 2480 #
2478 2481 # 1) the majority of chunks will be compressed (as opposed to inline
2479 2482 # raw data).
2480 2483 # 2) decompressing *any* data will likely by at least 10x slower than
2481 2484 # returning raw inline data.
2482 2485 # 3) we want to prioritize common and officially supported compression
2483 2486 # engines
2484 2487 #
2485 2488 # It follows that we want to optimize for "decompress compressed data
2486 2489 # when encoded with common and officially supported compression engines"
2487 2490 # case over "raw data" and "data encoded by less common or non-official
2488 2491 # compression engines." That is why we have the inline lookup first
2489 2492 # followed by the compengines lookup.
2490 2493 #
2491 2494 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2492 2495 # compressed chunks. And this matters for changelog and manifest reads.
2493 2496 t = data[0:1]
2494 2497
2495 2498 if t == b'x':
2496 2499 try:
2497 2500 return _zlibdecompress(data)
2498 2501 except zlib.error as e:
2499 2502 raise error.RevlogError(
2500 2503 _(b'revlog decompress error: %s')
2501 2504 % stringutil.forcebytestr(e)
2502 2505 )
2503 2506 # '\0' is more common than 'u' so it goes first.
2504 2507 elif t == b'\0':
2505 2508 return data
2506 2509 elif t == b'u':
2507 2510 return util.buffer(data, 1)
2508 2511
2509 2512 compressor = self._get_decompressor(t)
2510 2513
2511 2514 return compressor.decompress(data)
2512 2515
2513 2516 def _addrevision(
2514 2517 self,
2515 2518 node,
2516 2519 rawtext,
2517 2520 transaction,
2518 2521 link,
2519 2522 p1,
2520 2523 p2,
2521 2524 flags,
2522 2525 cachedelta,
2523 2526 alwayscache=False,
2524 2527 deltacomputer=None,
2525 2528 sidedata=None,
2526 2529 ):
2527 2530 """internal function to add revisions to the log
2528 2531
2529 2532 see addrevision for argument descriptions.
2530 2533
2531 2534 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2532 2535
2533 2536 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2534 2537 be used.
2535 2538
2536 2539 invariants:
2537 2540 - rawtext is optional (can be None); if not set, cachedelta must be set.
2538 2541 if both are set, they must correspond to each other.
2539 2542 """
2540 2543 if node == self.nullid:
2541 2544 raise error.RevlogError(
2542 2545 _(b"%s: attempt to add null revision") % self.display_id
2543 2546 )
2544 2547 if (
2545 2548 node == self.nodeconstants.wdirid
2546 2549 or node in self.nodeconstants.wdirfilenodeids
2547 2550 ):
2548 2551 raise error.RevlogError(
2549 2552 _(b"%s: attempt to add wdir revision") % self.display_id
2550 2553 )
2551 2554 if self._writinghandles is None:
2552 2555 msg = b'adding revision outside `revlog._writing` context'
2553 2556 raise error.ProgrammingError(msg)
2554 2557
2555 2558 if self._inline:
2556 2559 fh = self._writinghandles[0]
2557 2560 else:
2558 2561 fh = self._writinghandles[1]
2559 2562
2560 2563 btext = [rawtext]
2561 2564
2562 2565 curr = len(self)
2563 2566 prev = curr - 1
2564 2567
2565 2568 offset = self._get_data_offset(prev)
2566 2569
2567 2570 if self._concurrencychecker:
2568 2571 ifh, dfh, sdfh = self._writinghandles
2569 2572 # XXX no checking for the sidedata file
2570 2573 if self._inline:
2571 2574 # offset is "as if" it were in the .d file, so we need to add on
2572 2575 # the size of the entry metadata.
2573 2576 self._concurrencychecker(
2574 2577 ifh, self._indexfile, offset + curr * self.index.entry_size
2575 2578 )
2576 2579 else:
2577 2580 # Entries in the .i are a consistent size.
2578 2581 self._concurrencychecker(
2579 2582 ifh, self._indexfile, curr * self.index.entry_size
2580 2583 )
2581 2584 self._concurrencychecker(dfh, self._datafile, offset)
2582 2585
2583 2586 p1r, p2r = self.rev(p1), self.rev(p2)
2584 2587
2585 2588 # full versions are inserted when the needed deltas
2586 2589 # become comparable to the uncompressed text
2587 2590 if rawtext is None:
2588 2591 # need rawtext size, before changed by flag processors, which is
2589 2592 # the non-raw size. use revlog explicitly to avoid filelog's extra
2590 2593 # logic that might remove metadata size.
2591 2594 textlen = mdiff.patchedsize(
2592 2595 revlog.size(self, cachedelta[0]), cachedelta[1]
2593 2596 )
2594 2597 else:
2595 2598 textlen = len(rawtext)
2596 2599
2597 2600 if deltacomputer is None:
2598 2601 write_debug = None
2599 2602 if self._debug_delta:
2600 2603 write_debug = transaction._report
2601 2604 deltacomputer = deltautil.deltacomputer(
2602 2605 self, write_debug=write_debug
2603 2606 )
2604 2607
2605 2608 if cachedelta is not None and len(cachedelta) == 2:
2606 2609 # If the cached delta has no information about how it should be
2607 2610 # reused, add the default reuse instruction according to the
2608 2611 # revlog's configuration.
2609 2612 if self._generaldelta and self._lazydeltabase:
2610 2613 delta_base_reuse = DELTA_BASE_REUSE_TRY
2611 2614 else:
2612 2615 delta_base_reuse = DELTA_BASE_REUSE_NO
2613 2616 cachedelta = (cachedelta[0], cachedelta[1], delta_base_reuse)
2614 2617
2615 2618 revinfo = revlogutils.revisioninfo(
2616 2619 node,
2617 2620 p1,
2618 2621 p2,
2619 2622 btext,
2620 2623 textlen,
2621 2624 cachedelta,
2622 2625 flags,
2623 2626 )
2624 2627
2625 2628 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2626 2629
2627 2630 compression_mode = COMP_MODE_INLINE
2628 2631 if self._docket is not None:
2629 2632 default_comp = self._docket.default_compression_header
2630 2633 r = deltautil.delta_compression(default_comp, deltainfo)
2631 2634 compression_mode, deltainfo = r
2632 2635
2633 2636 sidedata_compression_mode = COMP_MODE_INLINE
2634 2637 if sidedata and self.hassidedata:
2635 2638 sidedata_compression_mode = COMP_MODE_PLAIN
2636 2639 serialized_sidedata = sidedatautil.serialize_sidedata(sidedata)
2637 2640 sidedata_offset = self._docket.sidedata_end
2638 2641 h, comp_sidedata = self.compress(serialized_sidedata)
2639 2642 if (
2640 2643 h != b'u'
2641 2644 and comp_sidedata[0:1] != b'\0'
2642 2645 and len(comp_sidedata) < len(serialized_sidedata)
2643 2646 ):
2644 2647 assert not h
2645 2648 if (
2646 2649 comp_sidedata[0:1]
2647 2650 == self._docket.default_compression_header
2648 2651 ):
2649 2652 sidedata_compression_mode = COMP_MODE_DEFAULT
2650 2653 serialized_sidedata = comp_sidedata
2651 2654 else:
2652 2655 sidedata_compression_mode = COMP_MODE_INLINE
2653 2656 serialized_sidedata = comp_sidedata
2654 2657 else:
2655 2658 serialized_sidedata = b""
2656 2659 # Don't store the offset if the sidedata is empty, that way
2657 2660 # we can easily detect empty sidedata and they will be no different
2658 2661 # than ones we manually add.
2659 2662 sidedata_offset = 0
2660 2663
2661 2664 rank = RANK_UNKNOWN
2662 2665 if self._compute_rank:
2663 2666 if (p1r, p2r) == (nullrev, nullrev):
2664 2667 rank = 1
2665 2668 elif p1r != nullrev and p2r == nullrev:
2666 2669 rank = 1 + self.fast_rank(p1r)
2667 2670 elif p1r == nullrev and p2r != nullrev:
2668 2671 rank = 1 + self.fast_rank(p2r)
2669 2672 else: # merge node
2670 2673 if rustdagop is not None and self.index.rust_ext_compat:
2671 2674 rank = rustdagop.rank(self.index, p1r, p2r)
2672 2675 else:
2673 2676 pmin, pmax = sorted((p1r, p2r))
2674 2677 rank = 1 + self.fast_rank(pmax)
2675 2678 rank += sum(1 for _ in self.findmissingrevs([pmax], [pmin]))
2676 2679
2677 2680 e = revlogutils.entry(
2678 2681 flags=flags,
2679 2682 data_offset=offset,
2680 2683 data_compressed_length=deltainfo.deltalen,
2681 2684 data_uncompressed_length=textlen,
2682 2685 data_compression_mode=compression_mode,
2683 2686 data_delta_base=deltainfo.base,
2684 2687 link_rev=link,
2685 2688 parent_rev_1=p1r,
2686 2689 parent_rev_2=p2r,
2687 2690 node_id=node,
2688 2691 sidedata_offset=sidedata_offset,
2689 2692 sidedata_compressed_length=len(serialized_sidedata),
2690 2693 sidedata_compression_mode=sidedata_compression_mode,
2691 2694 rank=rank,
2692 2695 )
2693 2696
2694 2697 self.index.append(e)
2695 2698 entry = self.index.entry_binary(curr)
2696 2699 if curr == 0 and self._docket is None:
2697 2700 header = self._format_flags | self._format_version
2698 2701 header = self.index.pack_header(header)
2699 2702 entry = header + entry
2700 2703 self._writeentry(
2701 2704 transaction,
2702 2705 entry,
2703 2706 deltainfo.data,
2704 2707 link,
2705 2708 offset,
2706 2709 serialized_sidedata,
2707 2710 sidedata_offset,
2708 2711 )
2709 2712
2710 2713 rawtext = btext[0]
2711 2714
2712 2715 if alwayscache and rawtext is None:
2713 2716 rawtext = deltacomputer.buildtext(revinfo, fh)
2714 2717
2715 2718 if type(rawtext) == bytes: # only accept immutable objects
2716 2719 self._revisioncache = (node, curr, rawtext)
2717 2720 self._chainbasecache[curr] = deltainfo.chainbase
2718 2721 return curr
2719 2722
2720 2723 def _get_data_offset(self, prev):
2721 2724 """Returns the current offset in the (in-transaction) data file.
2722 2725 Versions < 2 of the revlog can get this 0(1), revlog v2 needs a docket
2723 2726 file to store that information: since sidedata can be rewritten to the
2724 2727 end of the data file within a transaction, you can have cases where, for
2725 2728 example, rev `n` does not have sidedata while rev `n - 1` does, leading
2726 2729 to `n - 1`'s sidedata being written after `n`'s data.
2727 2730
2728 2731 TODO cache this in a docket file before getting out of experimental."""
2729 2732 if self._docket is None:
2730 2733 return self.end(prev)
2731 2734 else:
2732 2735 return self._docket.data_end
2733 2736
2734 2737 def _writeentry(
2735 2738 self, transaction, entry, data, link, offset, sidedata, sidedata_offset
2736 2739 ):
2737 2740 # Files opened in a+ mode have inconsistent behavior on various
2738 2741 # platforms. Windows requires that a file positioning call be made
2739 2742 # when the file handle transitions between reads and writes. See
2740 2743 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2741 2744 # platforms, Python or the platform itself can be buggy. Some versions
2742 2745 # of Solaris have been observed to not append at the end of the file
2743 2746 # if the file was seeked to before the end. See issue4943 for more.
2744 2747 #
2745 2748 # We work around this issue by inserting a seek() before writing.
2746 2749 # Note: This is likely not necessary on Python 3. However, because
2747 2750 # the file handle is reused for reads and may be seeked there, we need
2748 2751 # to be careful before changing this.
2749 2752 if self._writinghandles is None:
2750 2753 msg = b'adding revision outside `revlog._writing` context'
2751 2754 raise error.ProgrammingError(msg)
2752 2755 ifh, dfh, sdfh = self._writinghandles
2753 2756 if self._docket is None:
2754 2757 ifh.seek(0, os.SEEK_END)
2755 2758 else:
2756 2759 ifh.seek(self._docket.index_end, os.SEEK_SET)
2757 2760 if dfh:
2758 2761 if self._docket is None:
2759 2762 dfh.seek(0, os.SEEK_END)
2760 2763 else:
2761 2764 dfh.seek(self._docket.data_end, os.SEEK_SET)
2762 2765 if sdfh:
2763 2766 sdfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2764 2767
2765 2768 curr = len(self) - 1
2766 2769 if not self._inline:
2767 2770 transaction.add(self._datafile, offset)
2768 2771 if self._sidedatafile:
2769 2772 transaction.add(self._sidedatafile, sidedata_offset)
2770 2773 transaction.add(self._indexfile, curr * len(entry))
2771 2774 if data[0]:
2772 2775 dfh.write(data[0])
2773 2776 dfh.write(data[1])
2774 2777 if sidedata:
2775 2778 sdfh.write(sidedata)
2776 2779 ifh.write(entry)
2777 2780 else:
2778 2781 offset += curr * self.index.entry_size
2779 2782 transaction.add(self._indexfile, offset)
2780 2783 ifh.write(entry)
2781 2784 ifh.write(data[0])
2782 2785 ifh.write(data[1])
2783 2786 assert not sidedata
2784 2787 self._enforceinlinesize(transaction)
2785 2788 if self._docket is not None:
2786 2789 # revlog-v2 always has 3 writing handles, help Pytype
2787 2790 wh1 = self._writinghandles[0]
2788 2791 wh2 = self._writinghandles[1]
2789 2792 wh3 = self._writinghandles[2]
2790 2793 assert wh1 is not None
2791 2794 assert wh2 is not None
2792 2795 assert wh3 is not None
2793 2796 self._docket.index_end = wh1.tell()
2794 2797 self._docket.data_end = wh2.tell()
2795 2798 self._docket.sidedata_end = wh3.tell()
2796 2799
2797 2800 nodemaputil.setup_persistent_nodemap(transaction, self)
2798 2801
2799 2802 def addgroup(
2800 2803 self,
2801 2804 deltas,
2802 2805 linkmapper,
2803 2806 transaction,
2804 2807 alwayscache=False,
2805 2808 addrevisioncb=None,
2806 2809 duplicaterevisioncb=None,
2807 2810 debug_info=None,
2808 2811 delta_base_reuse_policy=None,
2809 2812 ):
2810 2813 """
2811 2814 add a delta group
2812 2815
2813 2816 given a set of deltas, add them to the revision log. the
2814 2817 first delta is against its parent, which should be in our
2815 2818 log, the rest are against the previous delta.
2816 2819
2817 2820 If ``addrevisioncb`` is defined, it will be called with arguments of
2818 2821 this revlog and the node that was added.
2819 2822 """
2820 2823
2821 2824 if self._adding_group:
2822 2825 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2823 2826
2824 2827 # read the default delta-base reuse policy from revlog config if the
2825 2828 # group did not specify one.
2826 2829 if delta_base_reuse_policy is None:
2827 2830 if self._generaldelta and self._lazydeltabase:
2828 2831 delta_base_reuse_policy = DELTA_BASE_REUSE_TRY
2829 2832 else:
2830 2833 delta_base_reuse_policy = DELTA_BASE_REUSE_NO
2831 2834
2832 2835 self._adding_group = True
2833 2836 empty = True
2834 2837 try:
2835 2838 with self._writing(transaction):
2836 2839 write_debug = None
2837 2840 if self._debug_delta:
2838 2841 write_debug = transaction._report
2839 2842 deltacomputer = deltautil.deltacomputer(
2840 2843 self,
2841 2844 write_debug=write_debug,
2842 2845 debug_info=debug_info,
2843 2846 )
2844 2847 # loop through our set of deltas
2845 2848 for data in deltas:
2846 2849 (
2847 2850 node,
2848 2851 p1,
2849 2852 p2,
2850 2853 linknode,
2851 2854 deltabase,
2852 2855 delta,
2853 2856 flags,
2854 2857 sidedata,
2855 2858 ) = data
2856 2859 link = linkmapper(linknode)
2857 2860 flags = flags or REVIDX_DEFAULT_FLAGS
2858 2861
2859 2862 rev = self.index.get_rev(node)
2860 2863 if rev is not None:
2861 2864 # this can happen if two branches make the same change
2862 2865 self._nodeduplicatecallback(transaction, rev)
2863 2866 if duplicaterevisioncb:
2864 2867 duplicaterevisioncb(self, rev)
2865 2868 empty = False
2866 2869 continue
2867 2870
2868 2871 for p in (p1, p2):
2869 2872 if not self.index.has_node(p):
2870 2873 raise error.LookupError(
2871 2874 p, self.radix, _(b'unknown parent')
2872 2875 )
2873 2876
2874 2877 if not self.index.has_node(deltabase):
2875 2878 raise error.LookupError(
2876 2879 deltabase, self.display_id, _(b'unknown delta base')
2877 2880 )
2878 2881
2879 2882 baserev = self.rev(deltabase)
2880 2883
2881 2884 if baserev != nullrev and self.iscensored(baserev):
2882 2885 # if base is censored, delta must be full replacement in a
2883 2886 # single patch operation
2884 2887 hlen = struct.calcsize(b">lll")
2885 2888 oldlen = self.rawsize(baserev)
2886 2889 newlen = len(delta) - hlen
2887 2890 if delta[:hlen] != mdiff.replacediffheader(
2888 2891 oldlen, newlen
2889 2892 ):
2890 2893 raise error.CensoredBaseError(
2891 2894 self.display_id, self.node(baserev)
2892 2895 )
2893 2896
2894 2897 if not flags and self._peek_iscensored(baserev, delta):
2895 2898 flags |= REVIDX_ISCENSORED
2896 2899
2897 2900 # We assume consumers of addrevisioncb will want to retrieve
2898 2901 # the added revision, which will require a call to
2899 2902 # revision(). revision() will fast path if there is a cache
2900 2903 # hit. So, we tell _addrevision() to always cache in this case.
2901 2904 # We're only using addgroup() in the context of changegroup
2902 2905 # generation so the revision data can always be handled as raw
2903 2906 # by the flagprocessor.
2904 2907 rev = self._addrevision(
2905 2908 node,
2906 2909 None,
2907 2910 transaction,
2908 2911 link,
2909 2912 p1,
2910 2913 p2,
2911 2914 flags,
2912 2915 (baserev, delta, delta_base_reuse_policy),
2913 2916 alwayscache=alwayscache,
2914 2917 deltacomputer=deltacomputer,
2915 2918 sidedata=sidedata,
2916 2919 )
2917 2920
2918 2921 if addrevisioncb:
2919 2922 addrevisioncb(self, rev)
2920 2923 empty = False
2921 2924 finally:
2922 2925 self._adding_group = False
2923 2926 return not empty
2924 2927
2925 2928 def iscensored(self, rev):
2926 2929 """Check if a file revision is censored."""
2927 2930 if not self._censorable:
2928 2931 return False
2929 2932
2930 2933 return self.flags(rev) & REVIDX_ISCENSORED
2931 2934
2932 2935 def _peek_iscensored(self, baserev, delta):
2933 2936 """Quickly check if a delta produces a censored revision."""
2934 2937 if not self._censorable:
2935 2938 return False
2936 2939
2937 2940 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2938 2941
2939 2942 def getstrippoint(self, minlink):
2940 2943 """find the minimum rev that must be stripped to strip the linkrev
2941 2944
2942 2945 Returns a tuple containing the minimum rev and a set of all revs that
2943 2946 have linkrevs that will be broken by this strip.
2944 2947 """
2945 2948 return storageutil.resolvestripinfo(
2946 2949 minlink,
2947 2950 len(self) - 1,
2948 2951 self.headrevs(),
2949 2952 self.linkrev,
2950 2953 self.parentrevs,
2951 2954 )
2952 2955
2953 2956 def strip(self, minlink, transaction):
2954 2957 """truncate the revlog on the first revision with a linkrev >= minlink
2955 2958
2956 2959 This function is called when we're stripping revision minlink and
2957 2960 its descendants from the repository.
2958 2961
2959 2962 We have to remove all revisions with linkrev >= minlink, because
2960 2963 the equivalent changelog revisions will be renumbered after the
2961 2964 strip.
2962 2965
2963 2966 So we truncate the revlog on the first of these revisions, and
2964 2967 trust that the caller has saved the revisions that shouldn't be
2965 2968 removed and that it'll re-add them after this truncation.
2966 2969 """
2967 2970 if len(self) == 0:
2968 2971 return
2969 2972
2970 2973 rev, _ = self.getstrippoint(minlink)
2971 2974 if rev == len(self):
2972 2975 return
2973 2976
2974 2977 # first truncate the files on disk
2975 2978 data_end = self.start(rev)
2976 2979 if not self._inline:
2977 2980 transaction.add(self._datafile, data_end)
2978 2981 end = rev * self.index.entry_size
2979 2982 else:
2980 2983 end = data_end + (rev * self.index.entry_size)
2981 2984
2982 2985 if self._sidedatafile:
2983 2986 sidedata_end = self.sidedata_cut_off(rev)
2984 2987 transaction.add(self._sidedatafile, sidedata_end)
2985 2988
2986 2989 transaction.add(self._indexfile, end)
2987 2990 if self._docket is not None:
2988 2991 # XXX we could, leverage the docket while stripping. However it is
2989 2992 # not powerfull enough at the time of this comment
2990 2993 self._docket.index_end = end
2991 2994 self._docket.data_end = data_end
2992 2995 self._docket.sidedata_end = sidedata_end
2993 2996 self._docket.write(transaction, stripping=True)
2994 2997
2995 2998 # then reset internal state in memory to forget those revisions
2996 2999 self._revisioncache = None
2997 3000 self._chaininfocache = util.lrucachedict(500)
2998 3001 self._segmentfile.clear_cache()
2999 3002 self._segmentfile_sidedata.clear_cache()
3000 3003
3001 3004 del self.index[rev:-1]
3002 3005
3003 3006 def checksize(self):
3004 3007 """Check size of index and data files
3005 3008
3006 3009 return a (dd, di) tuple.
3007 3010 - dd: extra bytes for the "data" file
3008 3011 - di: extra bytes for the "index" file
3009 3012
3010 3013 A healthy revlog will return (0, 0).
3011 3014 """
3012 3015 expected = 0
3013 3016 if len(self):
3014 3017 expected = max(0, self.end(len(self) - 1))
3015 3018
3016 3019 try:
3017 3020 with self._datafp() as f:
3018 3021 f.seek(0, io.SEEK_END)
3019 3022 actual = f.tell()
3020 3023 dd = actual - expected
3021 3024 except FileNotFoundError:
3022 3025 dd = 0
3023 3026
3024 3027 try:
3025 3028 f = self.opener(self._indexfile)
3026 3029 f.seek(0, io.SEEK_END)
3027 3030 actual = f.tell()
3028 3031 f.close()
3029 3032 s = self.index.entry_size
3030 3033 i = max(0, actual // s)
3031 3034 di = actual - (i * s)
3032 3035 if self._inline:
3033 3036 databytes = 0
3034 3037 for r in self:
3035 3038 databytes += max(0, self.length(r))
3036 3039 dd = 0
3037 3040 di = actual - len(self) * s - databytes
3038 3041 except FileNotFoundError:
3039 3042 di = 0
3040 3043
3041 3044 return (dd, di)
3042 3045
3043 3046 def files(self):
3044 3047 res = [self._indexfile]
3045 3048 if self._docket_file is None:
3046 3049 if not self._inline:
3047 3050 res.append(self._datafile)
3048 3051 else:
3049 3052 res.append(self._docket_file)
3050 3053 res.extend(self._docket.old_index_filepaths(include_empty=False))
3051 3054 if self._docket.data_end:
3052 3055 res.append(self._datafile)
3053 3056 res.extend(self._docket.old_data_filepaths(include_empty=False))
3054 3057 if self._docket.sidedata_end:
3055 3058 res.append(self._sidedatafile)
3056 3059 res.extend(self._docket.old_sidedata_filepaths(include_empty=False))
3057 3060 return res
3058 3061
3059 3062 def emitrevisions(
3060 3063 self,
3061 3064 nodes,
3062 3065 nodesorder=None,
3063 3066 revisiondata=False,
3064 3067 assumehaveparentrevisions=False,
3065 3068 deltamode=repository.CG_DELTAMODE_STD,
3066 3069 sidedata_helpers=None,
3067 3070 debug_info=None,
3068 3071 ):
3069 3072 if nodesorder not in (b'nodes', b'storage', b'linear', None):
3070 3073 raise error.ProgrammingError(
3071 3074 b'unhandled value for nodesorder: %s' % nodesorder
3072 3075 )
3073 3076
3074 3077 if nodesorder is None and not self._generaldelta:
3075 3078 nodesorder = b'storage'
3076 3079
3077 3080 if (
3078 3081 not self._storedeltachains
3079 3082 and deltamode != repository.CG_DELTAMODE_PREV
3080 3083 ):
3081 3084 deltamode = repository.CG_DELTAMODE_FULL
3082 3085
3083 3086 return storageutil.emitrevisions(
3084 3087 self,
3085 3088 nodes,
3086 3089 nodesorder,
3087 3090 revlogrevisiondelta,
3088 3091 deltaparentfn=self.deltaparent,
3089 3092 candeltafn=self.candelta,
3090 3093 rawsizefn=self.rawsize,
3091 3094 revdifffn=self.revdiff,
3092 3095 flagsfn=self.flags,
3093 3096 deltamode=deltamode,
3094 3097 revisiondata=revisiondata,
3095 3098 assumehaveparentrevisions=assumehaveparentrevisions,
3096 3099 sidedata_helpers=sidedata_helpers,
3097 3100 debug_info=debug_info,
3098 3101 )
3099 3102
3100 3103 DELTAREUSEALWAYS = b'always'
3101 3104 DELTAREUSESAMEREVS = b'samerevs'
3102 3105 DELTAREUSENEVER = b'never'
3103 3106
3104 3107 DELTAREUSEFULLADD = b'fulladd'
3105 3108
3106 3109 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
3107 3110
3108 3111 def clone(
3109 3112 self,
3110 3113 tr,
3111 3114 destrevlog,
3112 3115 addrevisioncb=None,
3113 3116 deltareuse=DELTAREUSESAMEREVS,
3114 3117 forcedeltabothparents=None,
3115 3118 sidedata_helpers=None,
3116 3119 ):
3117 3120 """Copy this revlog to another, possibly with format changes.
3118 3121
3119 3122 The destination revlog will contain the same revisions and nodes.
3120 3123 However, it may not be bit-for-bit identical due to e.g. delta encoding
3121 3124 differences.
3122 3125
3123 3126 The ``deltareuse`` argument control how deltas from the existing revlog
3124 3127 are preserved in the destination revlog. The argument can have the
3125 3128 following values:
3126 3129
3127 3130 DELTAREUSEALWAYS
3128 3131 Deltas will always be reused (if possible), even if the destination
3129 3132 revlog would not select the same revisions for the delta. This is the
3130 3133 fastest mode of operation.
3131 3134 DELTAREUSESAMEREVS
3132 3135 Deltas will be reused if the destination revlog would pick the same
3133 3136 revisions for the delta. This mode strikes a balance between speed
3134 3137 and optimization.
3135 3138 DELTAREUSENEVER
3136 3139 Deltas will never be reused. This is the slowest mode of execution.
3137 3140 This mode can be used to recompute deltas (e.g. if the diff/delta
3138 3141 algorithm changes).
3139 3142 DELTAREUSEFULLADD
3140 3143 Revision will be re-added as if their were new content. This is
3141 3144 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
3142 3145 eg: large file detection and handling.
3143 3146
3144 3147 Delta computation can be slow, so the choice of delta reuse policy can
3145 3148 significantly affect run time.
3146 3149
3147 3150 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
3148 3151 two extremes. Deltas will be reused if they are appropriate. But if the
3149 3152 delta could choose a better revision, it will do so. This means if you
3150 3153 are converting a non-generaldelta revlog to a generaldelta revlog,
3151 3154 deltas will be recomputed if the delta's parent isn't a parent of the
3152 3155 revision.
3153 3156
3154 3157 In addition to the delta policy, the ``forcedeltabothparents``
3155 3158 argument controls whether to force compute deltas against both parents
3156 3159 for merges. By default, the current default is used.
3157 3160
3158 3161 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
3159 3162 `sidedata_helpers`.
3160 3163 """
3161 3164 if deltareuse not in self.DELTAREUSEALL:
3162 3165 raise ValueError(
3163 3166 _(b'value for deltareuse invalid: %s') % deltareuse
3164 3167 )
3165 3168
3166 3169 if len(destrevlog):
3167 3170 raise ValueError(_(b'destination revlog is not empty'))
3168 3171
3169 3172 if getattr(self, 'filteredrevs', None):
3170 3173 raise ValueError(_(b'source revlog has filtered revisions'))
3171 3174 if getattr(destrevlog, 'filteredrevs', None):
3172 3175 raise ValueError(_(b'destination revlog has filtered revisions'))
3173 3176
3174 3177 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
3175 3178 # if possible.
3176 3179 oldlazydelta = destrevlog._lazydelta
3177 3180 oldlazydeltabase = destrevlog._lazydeltabase
3178 3181 oldamd = destrevlog._deltabothparents
3179 3182
3180 3183 try:
3181 3184 if deltareuse == self.DELTAREUSEALWAYS:
3182 3185 destrevlog._lazydeltabase = True
3183 3186 destrevlog._lazydelta = True
3184 3187 elif deltareuse == self.DELTAREUSESAMEREVS:
3185 3188 destrevlog._lazydeltabase = False
3186 3189 destrevlog._lazydelta = True
3187 3190 elif deltareuse == self.DELTAREUSENEVER:
3188 3191 destrevlog._lazydeltabase = False
3189 3192 destrevlog._lazydelta = False
3190 3193
3191 3194 destrevlog._deltabothparents = forcedeltabothparents or oldamd
3192 3195
3193 3196 self._clone(
3194 3197 tr,
3195 3198 destrevlog,
3196 3199 addrevisioncb,
3197 3200 deltareuse,
3198 3201 forcedeltabothparents,
3199 3202 sidedata_helpers,
3200 3203 )
3201 3204
3202 3205 finally:
3203 3206 destrevlog._lazydelta = oldlazydelta
3204 3207 destrevlog._lazydeltabase = oldlazydeltabase
3205 3208 destrevlog._deltabothparents = oldamd
3206 3209
3207 3210 def _clone(
3208 3211 self,
3209 3212 tr,
3210 3213 destrevlog,
3211 3214 addrevisioncb,
3212 3215 deltareuse,
3213 3216 forcedeltabothparents,
3214 3217 sidedata_helpers,
3215 3218 ):
3216 3219 """perform the core duty of `revlog.clone` after parameter processing"""
3217 3220 write_debug = None
3218 3221 if self._debug_delta:
3219 3222 write_debug = tr._report
3220 3223 deltacomputer = deltautil.deltacomputer(
3221 3224 destrevlog,
3222 3225 write_debug=write_debug,
3223 3226 )
3224 3227 index = self.index
3225 3228 for rev in self:
3226 3229 entry = index[rev]
3227 3230
3228 3231 # Some classes override linkrev to take filtered revs into
3229 3232 # account. Use raw entry from index.
3230 3233 flags = entry[0] & 0xFFFF
3231 3234 linkrev = entry[4]
3232 3235 p1 = index[entry[5]][7]
3233 3236 p2 = index[entry[6]][7]
3234 3237 node = entry[7]
3235 3238
3236 3239 # (Possibly) reuse the delta from the revlog if allowed and
3237 3240 # the revlog chunk is a delta.
3238 3241 cachedelta = None
3239 3242 rawtext = None
3240 3243 if deltareuse == self.DELTAREUSEFULLADD:
3241 3244 text = self._revisiondata(rev)
3242 3245 sidedata = self.sidedata(rev)
3243 3246
3244 3247 if sidedata_helpers is not None:
3245 3248 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3246 3249 self, sidedata_helpers, sidedata, rev
3247 3250 )
3248 3251 flags = flags | new_flags[0] & ~new_flags[1]
3249 3252
3250 3253 destrevlog.addrevision(
3251 3254 text,
3252 3255 tr,
3253 3256 linkrev,
3254 3257 p1,
3255 3258 p2,
3256 3259 cachedelta=cachedelta,
3257 3260 node=node,
3258 3261 flags=flags,
3259 3262 deltacomputer=deltacomputer,
3260 3263 sidedata=sidedata,
3261 3264 )
3262 3265 else:
3263 3266 if destrevlog._lazydelta:
3264 3267 dp = self.deltaparent(rev)
3265 3268 if dp != nullrev:
3266 3269 cachedelta = (dp, bytes(self._chunk(rev)))
3267 3270
3268 3271 sidedata = None
3269 3272 if not cachedelta:
3270 3273 rawtext = self._revisiondata(rev)
3271 3274 sidedata = self.sidedata(rev)
3272 3275 if sidedata is None:
3273 3276 sidedata = self.sidedata(rev)
3274 3277
3275 3278 if sidedata_helpers is not None:
3276 3279 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3277 3280 self, sidedata_helpers, sidedata, rev
3278 3281 )
3279 3282 flags = flags | new_flags[0] & ~new_flags[1]
3280 3283
3281 3284 with destrevlog._writing(tr):
3282 3285 destrevlog._addrevision(
3283 3286 node,
3284 3287 rawtext,
3285 3288 tr,
3286 3289 linkrev,
3287 3290 p1,
3288 3291 p2,
3289 3292 flags,
3290 3293 cachedelta,
3291 3294 deltacomputer=deltacomputer,
3292 3295 sidedata=sidedata,
3293 3296 )
3294 3297
3295 3298 if addrevisioncb:
3296 3299 addrevisioncb(self, rev, node)
3297 3300
3298 3301 def censorrevision(self, tr, censornode, tombstone=b''):
3299 3302 if self._format_version == REVLOGV0:
3300 3303 raise error.RevlogError(
3301 3304 _(b'cannot censor with version %d revlogs')
3302 3305 % self._format_version
3303 3306 )
3304 3307 elif self._format_version == REVLOGV1:
3305 3308 rewrite.v1_censor(self, tr, censornode, tombstone)
3306 3309 else:
3307 3310 rewrite.v2_censor(self, tr, censornode, tombstone)
3308 3311
3309 3312 def verifyintegrity(self, state):
3310 3313 """Verifies the integrity of the revlog.
3311 3314
3312 3315 Yields ``revlogproblem`` instances describing problems that are
3313 3316 found.
3314 3317 """
3315 3318 dd, di = self.checksize()
3316 3319 if dd:
3317 3320 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
3318 3321 if di:
3319 3322 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
3320 3323
3321 3324 version = self._format_version
3322 3325
3323 3326 # The verifier tells us what version revlog we should be.
3324 3327 if version != state[b'expectedversion']:
3325 3328 yield revlogproblem(
3326 3329 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
3327 3330 % (self.display_id, version, state[b'expectedversion'])
3328 3331 )
3329 3332
3330 3333 state[b'skipread'] = set()
3331 3334 state[b'safe_renamed'] = set()
3332 3335
3333 3336 for rev in self:
3334 3337 node = self.node(rev)
3335 3338
3336 3339 # Verify contents. 4 cases to care about:
3337 3340 #
3338 3341 # common: the most common case
3339 3342 # rename: with a rename
3340 3343 # meta: file content starts with b'\1\n', the metadata
3341 3344 # header defined in filelog.py, but without a rename
3342 3345 # ext: content stored externally
3343 3346 #
3344 3347 # More formally, their differences are shown below:
3345 3348 #
3346 3349 # | common | rename | meta | ext
3347 3350 # -------------------------------------------------------
3348 3351 # flags() | 0 | 0 | 0 | not 0
3349 3352 # renamed() | False | True | False | ?
3350 3353 # rawtext[0:2]=='\1\n'| False | True | True | ?
3351 3354 #
3352 3355 # "rawtext" means the raw text stored in revlog data, which
3353 3356 # could be retrieved by "rawdata(rev)". "text"
3354 3357 # mentioned below is "revision(rev)".
3355 3358 #
3356 3359 # There are 3 different lengths stored physically:
3357 3360 # 1. L1: rawsize, stored in revlog index
3358 3361 # 2. L2: len(rawtext), stored in revlog data
3359 3362 # 3. L3: len(text), stored in revlog data if flags==0, or
3360 3363 # possibly somewhere else if flags!=0
3361 3364 #
3362 3365 # L1 should be equal to L2. L3 could be different from them.
3363 3366 # "text" may or may not affect commit hash depending on flag
3364 3367 # processors (see flagutil.addflagprocessor).
3365 3368 #
3366 3369 # | common | rename | meta | ext
3367 3370 # -------------------------------------------------
3368 3371 # rawsize() | L1 | L1 | L1 | L1
3369 3372 # size() | L1 | L2-LM | L1(*) | L1 (?)
3370 3373 # len(rawtext) | L2 | L2 | L2 | L2
3371 3374 # len(text) | L2 | L2 | L2 | L3
3372 3375 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3373 3376 #
3374 3377 # LM: length of metadata, depending on rawtext
3375 3378 # (*): not ideal, see comment in filelog.size
3376 3379 # (?): could be "- len(meta)" if the resolved content has
3377 3380 # rename metadata
3378 3381 #
3379 3382 # Checks needed to be done:
3380 3383 # 1. length check: L1 == L2, in all cases.
3381 3384 # 2. hash check: depending on flag processor, we may need to
3382 3385 # use either "text" (external), or "rawtext" (in revlog).
3383 3386
3384 3387 try:
3385 3388 skipflags = state.get(b'skipflags', 0)
3386 3389 if skipflags:
3387 3390 skipflags &= self.flags(rev)
3388 3391
3389 3392 _verify_revision(self, skipflags, state, node)
3390 3393
3391 3394 l1 = self.rawsize(rev)
3392 3395 l2 = len(self.rawdata(node))
3393 3396
3394 3397 if l1 != l2:
3395 3398 yield revlogproblem(
3396 3399 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3397 3400 node=node,
3398 3401 )
3399 3402
3400 3403 except error.CensoredNodeError:
3401 3404 if state[b'erroroncensored']:
3402 3405 yield revlogproblem(
3403 3406 error=_(b'censored file data'), node=node
3404 3407 )
3405 3408 state[b'skipread'].add(node)
3406 3409 except Exception as e:
3407 3410 yield revlogproblem(
3408 3411 error=_(b'unpacking %s: %s')
3409 3412 % (short(node), stringutil.forcebytestr(e)),
3410 3413 node=node,
3411 3414 )
3412 3415 state[b'skipread'].add(node)
3413 3416
3414 3417 def storageinfo(
3415 3418 self,
3416 3419 exclusivefiles=False,
3417 3420 sharedfiles=False,
3418 3421 revisionscount=False,
3419 3422 trackedsize=False,
3420 3423 storedsize=False,
3421 3424 ):
3422 3425 d = {}
3423 3426
3424 3427 if exclusivefiles:
3425 3428 d[b'exclusivefiles'] = [(self.opener, self._indexfile)]
3426 3429 if not self._inline:
3427 3430 d[b'exclusivefiles'].append((self.opener, self._datafile))
3428 3431
3429 3432 if sharedfiles:
3430 3433 d[b'sharedfiles'] = []
3431 3434
3432 3435 if revisionscount:
3433 3436 d[b'revisionscount'] = len(self)
3434 3437
3435 3438 if trackedsize:
3436 3439 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3437 3440
3438 3441 if storedsize:
3439 3442 d[b'storedsize'] = sum(
3440 3443 self.opener.stat(path).st_size for path in self.files()
3441 3444 )
3442 3445
3443 3446 return d
3444 3447
3445 3448 def rewrite_sidedata(self, transaction, helpers, startrev, endrev):
3446 3449 if not self.hassidedata:
3447 3450 return
3448 3451 # revlog formats with sidedata support does not support inline
3449 3452 assert not self._inline
3450 3453 if not helpers[1] and not helpers[2]:
3451 3454 # Nothing to generate or remove
3452 3455 return
3453 3456
3454 3457 new_entries = []
3455 3458 # append the new sidedata
3456 3459 with self._writing(transaction):
3457 3460 ifh, dfh, sdfh = self._writinghandles
3458 3461 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
3459 3462
3460 3463 current_offset = sdfh.tell()
3461 3464 for rev in range(startrev, endrev + 1):
3462 3465 entry = self.index[rev]
3463 3466 new_sidedata, flags = sidedatautil.run_sidedata_helpers(
3464 3467 store=self,
3465 3468 sidedata_helpers=helpers,
3466 3469 sidedata={},
3467 3470 rev=rev,
3468 3471 )
3469 3472
3470 3473 serialized_sidedata = sidedatautil.serialize_sidedata(
3471 3474 new_sidedata
3472 3475 )
3473 3476
3474 3477 sidedata_compression_mode = COMP_MODE_INLINE
3475 3478 if serialized_sidedata and self.hassidedata:
3476 3479 sidedata_compression_mode = COMP_MODE_PLAIN
3477 3480 h, comp_sidedata = self.compress(serialized_sidedata)
3478 3481 if (
3479 3482 h != b'u'
3480 3483 and comp_sidedata[0] != b'\0'
3481 3484 and len(comp_sidedata) < len(serialized_sidedata)
3482 3485 ):
3483 3486 assert not h
3484 3487 if (
3485 3488 comp_sidedata[0]
3486 3489 == self._docket.default_compression_header
3487 3490 ):
3488 3491 sidedata_compression_mode = COMP_MODE_DEFAULT
3489 3492 serialized_sidedata = comp_sidedata
3490 3493 else:
3491 3494 sidedata_compression_mode = COMP_MODE_INLINE
3492 3495 serialized_sidedata = comp_sidedata
3493 3496 if entry[8] != 0 or entry[9] != 0:
3494 3497 # rewriting entries that already have sidedata is not
3495 3498 # supported yet, because it introduces garbage data in the
3496 3499 # revlog.
3497 3500 msg = b"rewriting existing sidedata is not supported yet"
3498 3501 raise error.Abort(msg)
3499 3502
3500 3503 # Apply (potential) flags to add and to remove after running
3501 3504 # the sidedata helpers
3502 3505 new_offset_flags = entry[0] | flags[0] & ~flags[1]
3503 3506 entry_update = (
3504 3507 current_offset,
3505 3508 len(serialized_sidedata),
3506 3509 new_offset_flags,
3507 3510 sidedata_compression_mode,
3508 3511 )
3509 3512
3510 3513 # the sidedata computation might have move the file cursors around
3511 3514 sdfh.seek(current_offset, os.SEEK_SET)
3512 3515 sdfh.write(serialized_sidedata)
3513 3516 new_entries.append(entry_update)
3514 3517 current_offset += len(serialized_sidedata)
3515 3518 self._docket.sidedata_end = sdfh.tell()
3516 3519
3517 3520 # rewrite the new index entries
3518 3521 ifh.seek(startrev * self.index.entry_size)
3519 3522 for i, e in enumerate(new_entries):
3520 3523 rev = startrev + i
3521 3524 self.index.replace_sidedata_info(rev, *e)
3522 3525 packed = self.index.entry_binary(rev)
3523 3526 if rev == 0 and self._docket is None:
3524 3527 header = self._format_flags | self._format_version
3525 3528 header = self.index.pack_header(header)
3526 3529 packed = header + packed
3527 3530 ifh.write(packed)
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now