Show More
The requested changes are too big and content was truncated. Show full diff
@@ -0,0 +1,15 b'' | |||
|
1 | FROM centos:centos8 | |
|
2 | ||
|
3 | RUN groupadd -g %GID% build && \ | |
|
4 | useradd -u %UID% -g %GID% -s /bin/bash -d /build -m build | |
|
5 | ||
|
6 | RUN yum install -y \ | |
|
7 | gcc \ | |
|
8 | gettext \ | |
|
9 | make \ | |
|
10 | python3-devel \ | |
|
11 | python3-docutils \ | |
|
12 | rpm-build | |
|
13 | ||
|
14 | # For creating repo meta data | |
|
15 | RUN yum install -y createrepo |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
@@ -1,187 +1,189 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 | 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 |
|
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 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 |
@@ -1,200 +1,202 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 | 59338f9561099de77c684c00f76507f11e46ebe8 5.2rc0 | |
|
202 | ca3dca416f8d5863ca6f5a4a6a6bb835dcd5feeb 5.2 |
@@ -1,261 +1,257 b'' | |||
|
1 | 1 | # If you want to change PREFIX, do not just edit it below. The changed |
|
2 | 2 | # value wont get passed on to recursive make calls. You should instead |
|
3 | 3 | # override the variable on the command like: |
|
4 | 4 | # |
|
5 | 5 | # % make PREFIX=/opt/ install |
|
6 | 6 | |
|
7 | 7 | export PREFIX=/usr/local |
|
8 | 8 | PYTHON?=python |
|
9 | 9 | $(eval HGROOT := $(shell pwd)) |
|
10 | 10 | HGPYTHONS ?= $(HGROOT)/build/pythons |
|
11 | 11 | PURE= |
|
12 | 12 | PYFILESCMD=find mercurial hgext doc -name '*.py' |
|
13 | 13 | PYFILES:=$(shell $(PYFILESCMD)) |
|
14 | 14 | DOCFILES=mercurial/help/*.txt |
|
15 | 15 | export LANGUAGE=C |
|
16 | 16 | export LC_ALL=C |
|
17 | 17 | TESTFLAGS ?= $(shell echo $$HGTESTFLAGS) |
|
18 | 18 | OSXVERSIONFLAGS ?= $(shell echo $$OSXVERSIONFLAGS) |
|
19 | 19 | CARGO = cargo |
|
20 | 20 | |
|
21 | 21 | # Set this to e.g. "mingw32" to use a non-default compiler. |
|
22 | 22 | COMPILER= |
|
23 | 23 | |
|
24 | 24 | COMPILERFLAG_tmp_ = |
|
25 | 25 | COMPILERFLAG_tmp_${COMPILER} ?= -c $(COMPILER) |
|
26 | 26 | COMPILERFLAG=${COMPILERFLAG_tmp_${COMPILER}} |
|
27 | 27 | |
|
28 | 28 | help: |
|
29 | 29 | @echo 'Commonly used make targets:' |
|
30 | 30 | @echo ' all - build program and documentation' |
|
31 | 31 | @echo ' install - install program and man pages to $$PREFIX ($(PREFIX))' |
|
32 | 32 | @echo ' install-home - install with setup.py install --home=$$HOME ($(HOME))' |
|
33 | 33 | @echo ' local - build for inplace usage' |
|
34 | 34 | @echo ' tests - run all tests in the automatic test suite' |
|
35 | 35 | @echo ' test-foo - run only specified tests (e.g. test-merge1.t)' |
|
36 | 36 | @echo ' dist - run all tests and create a source tarball in dist/' |
|
37 | 37 | @echo ' clean - remove files created by other targets' |
|
38 | 38 | @echo ' (except installed files or dist source tarball)' |
|
39 | 39 | @echo ' update-pot - update i18n/hg.pot' |
|
40 | 40 | @echo |
|
41 | 41 | @echo 'Example for a system-wide installation under /usr/local:' |
|
42 | 42 | @echo ' make all && su -c "make install" && hg version' |
|
43 | 43 | @echo |
|
44 | 44 | @echo 'Example for a local installation (usable in this directory):' |
|
45 | 45 | @echo ' make local && ./hg version' |
|
46 | 46 | |
|
47 | 47 | all: build doc |
|
48 | 48 | |
|
49 | 49 | local: |
|
50 | 50 | $(PYTHON) setup.py $(PURE) \ |
|
51 | 51 | build_py -c -d . \ |
|
52 | 52 | build_ext $(COMPILERFLAG) -i \ |
|
53 | 53 | build_hgexe $(COMPILERFLAG) -i \ |
|
54 | 54 | build_mo |
|
55 | 55 | env HGRCPATH= $(PYTHON) hg version |
|
56 | 56 | |
|
57 | 57 | build: |
|
58 | 58 | $(PYTHON) setup.py $(PURE) build $(COMPILERFLAG) |
|
59 | 59 | |
|
60 | 60 | wheel: |
|
61 | 61 | FORCE_SETUPTOOLS=1 $(PYTHON) setup.py $(PURE) bdist_wheel $(COMPILERFLAG) |
|
62 | 62 | |
|
63 | 63 | doc: |
|
64 | 64 | $(MAKE) -C doc |
|
65 | 65 | |
|
66 | 66 | cleanbutpackages: |
|
67 | 67 | -$(PYTHON) setup.py clean --all # ignore errors from this command |
|
68 | 68 | find contrib doc hgext hgext3rd i18n mercurial tests hgdemandimport \ |
|
69 | 69 | \( -name '*.py[cdo]' -o -name '*.so' \) -exec rm -f '{}' ';' |
|
70 | 70 | rm -f MANIFEST MANIFEST.in hgext/__index__.py tests/*.err |
|
71 | 71 | rm -f mercurial/__modulepolicy__.py |
|
72 | 72 | if test -d .hg; then rm -f mercurial/__version__.py; fi |
|
73 | 73 | rm -rf build mercurial/locale |
|
74 | 74 | $(MAKE) -C doc clean |
|
75 | 75 | $(MAKE) -C contrib/chg distclean |
|
76 | 76 | rm -rf rust/target |
|
77 | 77 | rm -f mercurial/rustext.so |
|
78 | 78 | |
|
79 | 79 | clean: cleanbutpackages |
|
80 | 80 | rm -rf packages |
|
81 | 81 | |
|
82 | 82 | install: install-bin install-doc |
|
83 | 83 | |
|
84 | 84 | install-bin: build |
|
85 | 85 | $(PYTHON) setup.py $(PURE) install --root="$(DESTDIR)/" --prefix="$(PREFIX)" --force |
|
86 | 86 | |
|
87 | 87 | install-doc: doc |
|
88 | 88 | cd doc && $(MAKE) $(MFLAGS) install |
|
89 | 89 | |
|
90 | 90 | install-home: install-home-bin install-home-doc |
|
91 | 91 | |
|
92 | 92 | install-home-bin: build |
|
93 | 93 | $(PYTHON) setup.py $(PURE) install --home="$(HOME)" --prefix="" --force |
|
94 | 94 | |
|
95 | 95 | install-home-doc: doc |
|
96 | 96 | cd doc && $(MAKE) $(MFLAGS) PREFIX="$(HOME)" install |
|
97 | 97 | |
|
98 | 98 | MANIFEST-doc: |
|
99 | 99 | $(MAKE) -C doc MANIFEST |
|
100 | 100 | |
|
101 | 101 | MANIFEST.in: MANIFEST-doc |
|
102 | 102 | hg manifest | sed -e 's/^/include /' > MANIFEST.in |
|
103 | 103 | echo include mercurial/__version__.py >> MANIFEST.in |
|
104 | 104 | sed -e 's/^/include /' < doc/MANIFEST >> MANIFEST.in |
|
105 | 105 | |
|
106 | 106 | dist: tests dist-notests |
|
107 | 107 | |
|
108 | 108 | dist-notests: doc MANIFEST.in |
|
109 | 109 | TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py -q sdist |
|
110 | 110 | |
|
111 | 111 | check: tests |
|
112 | 112 | |
|
113 | 113 | tests: |
|
114 | 114 | # Run Rust tests if cargo is installed |
|
115 | 115 | if command -v $(CARGO) >/dev/null 2>&1; then \ |
|
116 | 116 | $(MAKE) rust-tests; \ |
|
117 | 117 | fi |
|
118 | 118 | cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) |
|
119 | 119 | |
|
120 | 120 | test-%: |
|
121 | 121 | cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@ |
|
122 | 122 | |
|
123 | 123 | testpy-%: |
|
124 | 124 | @echo Looking for Python $* in $(HGPYTHONS) |
|
125 | 125 | [ -e $(HGPYTHONS)/$*/bin/python ] || ( \ |
|
126 | 126 | cd $$(mktemp --directory --tmpdir) && \ |
|
127 | 127 | $(MAKE) -f $(HGROOT)/contrib/Makefile.python PYTHONVER=$* PREFIX=$(HGPYTHONS)/$* python ) |
|
128 | 128 | cd tests && $(HGPYTHONS)/$*/bin/python run-tests.py $(TESTFLAGS) |
|
129 | 129 | |
|
130 | 130 | rust-tests: py_feature = $(shell $(PYTHON) -c \ |
|
131 | 131 | 'import sys; print(["python27-bin", "python3-bin"][sys.version_info[0] >= 3])') |
|
132 | 132 | rust-tests: |
|
133 | 133 | cd $(HGROOT)/rust/hg-cpython \ |
|
134 | 134 | && $(CARGO) test --quiet --all \ |
|
135 | 135 | --no-default-features --features "$(py_feature)" |
|
136 | 136 | |
|
137 | 137 | check-code: |
|
138 | 138 | hg manifest | xargs python contrib/check-code.py |
|
139 | 139 | |
|
140 | 140 | format-c: |
|
141 | 141 | clang-format --style file -i \ |
|
142 | 142 | `hg files 'set:(**.c or **.cc or **.h) and not "listfile:contrib/clang-format-ignorelist"'` |
|
143 | 143 | |
|
144 | 144 | update-pot: i18n/hg.pot |
|
145 | 145 | |
|
146 | 146 | i18n/hg.pot: $(PYFILES) $(DOCFILES) i18n/posplit i18n/hggettext |
|
147 | 147 | $(PYTHON) i18n/hggettext mercurial/commands.py \ |
|
148 | 148 | hgext/*.py hgext/*/__init__.py \ |
|
149 | 149 | mercurial/fileset.py mercurial/revset.py \ |
|
150 | 150 | mercurial/templatefilters.py \ |
|
151 | 151 | mercurial/templatefuncs.py \ |
|
152 | 152 | mercurial/templatekw.py \ |
|
153 | 153 | mercurial/filemerge.py \ |
|
154 | 154 | mercurial/hgweb/webcommands.py \ |
|
155 | 155 | mercurial/util.py \ |
|
156 | 156 | $(DOCFILES) > i18n/hg.pot.tmp |
|
157 | 157 | # All strings marked for translation in Mercurial contain |
|
158 | 158 | # ASCII characters only. But some files contain string |
|
159 | 159 | # literals like this '\037\213'. xgettext thinks it has to |
|
160 | 160 | # parse them even though they are not marked for translation. |
|
161 | 161 | # Extracting with an explicit encoding of ISO-8859-1 will make |
|
162 | 162 | # xgettext "parse" and ignore them. |
|
163 | 163 | $(PYFILESCMD) | xargs \ |
|
164 | 164 | xgettext --package-name "Mercurial" \ |
|
165 | 165 | --msgid-bugs-address "<mercurial-devel@mercurial-scm.org>" \ |
|
166 | 166 | --copyright-holder "Matt Mackall <mpm@selenic.com> and others" \ |
|
167 | 167 | --from-code ISO-8859-1 --join --sort-by-file --add-comments=i18n: \ |
|
168 | 168 | -d hg -p i18n -o hg.pot.tmp |
|
169 | 169 | $(PYTHON) i18n/posplit i18n/hg.pot.tmp |
|
170 | 170 | # The target file is not created before the last step. So it never is in |
|
171 | 171 | # an intermediate state. |
|
172 | 172 | mv -f i18n/hg.pot.tmp i18n/hg.pot |
|
173 | 173 | |
|
174 | 174 | %.po: i18n/hg.pot |
|
175 | 175 | # work on a temporary copy for never having a half completed target |
|
176 | 176 | cp $@ $@.tmp |
|
177 | 177 | msgmerge --no-location --update $@.tmp $^ |
|
178 | 178 | mv -f $@.tmp $@ |
|
179 | 179 | |
|
180 | 180 | # Packaging targets |
|
181 | 181 | |
|
182 | 182 | packaging_targets := \ |
|
183 | 183 | centos5 \ |
|
184 | 184 | centos6 \ |
|
185 | 185 | centos7 \ |
|
186 | centos8 \ | |
|
186 | 187 | deb \ |
|
187 | 188 | docker-centos5 \ |
|
188 | 189 | docker-centos6 \ |
|
189 | 190 | docker-centos7 \ |
|
191 | docker-centos8 \ | |
|
190 | 192 | docker-debian-jessie \ |
|
191 | 193 | docker-debian-stretch \ |
|
192 |
docker-fedora |
|
|
193 | docker-fedora21 \ | |
|
194 | docker-fedora28 \ | |
|
195 | docker-fedora29 \ | |
|
194 | docker-fedora \ | |
|
196 | 195 | docker-ubuntu-trusty \ |
|
197 | 196 | docker-ubuntu-trusty-ppa \ |
|
198 | 197 | docker-ubuntu-xenial \ |
|
199 | 198 | docker-ubuntu-xenial-ppa \ |
|
200 | 199 | docker-ubuntu-artful \ |
|
201 | 200 | docker-ubuntu-artful-ppa \ |
|
202 | 201 | docker-ubuntu-bionic \ |
|
203 | 202 | docker-ubuntu-bionic-ppa \ |
|
204 |
fedora |
|
|
205 | fedora21 \ | |
|
206 | fedora28 \ | |
|
207 | fedora29 \ | |
|
203 | fedora \ | |
|
208 | 204 | linux-wheels \ |
|
209 | 205 | linux-wheels-x86_64 \ |
|
210 | 206 | linux-wheels-i686 \ |
|
211 | 207 | ppa |
|
212 | 208 | |
|
213 | 209 | # Forward packaging targets for convenience. |
|
214 | 210 | $(packaging_targets): |
|
215 | 211 | $(MAKE) -C contrib/packaging $@ |
|
216 | 212 | |
|
217 | 213 | osx: |
|
218 | 214 | rm -rf build/mercurial |
|
219 | 215 | /usr/bin/python2.7 setup.py install --optimize=1 \ |
|
220 | 216 | --root=build/mercurial/ --prefix=/usr/local/ \ |
|
221 | 217 | --install-lib=/Library/Python/2.7/site-packages/ |
|
222 | 218 | make -C doc all install DESTDIR="$(PWD)/build/mercurial/" |
|
223 | 219 | # Place a bogon .DS_Store file in the target dir so we can be |
|
224 | 220 | # sure it doesn't get included in the final package. |
|
225 | 221 | touch build/mercurial/.DS_Store |
|
226 | 222 | # install zsh completions - this location appears to be |
|
227 | 223 | # searched by default as of macOS Sierra. |
|
228 | 224 | install -d build/mercurial/usr/local/share/zsh/site-functions/ |
|
229 | 225 | install -m 0644 contrib/zsh_completion build/mercurial/usr/local/share/zsh/site-functions/_hg |
|
230 | 226 | # install bash completions - there doesn't appear to be a |
|
231 | 227 | # place that's searched by default for bash, so we'll follow |
|
232 | 228 | # the lead of Apple's git install and just put it in a |
|
233 | 229 | # location of our own. |
|
234 | 230 | install -d build/mercurial/usr/local/hg/contrib/ |
|
235 | 231 | install -m 0644 contrib/bash_completion build/mercurial/usr/local/hg/contrib/hg-completion.bash |
|
236 | 232 | make -C contrib/chg \ |
|
237 | 233 | HGPATH=/usr/local/bin/hg \ |
|
238 | 234 | PYTHON=/usr/bin/python2.7 \ |
|
239 | 235 | HGEXTDIR=/Library/Python/2.7/site-packages/hgext \ |
|
240 | 236 | DESTDIR=../../build/mercurial \ |
|
241 | 237 | PREFIX=/usr/local \ |
|
242 | 238 | clean install |
|
243 | 239 | mkdir -p $${OUTPUTDIR:-dist} |
|
244 | 240 | HGVER=$$(python contrib/genosxversion.py $(OSXVERSIONFLAGS) build/mercurial/Library/Python/2.7/site-packages/mercurial/__version__.py) && \ |
|
245 | 241 | OSXVER=$$(sw_vers -productVersion | cut -d. -f1,2) && \ |
|
246 | 242 | pkgbuild --filter \\.DS_Store --root build/mercurial/ \ |
|
247 | 243 | --identifier org.mercurial-scm.mercurial \ |
|
248 | 244 | --version "$${HGVER}" \ |
|
249 | 245 | build/mercurial.pkg && \ |
|
250 | 246 | productbuild --distribution contrib/packaging/macosx/distribution.xml \ |
|
251 | 247 | --package-path build/ \ |
|
252 | 248 | --version "$${HGVER}" \ |
|
253 | 249 | --resources contrib/packaging/macosx/ \ |
|
254 | 250 | "$${OUTPUTDIR:-dist/}"/Mercurial-"$${HGVER}"-macosx"$${OSXVER}".pkg |
|
255 | 251 | |
|
256 | 252 | .PHONY: help all local build doc cleanbutpackages clean install install-bin \ |
|
257 | 253 | install-doc install-home install-home-bin install-home-doc \ |
|
258 | 254 | dist dist-notests check tests rust-tests check-code format-c \ |
|
259 | 255 | update-pot \ |
|
260 | 256 | $(packaging_targets) \ |
|
261 | 257 | osx |
@@ -1,17 +1,15 b'' | |||
|
1 | 1 | [tool.black] |
|
2 | 2 | line-length = 80 |
|
3 | 3 | exclude = ''' |
|
4 | 4 | build/ |
|
5 | 5 | | wheelhouse/ |
|
6 | 6 | | dist/ |
|
7 | 7 | | packages/ |
|
8 | 8 | | \.hg/ |
|
9 | 9 | | \.mypy_cache/ |
|
10 | 10 | | \.venv/ |
|
11 | 11 | | mercurial/thirdparty/ |
|
12 | | hgext/fsmonitor/pywatchman/ | |
|
13 | 12 | | contrib/python-zstandard/ |
|
14 | | contrib/grey.py | |
|
15 | 13 | ''' |
|
16 | 14 | skip-string-normalization = true |
|
17 | 15 | quiet = true |
@@ -1,1262 +1,1266 b'' | |||
|
1 | 1 | # aws.py - Automation code for Amazon Web Services |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.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 | # no-check-code because Python 3 native. |
|
9 | 9 | |
|
10 | 10 | import contextlib |
|
11 | 11 | import copy |
|
12 | 12 | import hashlib |
|
13 | 13 | import json |
|
14 | 14 | import os |
|
15 | 15 | import pathlib |
|
16 | 16 | import subprocess |
|
17 | 17 | import time |
|
18 | 18 | |
|
19 | 19 | import boto3 |
|
20 | 20 | import botocore.exceptions |
|
21 | 21 | |
|
22 | 22 | from .linux import BOOTSTRAP_DEBIAN |
|
23 | 23 | from .ssh import ( |
|
24 | 24 | exec_command as ssh_exec_command, |
|
25 | 25 | wait_for_ssh, |
|
26 | 26 | ) |
|
27 | 27 | from .winrm import ( |
|
28 | 28 | run_powershell, |
|
29 | 29 | wait_for_winrm, |
|
30 | 30 | ) |
|
31 | 31 | |
|
32 | 32 | |
|
33 | 33 | SOURCE_ROOT = pathlib.Path( |
|
34 | 34 | os.path.abspath(__file__) |
|
35 | 35 | ).parent.parent.parent.parent |
|
36 | 36 | |
|
37 | 37 | INSTALL_WINDOWS_DEPENDENCIES = ( |
|
38 | 38 | SOURCE_ROOT / 'contrib' / 'install-windows-dependencies.ps1' |
|
39 | 39 | ) |
|
40 | 40 | |
|
41 | 41 | |
|
42 | 42 | INSTANCE_TYPES_WITH_STORAGE = { |
|
43 | 43 | 'c5d', |
|
44 | 44 | 'd2', |
|
45 | 45 | 'h1', |
|
46 | 46 | 'i3', |
|
47 | 47 | 'm5ad', |
|
48 | 48 | 'm5d', |
|
49 | 49 | 'r5d', |
|
50 | 50 | 'r5ad', |
|
51 | 51 | 'x1', |
|
52 | 52 | 'z1d', |
|
53 | 53 | } |
|
54 | 54 | |
|
55 | 55 | |
|
56 | 56 | AMAZON_ACCOUNT_ID = '801119661308' |
|
57 | 57 | DEBIAN_ACCOUNT_ID = '379101102735' |
|
58 | 58 | DEBIAN_ACCOUNT_ID_2 = '136693071363' |
|
59 | 59 | UBUNTU_ACCOUNT_ID = '099720109477' |
|
60 | 60 | |
|
61 | 61 | |
|
62 | 62 | WINDOWS_BASE_IMAGE_NAME = 'Windows_Server-2019-English-Full-Base-2019.07.12' |
|
63 | 63 | |
|
64 | 64 | |
|
65 | 65 | KEY_PAIRS = { |
|
66 | 66 | 'automation', |
|
67 | 67 | } |
|
68 | 68 | |
|
69 | 69 | |
|
70 | 70 | SECURITY_GROUPS = { |
|
71 | 71 | 'linux-dev-1': { |
|
72 | 72 | 'description': 'Mercurial Linux instances that perform build/test automation', |
|
73 | 73 | 'ingress': [ |
|
74 | 74 | { |
|
75 | 75 | 'FromPort': 22, |
|
76 | 76 | 'ToPort': 22, |
|
77 | 77 | 'IpProtocol': 'tcp', |
|
78 | 78 | 'IpRanges': [ |
|
79 | 79 | { |
|
80 | 80 | 'CidrIp': '0.0.0.0/0', |
|
81 | 81 | 'Description': 'SSH from entire Internet', |
|
82 | 82 | }, |
|
83 | 83 | ], |
|
84 | 84 | }, |
|
85 | 85 | ], |
|
86 | 86 | }, |
|
87 | 87 | 'windows-dev-1': { |
|
88 | 88 | 'description': 'Mercurial Windows instances that perform build automation', |
|
89 | 89 | 'ingress': [ |
|
90 | 90 | { |
|
91 | 91 | 'FromPort': 22, |
|
92 | 92 | 'ToPort': 22, |
|
93 | 93 | 'IpProtocol': 'tcp', |
|
94 | 94 | 'IpRanges': [ |
|
95 | 95 | { |
|
96 | 96 | 'CidrIp': '0.0.0.0/0', |
|
97 | 97 | 'Description': 'SSH from entire Internet', |
|
98 | 98 | }, |
|
99 | 99 | ], |
|
100 | 100 | }, |
|
101 | 101 | { |
|
102 | 102 | 'FromPort': 3389, |
|
103 | 103 | 'ToPort': 3389, |
|
104 | 104 | 'IpProtocol': 'tcp', |
|
105 | 105 | 'IpRanges': [ |
|
106 | 106 | { |
|
107 | 107 | 'CidrIp': '0.0.0.0/0', |
|
108 | 108 | 'Description': 'RDP from entire Internet', |
|
109 | 109 | }, |
|
110 | 110 | ], |
|
111 | 111 | }, |
|
112 | 112 | { |
|
113 | 113 | 'FromPort': 5985, |
|
114 | 114 | 'ToPort': 5986, |
|
115 | 115 | 'IpProtocol': 'tcp', |
|
116 | 116 | 'IpRanges': [ |
|
117 | 117 | { |
|
118 | 118 | 'CidrIp': '0.0.0.0/0', |
|
119 | 119 | 'Description': 'PowerShell Remoting (Windows Remote Management)', |
|
120 | 120 | }, |
|
121 | 121 | ], |
|
122 | 122 | }, |
|
123 | 123 | ], |
|
124 | 124 | }, |
|
125 | 125 | } |
|
126 | 126 | |
|
127 | 127 | |
|
128 | 128 | IAM_ROLES = { |
|
129 | 129 | 'ephemeral-ec2-role-1': { |
|
130 | 130 | 'description': 'Mercurial temporary EC2 instances', |
|
131 | 131 | 'policy_arns': [ |
|
132 | 132 | 'arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM', |
|
133 | 133 | ], |
|
134 | 134 | }, |
|
135 | 135 | } |
|
136 | 136 | |
|
137 | 137 | |
|
138 | 138 | ASSUME_ROLE_POLICY_DOCUMENT = ''' |
|
139 | 139 | { |
|
140 | 140 | "Version": "2012-10-17", |
|
141 | 141 | "Statement": [ |
|
142 | 142 | { |
|
143 | 143 | "Effect": "Allow", |
|
144 | 144 | "Principal": { |
|
145 | 145 | "Service": "ec2.amazonaws.com" |
|
146 | 146 | }, |
|
147 | 147 | "Action": "sts:AssumeRole" |
|
148 | 148 | } |
|
149 | 149 | ] |
|
150 | 150 | } |
|
151 | 151 | '''.strip() |
|
152 | 152 | |
|
153 | 153 | |
|
154 | 154 | IAM_INSTANCE_PROFILES = { |
|
155 | 155 | 'ephemeral-ec2-1': {'roles': ['ephemeral-ec2-role-1',],} |
|
156 | 156 | } |
|
157 | 157 | |
|
158 | 158 | |
|
159 | 159 | # User Data for Windows EC2 instance. Mainly used to set the password |
|
160 | 160 | # and configure WinRM. |
|
161 | 161 | # Inspired by the User Data script used by Packer |
|
162 | 162 | # (from https://www.packer.io/intro/getting-started/build-image.html). |
|
163 | 163 | WINDOWS_USER_DATA = r''' |
|
164 | 164 | <powershell> |
|
165 | 165 | |
|
166 | 166 | # TODO enable this once we figure out what is failing. |
|
167 | 167 | #$ErrorActionPreference = "stop" |
|
168 | 168 | |
|
169 | 169 | # Set administrator password |
|
170 | 170 | net user Administrator "%s" |
|
171 | 171 | wmic useraccount where "name='Administrator'" set PasswordExpires=FALSE |
|
172 | 172 | |
|
173 | 173 | # First, make sure WinRM can't be connected to |
|
174 | 174 | netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=block |
|
175 | 175 | |
|
176 | 176 | # Delete any existing WinRM listeners |
|
177 | 177 | winrm delete winrm/config/listener?Address=*+Transport=HTTP 2>$Null |
|
178 | 178 | winrm delete winrm/config/listener?Address=*+Transport=HTTPS 2>$Null |
|
179 | 179 | |
|
180 | 180 | # Create a new WinRM listener and configure |
|
181 | 181 | winrm create winrm/config/listener?Address=*+Transport=HTTP |
|
182 | 182 | winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="0"}' |
|
183 | 183 | winrm set winrm/config '@{MaxTimeoutms="7200000"}' |
|
184 | 184 | winrm set winrm/config/service '@{AllowUnencrypted="true"}' |
|
185 | 185 | winrm set winrm/config/service '@{MaxConcurrentOperationsPerUser="12000"}' |
|
186 | 186 | winrm set winrm/config/service/auth '@{Basic="true"}' |
|
187 | 187 | winrm set winrm/config/client/auth '@{Basic="true"}' |
|
188 | 188 | |
|
189 | 189 | # Configure UAC to allow privilege elevation in remote shells |
|
190 | 190 | $Key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' |
|
191 | 191 | $Setting = 'LocalAccountTokenFilterPolicy' |
|
192 | 192 | Set-ItemProperty -Path $Key -Name $Setting -Value 1 -Force |
|
193 | 193 | |
|
194 | # Avoid long usernames in the temp directory path because the '~' causes extra quoting in ssh output | |
|
195 | [System.Environment]::SetEnvironmentVariable('TMP', 'C:\Temp', [System.EnvironmentVariableTarget]::User) | |
|
196 | [System.Environment]::SetEnvironmentVariable('TEMP', 'C:\Temp', [System.EnvironmentVariableTarget]::User) | |
|
197 | ||
|
194 | 198 | # Configure and restart the WinRM Service; Enable the required firewall exception |
|
195 | 199 | Stop-Service -Name WinRM |
|
196 | 200 | Set-Service -Name WinRM -StartupType Automatic |
|
197 | 201 | netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new action=allow localip=any remoteip=any |
|
198 | 202 | Start-Service -Name WinRM |
|
199 | 203 | |
|
200 | 204 | # Disable firewall on private network interfaces so prompts don't appear. |
|
201 | 205 | Set-NetFirewallProfile -Name private -Enabled false |
|
202 | 206 | </powershell> |
|
203 | 207 | '''.lstrip() |
|
204 | 208 | |
|
205 | 209 | |
|
206 | 210 | WINDOWS_BOOTSTRAP_POWERSHELL = ''' |
|
207 | 211 | Write-Output "installing PowerShell dependencies" |
|
208 | 212 | Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force |
|
209 | 213 | Set-PSRepository -Name PSGallery -InstallationPolicy Trusted |
|
210 | 214 | Install-Module -Name OpenSSHUtils -RequiredVersion 0.0.2.0 |
|
211 | 215 | |
|
212 | 216 | Write-Output "installing OpenSSL server" |
|
213 | 217 | Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 |
|
214 | 218 | # Various tools will attempt to use older versions of .NET. So we enable |
|
215 | 219 | # the feature that provides them so it doesn't have to be auto-enabled |
|
216 | 220 | # later. |
|
217 | 221 | Write-Output "enabling .NET Framework feature" |
|
218 | 222 | Install-WindowsFeature -Name Net-Framework-Core |
|
219 | 223 | ''' |
|
220 | 224 | |
|
221 | 225 | |
|
222 | 226 | class AWSConnection: |
|
223 | 227 | """Manages the state of a connection with AWS.""" |
|
224 | 228 | |
|
225 | 229 | def __init__(self, automation, region: str, ensure_ec2_state: bool = True): |
|
226 | 230 | self.automation = automation |
|
227 | 231 | self.local_state_path = automation.state_path |
|
228 | 232 | |
|
229 | 233 | self.prefix = 'hg-' |
|
230 | 234 | |
|
231 | 235 | self.session = boto3.session.Session(region_name=region) |
|
232 | 236 | self.ec2client = self.session.client('ec2') |
|
233 | 237 | self.ec2resource = self.session.resource('ec2') |
|
234 | 238 | self.iamclient = self.session.client('iam') |
|
235 | 239 | self.iamresource = self.session.resource('iam') |
|
236 | 240 | self.security_groups = {} |
|
237 | 241 | |
|
238 | 242 | if ensure_ec2_state: |
|
239 | 243 | ensure_key_pairs(automation.state_path, self.ec2resource) |
|
240 | 244 | self.security_groups = ensure_security_groups(self.ec2resource) |
|
241 | 245 | ensure_iam_state(self.iamclient, self.iamresource) |
|
242 | 246 | |
|
243 | 247 | def key_pair_path_private(self, name): |
|
244 | 248 | """Path to a key pair private key file.""" |
|
245 | 249 | return self.local_state_path / 'keys' / ('keypair-%s' % name) |
|
246 | 250 | |
|
247 | 251 | def key_pair_path_public(self, name): |
|
248 | 252 | return self.local_state_path / 'keys' / ('keypair-%s.pub' % name) |
|
249 | 253 | |
|
250 | 254 | |
|
251 | 255 | def rsa_key_fingerprint(p: pathlib.Path): |
|
252 | 256 | """Compute the fingerprint of an RSA private key.""" |
|
253 | 257 | |
|
254 | 258 | # TODO use rsa package. |
|
255 | 259 | res = subprocess.run( |
|
256 | 260 | [ |
|
257 | 261 | 'openssl', |
|
258 | 262 | 'pkcs8', |
|
259 | 263 | '-in', |
|
260 | 264 | str(p), |
|
261 | 265 | '-nocrypt', |
|
262 | 266 | '-topk8', |
|
263 | 267 | '-outform', |
|
264 | 268 | 'DER', |
|
265 | 269 | ], |
|
266 | 270 | capture_output=True, |
|
267 | 271 | check=True, |
|
268 | 272 | ) |
|
269 | 273 | |
|
270 | 274 | sha1 = hashlib.sha1(res.stdout).hexdigest() |
|
271 | 275 | return ':'.join(a + b for a, b in zip(sha1[::2], sha1[1::2])) |
|
272 | 276 | |
|
273 | 277 | |
|
274 | 278 | def ensure_key_pairs(state_path: pathlib.Path, ec2resource, prefix='hg-'): |
|
275 | 279 | remote_existing = {} |
|
276 | 280 | |
|
277 | 281 | for kpi in ec2resource.key_pairs.all(): |
|
278 | 282 | if kpi.name.startswith(prefix): |
|
279 | 283 | remote_existing[kpi.name[len(prefix) :]] = kpi.key_fingerprint |
|
280 | 284 | |
|
281 | 285 | # Validate that we have these keys locally. |
|
282 | 286 | key_path = state_path / 'keys' |
|
283 | 287 | key_path.mkdir(exist_ok=True, mode=0o700) |
|
284 | 288 | |
|
285 | 289 | def remove_remote(name): |
|
286 | 290 | print('deleting key pair %s' % name) |
|
287 | 291 | key = ec2resource.KeyPair(name) |
|
288 | 292 | key.delete() |
|
289 | 293 | |
|
290 | 294 | def remove_local(name): |
|
291 | 295 | pub_full = key_path / ('keypair-%s.pub' % name) |
|
292 | 296 | priv_full = key_path / ('keypair-%s' % name) |
|
293 | 297 | |
|
294 | 298 | print('removing %s' % pub_full) |
|
295 | 299 | pub_full.unlink() |
|
296 | 300 | print('removing %s' % priv_full) |
|
297 | 301 | priv_full.unlink() |
|
298 | 302 | |
|
299 | 303 | local_existing = {} |
|
300 | 304 | |
|
301 | 305 | for f in sorted(os.listdir(key_path)): |
|
302 | 306 | if not f.startswith('keypair-') or not f.endswith('.pub'): |
|
303 | 307 | continue |
|
304 | 308 | |
|
305 | 309 | name = f[len('keypair-') : -len('.pub')] |
|
306 | 310 | |
|
307 | 311 | pub_full = key_path / f |
|
308 | 312 | priv_full = key_path / ('keypair-%s' % name) |
|
309 | 313 | |
|
310 | 314 | with open(pub_full, 'r', encoding='ascii') as fh: |
|
311 | 315 | data = fh.read() |
|
312 | 316 | |
|
313 | 317 | if not data.startswith('ssh-rsa '): |
|
314 | 318 | print( |
|
315 | 319 | 'unexpected format for key pair file: %s; removing' % pub_full |
|
316 | 320 | ) |
|
317 | 321 | pub_full.unlink() |
|
318 | 322 | priv_full.unlink() |
|
319 | 323 | continue |
|
320 | 324 | |
|
321 | 325 | local_existing[name] = rsa_key_fingerprint(priv_full) |
|
322 | 326 | |
|
323 | 327 | for name in sorted(set(remote_existing) | set(local_existing)): |
|
324 | 328 | if name not in local_existing: |
|
325 | 329 | actual = '%s%s' % (prefix, name) |
|
326 | 330 | print('remote key %s does not exist locally' % name) |
|
327 | 331 | remove_remote(actual) |
|
328 | 332 | del remote_existing[name] |
|
329 | 333 | |
|
330 | 334 | elif name not in remote_existing: |
|
331 | 335 | print('local key %s does not exist remotely' % name) |
|
332 | 336 | remove_local(name) |
|
333 | 337 | del local_existing[name] |
|
334 | 338 | |
|
335 | 339 | elif remote_existing[name] != local_existing[name]: |
|
336 | 340 | print( |
|
337 | 341 | 'key fingerprint mismatch for %s; ' |
|
338 | 342 | 'removing from local and remote' % name |
|
339 | 343 | ) |
|
340 | 344 | remove_local(name) |
|
341 | 345 | remove_remote('%s%s' % (prefix, name)) |
|
342 | 346 | del local_existing[name] |
|
343 | 347 | del remote_existing[name] |
|
344 | 348 | |
|
345 | 349 | missing = KEY_PAIRS - set(remote_existing) |
|
346 | 350 | |
|
347 | 351 | for name in sorted(missing): |
|
348 | 352 | actual = '%s%s' % (prefix, name) |
|
349 | 353 | print('creating key pair %s' % actual) |
|
350 | 354 | |
|
351 | 355 | priv_full = key_path / ('keypair-%s' % name) |
|
352 | 356 | pub_full = key_path / ('keypair-%s.pub' % name) |
|
353 | 357 | |
|
354 | 358 | kp = ec2resource.create_key_pair(KeyName=actual) |
|
355 | 359 | |
|
356 | 360 | with priv_full.open('w', encoding='ascii') as fh: |
|
357 | 361 | fh.write(kp.key_material) |
|
358 | 362 | fh.write('\n') |
|
359 | 363 | |
|
360 | 364 | priv_full.chmod(0o0600) |
|
361 | 365 | |
|
362 | 366 | # SSH public key can be extracted via `ssh-keygen`. |
|
363 | 367 | with pub_full.open('w', encoding='ascii') as fh: |
|
364 | 368 | subprocess.run( |
|
365 | 369 | ['ssh-keygen', '-y', '-f', str(priv_full)], |
|
366 | 370 | stdout=fh, |
|
367 | 371 | check=True, |
|
368 | 372 | ) |
|
369 | 373 | |
|
370 | 374 | pub_full.chmod(0o0600) |
|
371 | 375 | |
|
372 | 376 | |
|
373 | 377 | def delete_instance_profile(profile): |
|
374 | 378 | for role in profile.roles: |
|
375 | 379 | print( |
|
376 | 380 | 'removing role %s from instance profile %s' |
|
377 | 381 | % (role.name, profile.name) |
|
378 | 382 | ) |
|
379 | 383 | profile.remove_role(RoleName=role.name) |
|
380 | 384 | |
|
381 | 385 | print('deleting instance profile %s' % profile.name) |
|
382 | 386 | profile.delete() |
|
383 | 387 | |
|
384 | 388 | |
|
385 | 389 | def ensure_iam_state(iamclient, iamresource, prefix='hg-'): |
|
386 | 390 | """Ensure IAM state is in sync with our canonical definition.""" |
|
387 | 391 | |
|
388 | 392 | remote_profiles = {} |
|
389 | 393 | |
|
390 | 394 | for profile in iamresource.instance_profiles.all(): |
|
391 | 395 | if profile.name.startswith(prefix): |
|
392 | 396 | remote_profiles[profile.name[len(prefix) :]] = profile |
|
393 | 397 | |
|
394 | 398 | for name in sorted(set(remote_profiles) - set(IAM_INSTANCE_PROFILES)): |
|
395 | 399 | delete_instance_profile(remote_profiles[name]) |
|
396 | 400 | del remote_profiles[name] |
|
397 | 401 | |
|
398 | 402 | remote_roles = {} |
|
399 | 403 | |
|
400 | 404 | for role in iamresource.roles.all(): |
|
401 | 405 | if role.name.startswith(prefix): |
|
402 | 406 | remote_roles[role.name[len(prefix) :]] = role |
|
403 | 407 | |
|
404 | 408 | for name in sorted(set(remote_roles) - set(IAM_ROLES)): |
|
405 | 409 | role = remote_roles[name] |
|
406 | 410 | |
|
407 | 411 | print('removing role %s' % role.name) |
|
408 | 412 | role.delete() |
|
409 | 413 | del remote_roles[name] |
|
410 | 414 | |
|
411 | 415 | # We've purged remote state that doesn't belong. Create missing |
|
412 | 416 | # instance profiles and roles. |
|
413 | 417 | for name in sorted(set(IAM_INSTANCE_PROFILES) - set(remote_profiles)): |
|
414 | 418 | actual = '%s%s' % (prefix, name) |
|
415 | 419 | print('creating IAM instance profile %s' % actual) |
|
416 | 420 | |
|
417 | 421 | profile = iamresource.create_instance_profile( |
|
418 | 422 | InstanceProfileName=actual |
|
419 | 423 | ) |
|
420 | 424 | remote_profiles[name] = profile |
|
421 | 425 | |
|
422 | 426 | waiter = iamclient.get_waiter('instance_profile_exists') |
|
423 | 427 | waiter.wait(InstanceProfileName=actual) |
|
424 | 428 | print('IAM instance profile %s is available' % actual) |
|
425 | 429 | |
|
426 | 430 | for name in sorted(set(IAM_ROLES) - set(remote_roles)): |
|
427 | 431 | entry = IAM_ROLES[name] |
|
428 | 432 | |
|
429 | 433 | actual = '%s%s' % (prefix, name) |
|
430 | 434 | print('creating IAM role %s' % actual) |
|
431 | 435 | |
|
432 | 436 | role = iamresource.create_role( |
|
433 | 437 | RoleName=actual, |
|
434 | 438 | Description=entry['description'], |
|
435 | 439 | AssumeRolePolicyDocument=ASSUME_ROLE_POLICY_DOCUMENT, |
|
436 | 440 | ) |
|
437 | 441 | |
|
438 | 442 | waiter = iamclient.get_waiter('role_exists') |
|
439 | 443 | waiter.wait(RoleName=actual) |
|
440 | 444 | print('IAM role %s is available' % actual) |
|
441 | 445 | |
|
442 | 446 | remote_roles[name] = role |
|
443 | 447 | |
|
444 | 448 | for arn in entry['policy_arns']: |
|
445 | 449 | print('attaching policy %s to %s' % (arn, role.name)) |
|
446 | 450 | role.attach_policy(PolicyArn=arn) |
|
447 | 451 | |
|
448 | 452 | # Now reconcile state of profiles. |
|
449 | 453 | for name, meta in sorted(IAM_INSTANCE_PROFILES.items()): |
|
450 | 454 | profile = remote_profiles[name] |
|
451 | 455 | wanted = {'%s%s' % (prefix, role) for role in meta['roles']} |
|
452 | 456 | have = {role.name for role in profile.roles} |
|
453 | 457 | |
|
454 | 458 | for role in sorted(have - wanted): |
|
455 | 459 | print('removing role %s from %s' % (role, profile.name)) |
|
456 | 460 | profile.remove_role(RoleName=role) |
|
457 | 461 | |
|
458 | 462 | for role in sorted(wanted - have): |
|
459 | 463 | print('adding role %s to %s' % (role, profile.name)) |
|
460 | 464 | profile.add_role(RoleName=role) |
|
461 | 465 | |
|
462 | 466 | |
|
463 | 467 | def find_image(ec2resource, owner_id, name): |
|
464 | 468 | """Find an AMI by its owner ID and name.""" |
|
465 | 469 | |
|
466 | 470 | images = ec2resource.images.filter( |
|
467 | 471 | Filters=[ |
|
468 | 472 | {'Name': 'owner-id', 'Values': [owner_id],}, |
|
469 | 473 | {'Name': 'state', 'Values': ['available'],}, |
|
470 | 474 | {'Name': 'image-type', 'Values': ['machine'],}, |
|
471 | 475 | {'Name': 'name', 'Values': [name],}, |
|
472 | 476 | ] |
|
473 | 477 | ) |
|
474 | 478 | |
|
475 | 479 | for image in images: |
|
476 | 480 | return image |
|
477 | 481 | |
|
478 | 482 | raise Exception('unable to find image for %s' % name) |
|
479 | 483 | |
|
480 | 484 | |
|
481 | 485 | def ensure_security_groups(ec2resource, prefix='hg-'): |
|
482 | 486 | """Ensure all necessary Mercurial security groups are present. |
|
483 | 487 | |
|
484 | 488 | All security groups are prefixed with ``hg-`` by default. Any security |
|
485 | 489 | groups having this prefix but aren't in our list are deleted. |
|
486 | 490 | """ |
|
487 | 491 | existing = {} |
|
488 | 492 | |
|
489 | 493 | for group in ec2resource.security_groups.all(): |
|
490 | 494 | if group.group_name.startswith(prefix): |
|
491 | 495 | existing[group.group_name[len(prefix) :]] = group |
|
492 | 496 | |
|
493 | 497 | purge = set(existing) - set(SECURITY_GROUPS) |
|
494 | 498 | |
|
495 | 499 | for name in sorted(purge): |
|
496 | 500 | group = existing[name] |
|
497 | 501 | print('removing legacy security group: %s' % group.group_name) |
|
498 | 502 | group.delete() |
|
499 | 503 | |
|
500 | 504 | security_groups = {} |
|
501 | 505 | |
|
502 | 506 | for name, group in sorted(SECURITY_GROUPS.items()): |
|
503 | 507 | if name in existing: |
|
504 | 508 | security_groups[name] = existing[name] |
|
505 | 509 | continue |
|
506 | 510 | |
|
507 | 511 | actual = '%s%s' % (prefix, name) |
|
508 | 512 | print('adding security group %s' % actual) |
|
509 | 513 | |
|
510 | 514 | group_res = ec2resource.create_security_group( |
|
511 | 515 | Description=group['description'], GroupName=actual, |
|
512 | 516 | ) |
|
513 | 517 | |
|
514 | 518 | group_res.authorize_ingress(IpPermissions=group['ingress'],) |
|
515 | 519 | |
|
516 | 520 | security_groups[name] = group_res |
|
517 | 521 | |
|
518 | 522 | return security_groups |
|
519 | 523 | |
|
520 | 524 | |
|
521 | 525 | def terminate_ec2_instances(ec2resource, prefix='hg-'): |
|
522 | 526 | """Terminate all EC2 instances managed by us.""" |
|
523 | 527 | waiting = [] |
|
524 | 528 | |
|
525 | 529 | for instance in ec2resource.instances.all(): |
|
526 | 530 | if instance.state['Name'] == 'terminated': |
|
527 | 531 | continue |
|
528 | 532 | |
|
529 | 533 | for tag in instance.tags or []: |
|
530 | 534 | if tag['Key'] == 'Name' and tag['Value'].startswith(prefix): |
|
531 | 535 | print('terminating %s' % instance.id) |
|
532 | 536 | instance.terminate() |
|
533 | 537 | waiting.append(instance) |
|
534 | 538 | |
|
535 | 539 | for instance in waiting: |
|
536 | 540 | instance.wait_until_terminated() |
|
537 | 541 | |
|
538 | 542 | |
|
539 | 543 | def remove_resources(c, prefix='hg-'): |
|
540 | 544 | """Purge all of our resources in this EC2 region.""" |
|
541 | 545 | ec2resource = c.ec2resource |
|
542 | 546 | iamresource = c.iamresource |
|
543 | 547 | |
|
544 | 548 | terminate_ec2_instances(ec2resource, prefix=prefix) |
|
545 | 549 | |
|
546 | 550 | for image in ec2resource.images.filter(Owners=['self']): |
|
547 | 551 | if image.name.startswith(prefix): |
|
548 | 552 | remove_ami(ec2resource, image) |
|
549 | 553 | |
|
550 | 554 | for group in ec2resource.security_groups.all(): |
|
551 | 555 | if group.group_name.startswith(prefix): |
|
552 | 556 | print('removing security group %s' % group.group_name) |
|
553 | 557 | group.delete() |
|
554 | 558 | |
|
555 | 559 | for profile in iamresource.instance_profiles.all(): |
|
556 | 560 | if profile.name.startswith(prefix): |
|
557 | 561 | delete_instance_profile(profile) |
|
558 | 562 | |
|
559 | 563 | for role in iamresource.roles.all(): |
|
560 | 564 | if role.name.startswith(prefix): |
|
561 | 565 | for p in role.attached_policies.all(): |
|
562 | 566 | print('detaching policy %s from %s' % (p.arn, role.name)) |
|
563 | 567 | role.detach_policy(PolicyArn=p.arn) |
|
564 | 568 | |
|
565 | 569 | print('removing role %s' % role.name) |
|
566 | 570 | role.delete() |
|
567 | 571 | |
|
568 | 572 | |
|
569 | 573 | def wait_for_ip_addresses(instances): |
|
570 | 574 | """Wait for the public IP addresses of an iterable of instances.""" |
|
571 | 575 | for instance in instances: |
|
572 | 576 | while True: |
|
573 | 577 | if not instance.public_ip_address: |
|
574 | 578 | time.sleep(2) |
|
575 | 579 | instance.reload() |
|
576 | 580 | continue |
|
577 | 581 | |
|
578 | 582 | print( |
|
579 | 583 | 'public IP address for %s: %s' |
|
580 | 584 | % (instance.id, instance.public_ip_address) |
|
581 | 585 | ) |
|
582 | 586 | break |
|
583 | 587 | |
|
584 | 588 | |
|
585 | 589 | def remove_ami(ec2resource, image): |
|
586 | 590 | """Remove an AMI and its underlying snapshots.""" |
|
587 | 591 | snapshots = [] |
|
588 | 592 | |
|
589 | 593 | for device in image.block_device_mappings: |
|
590 | 594 | if 'Ebs' in device: |
|
591 | 595 | snapshots.append(ec2resource.Snapshot(device['Ebs']['SnapshotId'])) |
|
592 | 596 | |
|
593 | 597 | print('deregistering %s' % image.id) |
|
594 | 598 | image.deregister() |
|
595 | 599 | |
|
596 | 600 | for snapshot in snapshots: |
|
597 | 601 | print('deleting snapshot %s' % snapshot.id) |
|
598 | 602 | snapshot.delete() |
|
599 | 603 | |
|
600 | 604 | |
|
601 | 605 | def wait_for_ssm(ssmclient, instances): |
|
602 | 606 | """Wait for SSM to come online for an iterable of instance IDs.""" |
|
603 | 607 | while True: |
|
604 | 608 | res = ssmclient.describe_instance_information( |
|
605 | 609 | Filters=[ |
|
606 | 610 | {'Key': 'InstanceIds', 'Values': [i.id for i in instances],}, |
|
607 | 611 | ], |
|
608 | 612 | ) |
|
609 | 613 | |
|
610 | 614 | available = len(res['InstanceInformationList']) |
|
611 | 615 | wanted = len(instances) |
|
612 | 616 | |
|
613 | 617 | print('%d/%d instances available in SSM' % (available, wanted)) |
|
614 | 618 | |
|
615 | 619 | if available == wanted: |
|
616 | 620 | return |
|
617 | 621 | |
|
618 | 622 | time.sleep(2) |
|
619 | 623 | |
|
620 | 624 | |
|
621 | 625 | def run_ssm_command(ssmclient, instances, document_name, parameters): |
|
622 | 626 | """Run a PowerShell script on an EC2 instance.""" |
|
623 | 627 | |
|
624 | 628 | res = ssmclient.send_command( |
|
625 | 629 | InstanceIds=[i.id for i in instances], |
|
626 | 630 | DocumentName=document_name, |
|
627 | 631 | Parameters=parameters, |
|
628 | 632 | CloudWatchOutputConfig={'CloudWatchOutputEnabled': True,}, |
|
629 | 633 | ) |
|
630 | 634 | |
|
631 | 635 | command_id = res['Command']['CommandId'] |
|
632 | 636 | |
|
633 | 637 | for instance in instances: |
|
634 | 638 | while True: |
|
635 | 639 | try: |
|
636 | 640 | res = ssmclient.get_command_invocation( |
|
637 | 641 | CommandId=command_id, InstanceId=instance.id, |
|
638 | 642 | ) |
|
639 | 643 | except botocore.exceptions.ClientError as e: |
|
640 | 644 | if e.response['Error']['Code'] == 'InvocationDoesNotExist': |
|
641 | 645 | print('could not find SSM command invocation; waiting') |
|
642 | 646 | time.sleep(1) |
|
643 | 647 | continue |
|
644 | 648 | else: |
|
645 | 649 | raise |
|
646 | 650 | |
|
647 | 651 | if res['Status'] == 'Success': |
|
648 | 652 | break |
|
649 | 653 | elif res['Status'] in ('Pending', 'InProgress', 'Delayed'): |
|
650 | 654 | time.sleep(2) |
|
651 | 655 | else: |
|
652 | 656 | raise Exception( |
|
653 | 657 | 'command failed on %s: %s' % (instance.id, res['Status']) |
|
654 | 658 | ) |
|
655 | 659 | |
|
656 | 660 | |
|
657 | 661 | @contextlib.contextmanager |
|
658 | 662 | def temporary_ec2_instances(ec2resource, config): |
|
659 | 663 | """Create temporary EC2 instances. |
|
660 | 664 | |
|
661 | 665 | This is a proxy to ``ec2client.run_instances(**config)`` that takes care of |
|
662 | 666 | managing the lifecycle of the instances. |
|
663 | 667 | |
|
664 | 668 | When the context manager exits, the instances are terminated. |
|
665 | 669 | |
|
666 | 670 | The context manager evaluates to the list of data structures |
|
667 | 671 | describing each created instance. The instances may not be available |
|
668 | 672 | for work immediately: it is up to the caller to wait for the instance |
|
669 | 673 | to start responding. |
|
670 | 674 | """ |
|
671 | 675 | |
|
672 | 676 | ids = None |
|
673 | 677 | |
|
674 | 678 | try: |
|
675 | 679 | res = ec2resource.create_instances(**config) |
|
676 | 680 | |
|
677 | 681 | ids = [i.id for i in res] |
|
678 | 682 | print('started instances: %s' % ' '.join(ids)) |
|
679 | 683 | |
|
680 | 684 | yield res |
|
681 | 685 | finally: |
|
682 | 686 | if ids: |
|
683 | 687 | print('terminating instances: %s' % ' '.join(ids)) |
|
684 | 688 | for instance in res: |
|
685 | 689 | instance.terminate() |
|
686 | 690 | print('terminated %d instances' % len(ids)) |
|
687 | 691 | |
|
688 | 692 | |
|
689 | 693 | @contextlib.contextmanager |
|
690 | 694 | def create_temp_windows_ec2_instances( |
|
691 | 695 | c: AWSConnection, config, bootstrap: bool = False |
|
692 | 696 | ): |
|
693 | 697 | """Create temporary Windows EC2 instances. |
|
694 | 698 | |
|
695 | 699 | This is a higher-level wrapper around ``create_temp_ec2_instances()`` that |
|
696 | 700 | configures the Windows instance for Windows Remote Management. The emitted |
|
697 | 701 | instances will have a ``winrm_client`` attribute containing a |
|
698 | 702 | ``pypsrp.client.Client`` instance bound to the instance. |
|
699 | 703 | """ |
|
700 | 704 | if 'IamInstanceProfile' in config: |
|
701 | 705 | raise ValueError('IamInstanceProfile cannot be provided in config') |
|
702 | 706 | if 'UserData' in config: |
|
703 | 707 | raise ValueError('UserData cannot be provided in config') |
|
704 | 708 | |
|
705 | 709 | password = c.automation.default_password() |
|
706 | 710 | |
|
707 | 711 | config = copy.deepcopy(config) |
|
708 | 712 | config['IamInstanceProfile'] = { |
|
709 | 713 | 'Name': 'hg-ephemeral-ec2-1', |
|
710 | 714 | } |
|
711 | 715 | config.setdefault('TagSpecifications', []).append( |
|
712 | 716 | { |
|
713 | 717 | 'ResourceType': 'instance', |
|
714 | 718 | 'Tags': [{'Key': 'Name', 'Value': 'hg-temp-windows'}], |
|
715 | 719 | } |
|
716 | 720 | ) |
|
717 | 721 | |
|
718 | 722 | if bootstrap: |
|
719 | 723 | config['UserData'] = WINDOWS_USER_DATA % password |
|
720 | 724 | |
|
721 | 725 | with temporary_ec2_instances(c.ec2resource, config) as instances: |
|
722 | 726 | wait_for_ip_addresses(instances) |
|
723 | 727 | |
|
724 | 728 | print('waiting for Windows Remote Management service...') |
|
725 | 729 | |
|
726 | 730 | for instance in instances: |
|
727 | 731 | client = wait_for_winrm( |
|
728 | 732 | instance.public_ip_address, 'Administrator', password |
|
729 | 733 | ) |
|
730 | 734 | print('established WinRM connection to %s' % instance.id) |
|
731 | 735 | instance.winrm_client = client |
|
732 | 736 | |
|
733 | 737 | yield instances |
|
734 | 738 | |
|
735 | 739 | |
|
736 | 740 | def resolve_fingerprint(fingerprint): |
|
737 | 741 | fingerprint = json.dumps(fingerprint, sort_keys=True) |
|
738 | 742 | return hashlib.sha256(fingerprint.encode('utf-8')).hexdigest() |
|
739 | 743 | |
|
740 | 744 | |
|
741 | 745 | def find_and_reconcile_image(ec2resource, name, fingerprint): |
|
742 | 746 | """Attempt to find an existing EC2 AMI with a name and fingerprint. |
|
743 | 747 | |
|
744 | 748 | If an image with the specified fingerprint is found, it is returned. |
|
745 | 749 | Otherwise None is returned. |
|
746 | 750 | |
|
747 | 751 | Existing images for the specified name that don't have the specified |
|
748 | 752 | fingerprint or are missing required metadata or deleted. |
|
749 | 753 | """ |
|
750 | 754 | # Find existing AMIs with this name and delete the ones that are invalid. |
|
751 | 755 | # Store a reference to a good image so it can be returned one the |
|
752 | 756 | # image state is reconciled. |
|
753 | 757 | images = ec2resource.images.filter( |
|
754 | 758 | Filters=[{'Name': 'name', 'Values': [name]}] |
|
755 | 759 | ) |
|
756 | 760 | |
|
757 | 761 | existing_image = None |
|
758 | 762 | |
|
759 | 763 | for image in images: |
|
760 | 764 | if image.tags is None: |
|
761 | 765 | print( |
|
762 | 766 | 'image %s for %s lacks required tags; removing' |
|
763 | 767 | % (image.id, image.name) |
|
764 | 768 | ) |
|
765 | 769 | remove_ami(ec2resource, image) |
|
766 | 770 | else: |
|
767 | 771 | tags = {t['Key']: t['Value'] for t in image.tags} |
|
768 | 772 | |
|
769 | 773 | if tags.get('HGIMAGEFINGERPRINT') == fingerprint: |
|
770 | 774 | existing_image = image |
|
771 | 775 | else: |
|
772 | 776 | print( |
|
773 | 777 | 'image %s for %s has wrong fingerprint; removing' |
|
774 | 778 | % (image.id, image.name) |
|
775 | 779 | ) |
|
776 | 780 | remove_ami(ec2resource, image) |
|
777 | 781 | |
|
778 | 782 | return existing_image |
|
779 | 783 | |
|
780 | 784 | |
|
781 | 785 | def create_ami_from_instance( |
|
782 | 786 | ec2client, instance, name, description, fingerprint |
|
783 | 787 | ): |
|
784 | 788 | """Create an AMI from a running instance. |
|
785 | 789 | |
|
786 | 790 | Returns the ``ec2resource.Image`` representing the created AMI. |
|
787 | 791 | """ |
|
788 | 792 | instance.stop() |
|
789 | 793 | |
|
790 | 794 | ec2client.get_waiter('instance_stopped').wait( |
|
791 | 795 | InstanceIds=[instance.id], WaiterConfig={'Delay': 5,} |
|
792 | 796 | ) |
|
793 | 797 | print('%s is stopped' % instance.id) |
|
794 | 798 | |
|
795 | 799 | image = instance.create_image(Name=name, Description=description,) |
|
796 | 800 | |
|
797 | 801 | image.create_tags( |
|
798 | 802 | Tags=[{'Key': 'HGIMAGEFINGERPRINT', 'Value': fingerprint,},] |
|
799 | 803 | ) |
|
800 | 804 | |
|
801 | 805 | print('waiting for image %s' % image.id) |
|
802 | 806 | |
|
803 | 807 | ec2client.get_waiter('image_available').wait(ImageIds=[image.id],) |
|
804 | 808 | |
|
805 | 809 | print('image %s available as %s' % (image.id, image.name)) |
|
806 | 810 | |
|
807 | 811 | return image |
|
808 | 812 | |
|
809 | 813 | |
|
810 | 814 | def ensure_linux_dev_ami(c: AWSConnection, distro='debian10', prefix='hg-'): |
|
811 | 815 | """Ensures a Linux development AMI is available and up-to-date. |
|
812 | 816 | |
|
813 | 817 | Returns an ``ec2.Image`` of either an existing AMI or a newly-built one. |
|
814 | 818 | """ |
|
815 | 819 | ec2client = c.ec2client |
|
816 | 820 | ec2resource = c.ec2resource |
|
817 | 821 | |
|
818 | 822 | name = '%s%s-%s' % (prefix, 'linux-dev', distro) |
|
819 | 823 | |
|
820 | 824 | if distro == 'debian9': |
|
821 | 825 | image = find_image( |
|
822 | 826 | ec2resource, |
|
823 | 827 | DEBIAN_ACCOUNT_ID, |
|
824 | 828 | 'debian-stretch-hvm-x86_64-gp2-2019-09-08-17994', |
|
825 | 829 | ) |
|
826 | 830 | ssh_username = 'admin' |
|
827 | 831 | elif distro == 'debian10': |
|
828 | 832 | image = find_image( |
|
829 | 833 | ec2resource, DEBIAN_ACCOUNT_ID_2, 'debian-10-amd64-20190909-10', |
|
830 | 834 | ) |
|
831 | 835 | ssh_username = 'admin' |
|
832 | 836 | elif distro == 'ubuntu18.04': |
|
833 | 837 | image = find_image( |
|
834 | 838 | ec2resource, |
|
835 | 839 | UBUNTU_ACCOUNT_ID, |
|
836 | 840 | 'ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-20190918', |
|
837 | 841 | ) |
|
838 | 842 | ssh_username = 'ubuntu' |
|
839 | 843 | elif distro == 'ubuntu19.04': |
|
840 | 844 | image = find_image( |
|
841 | 845 | ec2resource, |
|
842 | 846 | UBUNTU_ACCOUNT_ID, |
|
843 | 847 | 'ubuntu/images/hvm-ssd/ubuntu-disco-19.04-amd64-server-20190918', |
|
844 | 848 | ) |
|
845 | 849 | ssh_username = 'ubuntu' |
|
846 | 850 | else: |
|
847 | 851 | raise ValueError('unsupported Linux distro: %s' % distro) |
|
848 | 852 | |
|
849 | 853 | config = { |
|
850 | 854 | 'BlockDeviceMappings': [ |
|
851 | 855 | { |
|
852 | 856 | 'DeviceName': image.block_device_mappings[0]['DeviceName'], |
|
853 | 857 | 'Ebs': { |
|
854 | 858 | 'DeleteOnTermination': True, |
|
855 | 859 | 'VolumeSize': 10, |
|
856 | 860 | 'VolumeType': 'gp2', |
|
857 | 861 | }, |
|
858 | 862 | }, |
|
859 | 863 | ], |
|
860 | 864 | 'EbsOptimized': True, |
|
861 | 865 | 'ImageId': image.id, |
|
862 | 866 | 'InstanceInitiatedShutdownBehavior': 'stop', |
|
863 | 867 | # 8 VCPUs for compiling Python. |
|
864 | 868 | 'InstanceType': 't3.2xlarge', |
|
865 | 869 | 'KeyName': '%sautomation' % prefix, |
|
866 | 870 | 'MaxCount': 1, |
|
867 | 871 | 'MinCount': 1, |
|
868 | 872 | 'SecurityGroupIds': [c.security_groups['linux-dev-1'].id], |
|
869 | 873 | } |
|
870 | 874 | |
|
871 | 875 | requirements2_path = ( |
|
872 | 876 | pathlib.Path(__file__).parent.parent / 'linux-requirements-py2.txt' |
|
873 | 877 | ) |
|
874 | 878 | requirements3_path = ( |
|
875 | 879 | pathlib.Path(__file__).parent.parent / 'linux-requirements-py3.txt' |
|
876 | 880 | ) |
|
877 | 881 | with requirements2_path.open('r', encoding='utf-8') as fh: |
|
878 | 882 | requirements2 = fh.read() |
|
879 | 883 | with requirements3_path.open('r', encoding='utf-8') as fh: |
|
880 | 884 | requirements3 = fh.read() |
|
881 | 885 | |
|
882 | 886 | # Compute a deterministic fingerprint to determine whether image needs to |
|
883 | 887 | # be regenerated. |
|
884 | 888 | fingerprint = resolve_fingerprint( |
|
885 | 889 | { |
|
886 | 890 | 'instance_config': config, |
|
887 | 891 | 'bootstrap_script': BOOTSTRAP_DEBIAN, |
|
888 | 892 | 'requirements_py2': requirements2, |
|
889 | 893 | 'requirements_py3': requirements3, |
|
890 | 894 | } |
|
891 | 895 | ) |
|
892 | 896 | |
|
893 | 897 | existing_image = find_and_reconcile_image(ec2resource, name, fingerprint) |
|
894 | 898 | |
|
895 | 899 | if existing_image: |
|
896 | 900 | return existing_image |
|
897 | 901 | |
|
898 | 902 | print('no suitable %s image found; creating one...' % name) |
|
899 | 903 | |
|
900 | 904 | with temporary_ec2_instances(ec2resource, config) as instances: |
|
901 | 905 | wait_for_ip_addresses(instances) |
|
902 | 906 | |
|
903 | 907 | instance = instances[0] |
|
904 | 908 | |
|
905 | 909 | client = wait_for_ssh( |
|
906 | 910 | instance.public_ip_address, |
|
907 | 911 | 22, |
|
908 | 912 | username=ssh_username, |
|
909 | 913 | key_filename=str(c.key_pair_path_private('automation')), |
|
910 | 914 | ) |
|
911 | 915 | |
|
912 | 916 | home = '/home/%s' % ssh_username |
|
913 | 917 | |
|
914 | 918 | with client: |
|
915 | 919 | print('connecting to SSH server') |
|
916 | 920 | sftp = client.open_sftp() |
|
917 | 921 | |
|
918 | 922 | print('uploading bootstrap files') |
|
919 | 923 | with sftp.open('%s/bootstrap' % home, 'wb') as fh: |
|
920 | 924 | fh.write(BOOTSTRAP_DEBIAN) |
|
921 | 925 | fh.chmod(0o0700) |
|
922 | 926 | |
|
923 | 927 | with sftp.open('%s/requirements-py2.txt' % home, 'wb') as fh: |
|
924 | 928 | fh.write(requirements2) |
|
925 | 929 | fh.chmod(0o0700) |
|
926 | 930 | |
|
927 | 931 | with sftp.open('%s/requirements-py3.txt' % home, 'wb') as fh: |
|
928 | 932 | fh.write(requirements3) |
|
929 | 933 | fh.chmod(0o0700) |
|
930 | 934 | |
|
931 | 935 | print('executing bootstrap') |
|
932 | 936 | chan, stdin, stdout = ssh_exec_command( |
|
933 | 937 | client, '%s/bootstrap' % home |
|
934 | 938 | ) |
|
935 | 939 | stdin.close() |
|
936 | 940 | |
|
937 | 941 | for line in stdout: |
|
938 | 942 | print(line, end='') |
|
939 | 943 | |
|
940 | 944 | res = chan.recv_exit_status() |
|
941 | 945 | if res: |
|
942 | 946 | raise Exception('non-0 exit from bootstrap: %d' % res) |
|
943 | 947 | |
|
944 | 948 | print( |
|
945 | 949 | 'bootstrap completed; stopping %s to create %s' |
|
946 | 950 | % (instance.id, name) |
|
947 | 951 | ) |
|
948 | 952 | |
|
949 | 953 | return create_ami_from_instance( |
|
950 | 954 | ec2client, |
|
951 | 955 | instance, |
|
952 | 956 | name, |
|
953 | 957 | 'Mercurial Linux development environment', |
|
954 | 958 | fingerprint, |
|
955 | 959 | ) |
|
956 | 960 | |
|
957 | 961 | |
|
958 | 962 | @contextlib.contextmanager |
|
959 | 963 | def temporary_linux_dev_instances( |
|
960 | 964 | c: AWSConnection, |
|
961 | 965 | image, |
|
962 | 966 | instance_type, |
|
963 | 967 | prefix='hg-', |
|
964 | 968 | ensure_extra_volume=False, |
|
965 | 969 | ): |
|
966 | 970 | """Create temporary Linux development EC2 instances. |
|
967 | 971 | |
|
968 | 972 | Context manager resolves to a list of ``ec2.Instance`` that were created |
|
969 | 973 | and are running. |
|
970 | 974 | |
|
971 | 975 | ``ensure_extra_volume`` can be set to ``True`` to require that instances |
|
972 | 976 | have a 2nd storage volume available other than the primary AMI volume. |
|
973 | 977 | For instance types with instance storage, this does nothing special. |
|
974 | 978 | But for instance types without instance storage, an additional EBS volume |
|
975 | 979 | will be added to the instance. |
|
976 | 980 | |
|
977 | 981 | Instances have an ``ssh_client`` attribute containing a paramiko SSHClient |
|
978 | 982 | instance bound to the instance. |
|
979 | 983 | |
|
980 | 984 | Instances have an ``ssh_private_key_path`` attributing containing the |
|
981 | 985 | str path to the SSH private key to connect to the instance. |
|
982 | 986 | """ |
|
983 | 987 | |
|
984 | 988 | block_device_mappings = [ |
|
985 | 989 | { |
|
986 | 990 | 'DeviceName': image.block_device_mappings[0]['DeviceName'], |
|
987 | 991 | 'Ebs': { |
|
988 | 992 | 'DeleteOnTermination': True, |
|
989 | 993 | 'VolumeSize': 12, |
|
990 | 994 | 'VolumeType': 'gp2', |
|
991 | 995 | }, |
|
992 | 996 | } |
|
993 | 997 | ] |
|
994 | 998 | |
|
995 | 999 | # This is not an exhaustive list of instance types having instance storage. |
|
996 | 1000 | # But |
|
997 | 1001 | if ensure_extra_volume and not instance_type.startswith( |
|
998 | 1002 | tuple(INSTANCE_TYPES_WITH_STORAGE) |
|
999 | 1003 | ): |
|
1000 | 1004 | main_device = block_device_mappings[0]['DeviceName'] |
|
1001 | 1005 | |
|
1002 | 1006 | if main_device == 'xvda': |
|
1003 | 1007 | second_device = 'xvdb' |
|
1004 | 1008 | elif main_device == '/dev/sda1': |
|
1005 | 1009 | second_device = '/dev/sdb' |
|
1006 | 1010 | else: |
|
1007 | 1011 | raise ValueError( |
|
1008 | 1012 | 'unhandled primary EBS device name: %s' % main_device |
|
1009 | 1013 | ) |
|
1010 | 1014 | |
|
1011 | 1015 | block_device_mappings.append( |
|
1012 | 1016 | { |
|
1013 | 1017 | 'DeviceName': second_device, |
|
1014 | 1018 | 'Ebs': { |
|
1015 | 1019 | 'DeleteOnTermination': True, |
|
1016 | 1020 | 'VolumeSize': 8, |
|
1017 | 1021 | 'VolumeType': 'gp2', |
|
1018 | 1022 | }, |
|
1019 | 1023 | } |
|
1020 | 1024 | ) |
|
1021 | 1025 | |
|
1022 | 1026 | config = { |
|
1023 | 1027 | 'BlockDeviceMappings': block_device_mappings, |
|
1024 | 1028 | 'EbsOptimized': True, |
|
1025 | 1029 | 'ImageId': image.id, |
|
1026 | 1030 | 'InstanceInitiatedShutdownBehavior': 'terminate', |
|
1027 | 1031 | 'InstanceType': instance_type, |
|
1028 | 1032 | 'KeyName': '%sautomation' % prefix, |
|
1029 | 1033 | 'MaxCount': 1, |
|
1030 | 1034 | 'MinCount': 1, |
|
1031 | 1035 | 'SecurityGroupIds': [c.security_groups['linux-dev-1'].id], |
|
1032 | 1036 | } |
|
1033 | 1037 | |
|
1034 | 1038 | with temporary_ec2_instances(c.ec2resource, config) as instances: |
|
1035 | 1039 | wait_for_ip_addresses(instances) |
|
1036 | 1040 | |
|
1037 | 1041 | ssh_private_key_path = str(c.key_pair_path_private('automation')) |
|
1038 | 1042 | |
|
1039 | 1043 | for instance in instances: |
|
1040 | 1044 | client = wait_for_ssh( |
|
1041 | 1045 | instance.public_ip_address, |
|
1042 | 1046 | 22, |
|
1043 | 1047 | username='hg', |
|
1044 | 1048 | key_filename=ssh_private_key_path, |
|
1045 | 1049 | ) |
|
1046 | 1050 | |
|
1047 | 1051 | instance.ssh_client = client |
|
1048 | 1052 | instance.ssh_private_key_path = ssh_private_key_path |
|
1049 | 1053 | |
|
1050 | 1054 | try: |
|
1051 | 1055 | yield instances |
|
1052 | 1056 | finally: |
|
1053 | 1057 | for instance in instances: |
|
1054 | 1058 | instance.ssh_client.close() |
|
1055 | 1059 | |
|
1056 | 1060 | |
|
1057 | 1061 | def ensure_windows_dev_ami( |
|
1058 | 1062 | c: AWSConnection, prefix='hg-', base_image_name=WINDOWS_BASE_IMAGE_NAME |
|
1059 | 1063 | ): |
|
1060 | 1064 | """Ensure Windows Development AMI is available and up-to-date. |
|
1061 | 1065 | |
|
1062 | 1066 | If necessary, a modern AMI will be built by starting a temporary EC2 |
|
1063 | 1067 | instance and bootstrapping it. |
|
1064 | 1068 | |
|
1065 | 1069 | Obsolete AMIs will be deleted so there is only a single AMI having the |
|
1066 | 1070 | desired name. |
|
1067 | 1071 | |
|
1068 | 1072 | Returns an ``ec2.Image`` of either an existing AMI or a newly-built |
|
1069 | 1073 | one. |
|
1070 | 1074 | """ |
|
1071 | 1075 | ec2client = c.ec2client |
|
1072 | 1076 | ec2resource = c.ec2resource |
|
1073 | 1077 | ssmclient = c.session.client('ssm') |
|
1074 | 1078 | |
|
1075 | 1079 | name = '%s%s' % (prefix, 'windows-dev') |
|
1076 | 1080 | |
|
1077 | 1081 | image = find_image(ec2resource, AMAZON_ACCOUNT_ID, base_image_name) |
|
1078 | 1082 | |
|
1079 | 1083 | config = { |
|
1080 | 1084 | 'BlockDeviceMappings': [ |
|
1081 | 1085 | { |
|
1082 | 1086 | 'DeviceName': '/dev/sda1', |
|
1083 | 1087 | 'Ebs': { |
|
1084 | 1088 | 'DeleteOnTermination': True, |
|
1085 | 1089 | 'VolumeSize': 32, |
|
1086 | 1090 | 'VolumeType': 'gp2', |
|
1087 | 1091 | }, |
|
1088 | 1092 | } |
|
1089 | 1093 | ], |
|
1090 | 1094 | 'ImageId': image.id, |
|
1091 | 1095 | 'InstanceInitiatedShutdownBehavior': 'stop', |
|
1092 | 1096 | 'InstanceType': 't3.medium', |
|
1093 | 1097 | 'KeyName': '%sautomation' % prefix, |
|
1094 | 1098 | 'MaxCount': 1, |
|
1095 | 1099 | 'MinCount': 1, |
|
1096 | 1100 | 'SecurityGroupIds': [c.security_groups['windows-dev-1'].id], |
|
1097 | 1101 | } |
|
1098 | 1102 | |
|
1099 | 1103 | commands = [ |
|
1100 | 1104 | # Need to start the service so sshd_config is generated. |
|
1101 | 1105 | 'Start-Service sshd', |
|
1102 | 1106 | 'Write-Output "modifying sshd_config"', |
|
1103 | 1107 | r'$content = Get-Content C:\ProgramData\ssh\sshd_config', |
|
1104 | 1108 | '$content = $content -replace "Match Group administrators","" -replace "AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys",""', |
|
1105 | 1109 | r'$content | Set-Content C:\ProgramData\ssh\sshd_config', |
|
1106 | 1110 | 'Import-Module OpenSSHUtils', |
|
1107 | 1111 | r'Repair-SshdConfigPermission C:\ProgramData\ssh\sshd_config -Confirm:$false', |
|
1108 | 1112 | 'Restart-Service sshd', |
|
1109 | 1113 | 'Write-Output "installing OpenSSL client"', |
|
1110 | 1114 | 'Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0', |
|
1111 | 1115 | 'Set-Service -Name sshd -StartupType "Automatic"', |
|
1112 | 1116 | 'Write-Output "OpenSSH server running"', |
|
1113 | 1117 | ] |
|
1114 | 1118 | |
|
1115 | 1119 | with INSTALL_WINDOWS_DEPENDENCIES.open('r', encoding='utf-8') as fh: |
|
1116 | 1120 | commands.extend(l.rstrip() for l in fh) |
|
1117 | 1121 | |
|
1118 | 1122 | # Schedule run of EC2Launch on next boot. This ensures that UserData |
|
1119 | 1123 | # is executed. |
|
1120 | 1124 | # We disable setComputerName because it forces a reboot. |
|
1121 | 1125 | # We set an explicit admin password because this causes UserData to run |
|
1122 | 1126 | # as Administrator instead of System. |
|
1123 | 1127 | commands.extend( |
|
1124 | 1128 | [ |
|
1125 | 1129 | r'''Set-Content -Path C:\ProgramData\Amazon\EC2-Windows\Launch\Config\LaunchConfig.json ''' |
|
1126 | 1130 | r'''-Value '{"setComputerName": false, "setWallpaper": true, "addDnsSuffixList": true, ''' |
|
1127 | 1131 | r'''"extendBootVolumeSize": true, "handleUserData": true, ''' |
|
1128 | 1132 | r'''"adminPasswordType": "Specify", "adminPassword": "%s"}' ''' |
|
1129 | 1133 | % c.automation.default_password(), |
|
1130 | 1134 | r'C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 ' |
|
1131 | 1135 | r'–Schedule', |
|
1132 | 1136 | ] |
|
1133 | 1137 | ) |
|
1134 | 1138 | |
|
1135 | 1139 | # Disable Windows Defender when bootstrapping because it just slows |
|
1136 | 1140 | # things down. |
|
1137 | 1141 | commands.insert(0, 'Set-MpPreference -DisableRealtimeMonitoring $true') |
|
1138 | 1142 | commands.append('Set-MpPreference -DisableRealtimeMonitoring $false') |
|
1139 | 1143 | |
|
1140 | 1144 | # Compute a deterministic fingerprint to determine whether image needs |
|
1141 | 1145 | # to be regenerated. |
|
1142 | 1146 | fingerprint = resolve_fingerprint( |
|
1143 | 1147 | { |
|
1144 | 1148 | 'instance_config': config, |
|
1145 | 1149 | 'user_data': WINDOWS_USER_DATA, |
|
1146 | 1150 | 'initial_bootstrap': WINDOWS_BOOTSTRAP_POWERSHELL, |
|
1147 | 1151 | 'bootstrap_commands': commands, |
|
1148 | 1152 | 'base_image_name': base_image_name, |
|
1149 | 1153 | } |
|
1150 | 1154 | ) |
|
1151 | 1155 | |
|
1152 | 1156 | existing_image = find_and_reconcile_image(ec2resource, name, fingerprint) |
|
1153 | 1157 | |
|
1154 | 1158 | if existing_image: |
|
1155 | 1159 | return existing_image |
|
1156 | 1160 | |
|
1157 | 1161 | print('no suitable Windows development image found; creating one...') |
|
1158 | 1162 | |
|
1159 | 1163 | with create_temp_windows_ec2_instances( |
|
1160 | 1164 | c, config, bootstrap=True |
|
1161 | 1165 | ) as instances: |
|
1162 | 1166 | assert len(instances) == 1 |
|
1163 | 1167 | instance = instances[0] |
|
1164 | 1168 | |
|
1165 | 1169 | wait_for_ssm(ssmclient, [instance]) |
|
1166 | 1170 | |
|
1167 | 1171 | # On first boot, install various Windows updates. |
|
1168 | 1172 | # We would ideally use PowerShell Remoting for this. However, there are |
|
1169 | 1173 | # trust issues that make it difficult to invoke Windows Update |
|
1170 | 1174 | # remotely. So we use SSM, which has a mechanism for running Windows |
|
1171 | 1175 | # Update. |
|
1172 | 1176 | print('installing Windows features...') |
|
1173 | 1177 | run_ssm_command( |
|
1174 | 1178 | ssmclient, |
|
1175 | 1179 | [instance], |
|
1176 | 1180 | 'AWS-RunPowerShellScript', |
|
1177 | 1181 | {'commands': WINDOWS_BOOTSTRAP_POWERSHELL.split('\n'),}, |
|
1178 | 1182 | ) |
|
1179 | 1183 | |
|
1180 | 1184 | # Reboot so all updates are fully applied. |
|
1181 | 1185 | # |
|
1182 | 1186 | # We don't use instance.reboot() here because it is asynchronous and |
|
1183 | 1187 | # we don't know when exactly the instance has rebooted. It could take |
|
1184 | 1188 | # a while to stop and we may start trying to interact with the instance |
|
1185 | 1189 | # before it has rebooted. |
|
1186 | 1190 | print('rebooting instance %s' % instance.id) |
|
1187 | 1191 | instance.stop() |
|
1188 | 1192 | ec2client.get_waiter('instance_stopped').wait( |
|
1189 | 1193 | InstanceIds=[instance.id], WaiterConfig={'Delay': 5,} |
|
1190 | 1194 | ) |
|
1191 | 1195 | |
|
1192 | 1196 | instance.start() |
|
1193 | 1197 | wait_for_ip_addresses([instance]) |
|
1194 | 1198 | |
|
1195 | 1199 | # There is a race condition here between the User Data PS script running |
|
1196 | 1200 | # and us connecting to WinRM. This can manifest as |
|
1197 | 1201 | # "AuthorizationManager check failed" failures during run_powershell(). |
|
1198 | 1202 | # TODO figure out a workaround. |
|
1199 | 1203 | |
|
1200 | 1204 | print('waiting for Windows Remote Management to come back...') |
|
1201 | 1205 | client = wait_for_winrm( |
|
1202 | 1206 | instance.public_ip_address, |
|
1203 | 1207 | 'Administrator', |
|
1204 | 1208 | c.automation.default_password(), |
|
1205 | 1209 | ) |
|
1206 | 1210 | print('established WinRM connection to %s' % instance.id) |
|
1207 | 1211 | instance.winrm_client = client |
|
1208 | 1212 | |
|
1209 | 1213 | print('bootstrapping instance...') |
|
1210 | 1214 | run_powershell(instance.winrm_client, '\n'.join(commands)) |
|
1211 | 1215 | |
|
1212 | 1216 | print('bootstrap completed; stopping %s to create image' % instance.id) |
|
1213 | 1217 | return create_ami_from_instance( |
|
1214 | 1218 | ec2client, |
|
1215 | 1219 | instance, |
|
1216 | 1220 | name, |
|
1217 | 1221 | 'Mercurial Windows development environment', |
|
1218 | 1222 | fingerprint, |
|
1219 | 1223 | ) |
|
1220 | 1224 | |
|
1221 | 1225 | |
|
1222 | 1226 | @contextlib.contextmanager |
|
1223 | 1227 | def temporary_windows_dev_instances( |
|
1224 | 1228 | c: AWSConnection, |
|
1225 | 1229 | image, |
|
1226 | 1230 | instance_type, |
|
1227 | 1231 | prefix='hg-', |
|
1228 | 1232 | disable_antivirus=False, |
|
1229 | 1233 | ): |
|
1230 | 1234 | """Create a temporary Windows development EC2 instance. |
|
1231 | 1235 | |
|
1232 | 1236 | Context manager resolves to the list of ``EC2.Instance`` that were created. |
|
1233 | 1237 | """ |
|
1234 | 1238 | config = { |
|
1235 | 1239 | 'BlockDeviceMappings': [ |
|
1236 | 1240 | { |
|
1237 | 1241 | 'DeviceName': '/dev/sda1', |
|
1238 | 1242 | 'Ebs': { |
|
1239 | 1243 | 'DeleteOnTermination': True, |
|
1240 | 1244 | 'VolumeSize': 32, |
|
1241 | 1245 | 'VolumeType': 'gp2', |
|
1242 | 1246 | }, |
|
1243 | 1247 | } |
|
1244 | 1248 | ], |
|
1245 | 1249 | 'ImageId': image.id, |
|
1246 | 1250 | 'InstanceInitiatedShutdownBehavior': 'stop', |
|
1247 | 1251 | 'InstanceType': instance_type, |
|
1248 | 1252 | 'KeyName': '%sautomation' % prefix, |
|
1249 | 1253 | 'MaxCount': 1, |
|
1250 | 1254 | 'MinCount': 1, |
|
1251 | 1255 | 'SecurityGroupIds': [c.security_groups['windows-dev-1'].id], |
|
1252 | 1256 | } |
|
1253 | 1257 | |
|
1254 | 1258 | with create_temp_windows_ec2_instances(c, config) as instances: |
|
1255 | 1259 | if disable_antivirus: |
|
1256 | 1260 | for instance in instances: |
|
1257 | 1261 | run_powershell( |
|
1258 | 1262 | instance.winrm_client, |
|
1259 | 1263 | 'Set-MpPreference -DisableRealtimeMonitoring $true', |
|
1260 | 1264 | ) |
|
1261 | 1265 | |
|
1262 | 1266 | yield instances |
@@ -1,594 +1,595 b'' | |||
|
1 | 1 | # linux.py - Linux specific automation functionality |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.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 | # no-check-code because Python 3 native. |
|
9 | 9 | |
|
10 | 10 | import os |
|
11 | 11 | import pathlib |
|
12 | 12 | import shlex |
|
13 | 13 | import subprocess |
|
14 | 14 | import tempfile |
|
15 | 15 | |
|
16 | 16 | from .ssh import exec_command |
|
17 | 17 | |
|
18 | 18 | |
|
19 | 19 | # Linux distributions that are supported. |
|
20 | 20 | DISTROS = { |
|
21 | 21 | 'debian9', |
|
22 | 22 | 'debian10', |
|
23 | 23 | 'ubuntu18.04', |
|
24 | 24 | 'ubuntu19.04', |
|
25 | 25 | } |
|
26 | 26 | |
|
27 | 27 | INSTALL_PYTHONS = r''' |
|
28 |
PYENV2_VERSIONS="2.7.1 |
|
|
29 |
PYENV3_VERSIONS="3.5.7 3.6.9 3.7. |
|
|
28 | PYENV2_VERSIONS="2.7.17 pypy2.7-7.2.0" | |
|
29 | PYENV3_VERSIONS="3.5.7 3.6.9 3.7.5 3.8.0 pypy3.5-7.0.0 pypy3.6-7.2.0" | |
|
30 | 30 | |
|
31 | 31 | git clone https://github.com/pyenv/pyenv.git /hgdev/pyenv |
|
32 | 32 | pushd /hgdev/pyenv |
|
33 | git checkout d6d6bc8bb08bcdcbf4eb79509aa7061011ade1c4 | |
|
33 | git checkout 0e7cfc3b3d4eca46ad83d632e1505f5932cd179b | |
|
34 | 34 | popd |
|
35 | 35 | |
|
36 | 36 | export PYENV_ROOT="/hgdev/pyenv" |
|
37 | 37 | export PATH="$PYENV_ROOT/bin:$PATH" |
|
38 | 38 | |
|
39 | 39 | # pip 19.2.3. |
|
40 | 40 | PIP_SHA256=57e3643ff19f018f8a00dfaa6b7e4620e3c1a7a2171fd218425366ec006b3bfe |
|
41 | 41 | wget -O get-pip.py --progress dot:mega https://github.com/pypa/get-pip/raw/309a56c5fd94bd1134053a541cb4657a4e47e09d/get-pip.py |
|
42 | 42 | echo "${PIP_SHA256} get-pip.py" | sha256sum --check - |
|
43 | 43 | |
|
44 | 44 | VIRTUALENV_SHA256=f78d81b62d3147396ac33fc9d77579ddc42cc2a98dd9ea38886f616b33bc7fb2 |
|
45 | 45 | VIRTUALENV_TARBALL=virtualenv-16.7.5.tar.gz |
|
46 | 46 | wget -O ${VIRTUALENV_TARBALL} --progress dot:mega https://files.pythonhosted.org/packages/66/f0/6867af06d2e2f511e4e1d7094ff663acdebc4f15d4a0cb0fed1007395124/${VIRTUALENV_TARBALL} |
|
47 | 47 | echo "${VIRTUALENV_SHA256} ${VIRTUALENV_TARBALL}" | sha256sum --check - |
|
48 | 48 | |
|
49 | 49 | for v in ${PYENV2_VERSIONS}; do |
|
50 | 50 | pyenv install -v ${v} |
|
51 | 51 | ${PYENV_ROOT}/versions/${v}/bin/python get-pip.py |
|
52 | 52 | ${PYENV_ROOT}/versions/${v}/bin/pip install ${VIRTUALENV_TARBALL} |
|
53 | 53 | ${PYENV_ROOT}/versions/${v}/bin/pip install -r /hgdev/requirements-py2.txt |
|
54 | 54 | done |
|
55 | 55 | |
|
56 | 56 | for v in ${PYENV3_VERSIONS}; do |
|
57 | 57 | pyenv install -v ${v} |
|
58 | 58 | ${PYENV_ROOT}/versions/${v}/bin/python get-pip.py |
|
59 | 59 | ${PYENV_ROOT}/versions/${v}/bin/pip install -r /hgdev/requirements-py3.txt |
|
60 | 60 | done |
|
61 | 61 | |
|
62 | 62 | pyenv global ${PYENV2_VERSIONS} ${PYENV3_VERSIONS} system |
|
63 | 63 | '''.lstrip().replace( |
|
64 | 64 | '\r\n', '\n' |
|
65 | 65 | ) |
|
66 | 66 | |
|
67 | 67 | |
|
68 | 68 | INSTALL_RUST = r''' |
|
69 | 69 | RUSTUP_INIT_SHA256=a46fe67199b7bcbbde2dcbc23ae08db6f29883e260e23899a88b9073effc9076 |
|
70 | 70 | wget -O rustup-init --progress dot:mega https://static.rust-lang.org/rustup/archive/1.18.3/x86_64-unknown-linux-gnu/rustup-init |
|
71 | 71 | echo "${RUSTUP_INIT_SHA256} rustup-init" | sha256sum --check - |
|
72 | 72 | |
|
73 | 73 | chmod +x rustup-init |
|
74 | 74 | sudo -H -u hg -g hg ./rustup-init -y |
|
75 | 75 | sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup install 1.31.1 1.34.2 |
|
76 | 76 | sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup component add clippy |
|
77 | 77 | ''' |
|
78 | 78 | |
|
79 | 79 | |
|
80 | 80 | BOOTSTRAP_VIRTUALENV = r''' |
|
81 | 81 | /usr/bin/virtualenv /hgdev/venv-bootstrap |
|
82 | 82 | |
|
83 | 83 | HG_SHA256=35fc8ba5e0379c1b3affa2757e83fb0509e8ac314cbd9f1fd133cf265d16e49f |
|
84 | 84 | HG_TARBALL=mercurial-5.1.1.tar.gz |
|
85 | 85 | |
|
86 | 86 | wget -O ${HG_TARBALL} --progress dot:mega https://www.mercurial-scm.org/release/${HG_TARBALL} |
|
87 | 87 | echo "${HG_SHA256} ${HG_TARBALL}" | sha256sum --check - |
|
88 | 88 | |
|
89 | 89 | /hgdev/venv-bootstrap/bin/pip install ${HG_TARBALL} |
|
90 | 90 | '''.lstrip().replace( |
|
91 | 91 | '\r\n', '\n' |
|
92 | 92 | ) |
|
93 | 93 | |
|
94 | 94 | |
|
95 | 95 | BOOTSTRAP_DEBIAN = ( |
|
96 | 96 | r''' |
|
97 | 97 | #!/bin/bash |
|
98 | 98 | |
|
99 | 99 | set -ex |
|
100 | 100 | |
|
101 | 101 | DISTRO=`grep DISTRIB_ID /etc/lsb-release | awk -F= '{{print $2}}'` |
|
102 | 102 | DEBIAN_VERSION=`cat /etc/debian_version` |
|
103 | 103 | LSB_RELEASE=`lsb_release -cs` |
|
104 | 104 | |
|
105 | 105 | sudo /usr/sbin/groupadd hg |
|
106 | 106 | sudo /usr/sbin/groupadd docker |
|
107 | 107 | sudo /usr/sbin/useradd -g hg -G sudo,docker -d /home/hg -m -s /bin/bash hg |
|
108 | 108 | sudo mkdir /home/hg/.ssh |
|
109 | 109 | sudo cp ~/.ssh/authorized_keys /home/hg/.ssh/authorized_keys |
|
110 | 110 | sudo chown -R hg:hg /home/hg/.ssh |
|
111 | 111 | sudo chmod 700 /home/hg/.ssh |
|
112 | 112 | sudo chmod 600 /home/hg/.ssh/authorized_keys |
|
113 | 113 | |
|
114 | 114 | cat << EOF | sudo tee /etc/sudoers.d/90-hg |
|
115 | 115 | hg ALL=(ALL) NOPASSWD:ALL |
|
116 | 116 | EOF |
|
117 | 117 | |
|
118 | 118 | sudo apt-get update |
|
119 | 119 | sudo DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade |
|
120 | 120 | |
|
121 | 121 | # Install packages necessary to set up Docker Apt repo. |
|
122 | 122 | sudo DEBIAN_FRONTEND=noninteractive apt-get -yq install --no-install-recommends \ |
|
123 | 123 | apt-transport-https \ |
|
124 | 124 | gnupg |
|
125 | 125 | |
|
126 | 126 | cat > docker-apt-key << EOF |
|
127 | 127 | -----BEGIN PGP PUBLIC KEY BLOCK----- |
|
128 | 128 | |
|
129 | 129 | mQINBFit2ioBEADhWpZ8/wvZ6hUTiXOwQHXMAlaFHcPH9hAtr4F1y2+OYdbtMuth |
|
130 | 130 | lqqwp028AqyY+PRfVMtSYMbjuQuu5byyKR01BbqYhuS3jtqQmljZ/bJvXqnmiVXh |
|
131 | 131 | 38UuLa+z077PxyxQhu5BbqntTPQMfiyqEiU+BKbq2WmANUKQf+1AmZY/IruOXbnq |
|
132 | 132 | L4C1+gJ8vfmXQt99npCaxEjaNRVYfOS8QcixNzHUYnb6emjlANyEVlZzeqo7XKl7 |
|
133 | 133 | UrwV5inawTSzWNvtjEjj4nJL8NsLwscpLPQUhTQ+7BbQXAwAmeHCUTQIvvWXqw0N |
|
134 | 134 | cmhh4HgeQscQHYgOJjjDVfoY5MucvglbIgCqfzAHW9jxmRL4qbMZj+b1XoePEtht |
|
135 | 135 | ku4bIQN1X5P07fNWzlgaRL5Z4POXDDZTlIQ/El58j9kp4bnWRCJW0lya+f8ocodo |
|
136 | 136 | vZZ+Doi+fy4D5ZGrL4XEcIQP/Lv5uFyf+kQtl/94VFYVJOleAv8W92KdgDkhTcTD |
|
137 | 137 | G7c0tIkVEKNUq48b3aQ64NOZQW7fVjfoKwEZdOqPE72Pa45jrZzvUFxSpdiNk2tZ |
|
138 | 138 | XYukHjlxxEgBdC/J3cMMNRE1F4NCA3ApfV1Y7/hTeOnmDuDYwr9/obA8t016Yljj |
|
139 | 139 | q5rdkywPf4JF8mXUW5eCN1vAFHxeg9ZWemhBtQmGxXnw9M+z6hWwc6ahmwARAQAB |
|
140 | 140 | tCtEb2NrZXIgUmVsZWFzZSAoQ0UgZGViKSA8ZG9ja2VyQGRvY2tlci5jb20+iQI3 |
|
141 | 141 | BBMBCgAhBQJYrefAAhsvBQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJEI2BgDwO |
|
142 | 142 | v82IsskP/iQZo68flDQmNvn8X5XTd6RRaUH33kXYXquT6NkHJciS7E2gTJmqvMqd |
|
143 | 143 | tI4mNYHCSEYxI5qrcYV5YqX9P6+Ko+vozo4nseUQLPH/ATQ4qL0Zok+1jkag3Lgk |
|
144 | 144 | jonyUf9bwtWxFp05HC3GMHPhhcUSexCxQLQvnFWXD2sWLKivHp2fT8QbRGeZ+d3m |
|
145 | 145 | 6fqcd5Fu7pxsqm0EUDK5NL+nPIgYhN+auTrhgzhK1CShfGccM/wfRlei9Utz6p9P |
|
146 | 146 | XRKIlWnXtT4qNGZNTN0tR+NLG/6Bqd8OYBaFAUcue/w1VW6JQ2VGYZHnZu9S8LMc |
|
147 | 147 | FYBa5Ig9PxwGQOgq6RDKDbV+PqTQT5EFMeR1mrjckk4DQJjbxeMZbiNMG5kGECA8 |
|
148 | 148 | g383P3elhn03WGbEEa4MNc3Z4+7c236QI3xWJfNPdUbXRaAwhy/6rTSFbzwKB0Jm |
|
149 | 149 | ebwzQfwjQY6f55MiI/RqDCyuPj3r3jyVRkK86pQKBAJwFHyqj9KaKXMZjfVnowLh |
|
150 | 150 | 9svIGfNbGHpucATqREvUHuQbNnqkCx8VVhtYkhDb9fEP2xBu5VvHbR+3nfVhMut5 |
|
151 | 151 | G34Ct5RS7Jt6LIfFdtcn8CaSas/l1HbiGeRgc70X/9aYx/V/CEJv0lIe8gP6uDoW |
|
152 | 152 | FPIZ7d6vH+Vro6xuWEGiuMaiznap2KhZmpkgfupyFmplh0s6knymuQINBFit2ioB |
|
153 | 153 | EADneL9S9m4vhU3blaRjVUUyJ7b/qTjcSylvCH5XUE6R2k+ckEZjfAMZPLpO+/tF |
|
154 | 154 | M2JIJMD4SifKuS3xck9KtZGCufGmcwiLQRzeHF7vJUKrLD5RTkNi23ydvWZgPjtx |
|
155 | 155 | Q+DTT1Zcn7BrQFY6FgnRoUVIxwtdw1bMY/89rsFgS5wwuMESd3Q2RYgb7EOFOpnu |
|
156 | 156 | w6da7WakWf4IhnF5nsNYGDVaIHzpiqCl+uTbf1epCjrOlIzkZ3Z3Yk5CM/TiFzPk |
|
157 | 157 | z2lLz89cpD8U+NtCsfagWWfjd2U3jDapgH+7nQnCEWpROtzaKHG6lA3pXdix5zG8 |
|
158 | 158 | eRc6/0IbUSWvfjKxLLPfNeCS2pCL3IeEI5nothEEYdQH6szpLog79xB9dVnJyKJb |
|
159 | 159 | VfxXnseoYqVrRz2VVbUI5Blwm6B40E3eGVfUQWiux54DspyVMMk41Mx7QJ3iynIa |
|
160 | 160 | 1N4ZAqVMAEruyXTRTxc9XW0tYhDMA/1GYvz0EmFpm8LzTHA6sFVtPm/ZlNCX6P1X |
|
161 | 161 | zJwrv7DSQKD6GGlBQUX+OeEJ8tTkkf8QTJSPUdh8P8YxDFS5EOGAvhhpMBYD42kQ |
|
162 | 162 | pqXjEC+XcycTvGI7impgv9PDY1RCC1zkBjKPa120rNhv/hkVk/YhuGoajoHyy4h7 |
|
163 | 163 | ZQopdcMtpN2dgmhEegny9JCSwxfQmQ0zK0g7m6SHiKMwjwARAQABiQQ+BBgBCAAJ |
|
164 | 164 | BQJYrdoqAhsCAikJEI2BgDwOv82IwV0gBBkBCAAGBQJYrdoqAAoJEH6gqcPyc/zY |
|
165 | 165 | 1WAP/2wJ+R0gE6qsce3rjaIz58PJmc8goKrir5hnElWhPgbq7cYIsW5qiFyLhkdp |
|
166 | 166 | YcMmhD9mRiPpQn6Ya2w3e3B8zfIVKipbMBnke/ytZ9M7qHmDCcjoiSmwEXN3wKYI |
|
167 | 167 | mD9VHONsl/CG1rU9Isw1jtB5g1YxuBA7M/m36XN6x2u+NtNMDB9P56yc4gfsZVES |
|
168 | 168 | KA9v+yY2/l45L8d/WUkUi0YXomn6hyBGI7JrBLq0CX37GEYP6O9rrKipfz73XfO7 |
|
169 | 169 | JIGzOKZlljb/D9RX/g7nRbCn+3EtH7xnk+TK/50euEKw8SMUg147sJTcpQmv6UzZ |
|
170 | 170 | cM4JgL0HbHVCojV4C/plELwMddALOFeYQzTif6sMRPf+3DSj8frbInjChC3yOLy0 |
|
171 | 171 | 6br92KFom17EIj2CAcoeq7UPhi2oouYBwPxh5ytdehJkoo+sN7RIWua6P2WSmon5 |
|
172 | 172 | U888cSylXC0+ADFdgLX9K2zrDVYUG1vo8CX0vzxFBaHwN6Px26fhIT1/hYUHQR1z |
|
173 | 173 | VfNDcyQmXqkOnZvvoMfz/Q0s9BhFJ/zU6AgQbIZE/hm1spsfgvtsD1frZfygXJ9f |
|
174 | 174 | irP+MSAI80xHSf91qSRZOj4Pl3ZJNbq4yYxv0b1pkMqeGdjdCYhLU+LZ4wbQmpCk |
|
175 | 175 | SVe2prlLureigXtmZfkqevRz7FrIZiu9ky8wnCAPwC7/zmS18rgP/17bOtL4/iIz |
|
176 | 176 | QhxAAoAMWVrGyJivSkjhSGx1uCojsWfsTAm11P7jsruIL61ZzMUVE2aM3Pmj5G+W |
|
177 | 177 | 9AcZ58Em+1WsVnAXdUR//bMmhyr8wL/G1YO1V3JEJTRdxsSxdYa4deGBBY/Adpsw |
|
178 | 178 | 24jxhOJR+lsJpqIUeb999+R8euDhRHG9eFO7DRu6weatUJ6suupoDTRWtr/4yGqe |
|
179 | 179 | dKxV3qQhNLSnaAzqW/1nA3iUB4k7kCaKZxhdhDbClf9P37qaRW467BLCVO/coL3y |
|
180 | 180 | Vm50dwdrNtKpMBh3ZpbB1uJvgi9mXtyBOMJ3v8RZeDzFiG8HdCtg9RvIt/AIFoHR |
|
181 | 181 | H3S+U79NT6i0KPzLImDfs8T7RlpyuMc4Ufs8ggyg9v3Ae6cN3eQyxcK3w0cbBwsh |
|
182 | 182 | /nQNfsA6uu+9H7NhbehBMhYnpNZyrHzCmzyXkauwRAqoCbGCNykTRwsur9gS41TQ |
|
183 | 183 | M8ssD1jFheOJf3hODnkKU+HKjvMROl1DK7zdmLdNzA1cvtZH/nCC9KPj1z8QC47S |
|
184 | 184 | xx+dTZSx4ONAhwbS/LN3PoKtn8LPjY9NP9uDWI+TWYquS2U+KHDrBDlsgozDbs/O |
|
185 | 185 | jCxcpDzNmXpWQHEtHU7649OXHP7UeNST1mCUCH5qdank0V1iejF6/CfTFU4MfcrG |
|
186 | 186 | YT90qFF93M3v01BbxP+EIY2/9tiIPbrd |
|
187 | 187 | =0YYh |
|
188 | 188 | -----END PGP PUBLIC KEY BLOCK----- |
|
189 | 189 | EOF |
|
190 | 190 | |
|
191 | 191 | sudo apt-key add docker-apt-key |
|
192 | 192 | |
|
193 | 193 | if [ "$LSB_RELEASE" = "stretch" ]; then |
|
194 | 194 | cat << EOF | sudo tee -a /etc/apt/sources.list |
|
195 | 195 | # Need backports for clang-format-6.0 |
|
196 | 196 | deb http://deb.debian.org/debian stretch-backports main |
|
197 | 197 | EOF |
|
198 | 198 | fi |
|
199 | 199 | |
|
200 | 200 | if [ "$LSB_RELEASE" = "stretch" -o "$LSB_RELEASE" = "buster" ]; then |
|
201 | 201 | cat << EOF | sudo tee -a /etc/apt/sources.list |
|
202 | 202 | # Sources are useful if we want to compile things locally. |
|
203 | 203 | deb-src http://deb.debian.org/debian $LSB_RELEASE main |
|
204 | 204 | deb-src http://security.debian.org/debian-security $LSB_RELEASE/updates main |
|
205 | 205 | deb-src http://deb.debian.org/debian $LSB_RELEASE-updates main |
|
206 | 206 | deb-src http://deb.debian.org/debian $LSB_RELEASE-backports main |
|
207 | 207 | |
|
208 | 208 | deb [arch=amd64] https://download.docker.com/linux/debian $LSB_RELEASE stable |
|
209 | 209 | EOF |
|
210 | 210 | |
|
211 | 211 | elif [ "$DISTRO" = "Ubuntu" ]; then |
|
212 | 212 | cat << EOF | sudo tee -a /etc/apt/sources.list |
|
213 | 213 | deb [arch=amd64] https://download.docker.com/linux/ubuntu $LSB_RELEASE stable |
|
214 | 214 | EOF |
|
215 | 215 | |
|
216 | 216 | fi |
|
217 | 217 | |
|
218 | 218 | sudo apt-get update |
|
219 | 219 | |
|
220 | 220 | PACKAGES="\ |
|
221 | 221 | awscli \ |
|
222 | 222 | btrfs-progs \ |
|
223 | 223 | build-essential \ |
|
224 | 224 | bzr \ |
|
225 | 225 | clang-format-6.0 \ |
|
226 | 226 | cvs \ |
|
227 | 227 | darcs \ |
|
228 | 228 | debhelper \ |
|
229 | 229 | devscripts \ |
|
230 | 230 | docker-ce \ |
|
231 | 231 | dpkg-dev \ |
|
232 | 232 | dstat \ |
|
233 | 233 | emacs \ |
|
234 | 234 | gettext \ |
|
235 | 235 | git \ |
|
236 | 236 | htop \ |
|
237 | 237 | iotop \ |
|
238 | 238 | jfsutils \ |
|
239 | 239 | libbz2-dev \ |
|
240 | 240 | libexpat1-dev \ |
|
241 | 241 | libffi-dev \ |
|
242 | 242 | libgdbm-dev \ |
|
243 | 243 | liblzma-dev \ |
|
244 | 244 | libncurses5-dev \ |
|
245 | 245 | libnss3-dev \ |
|
246 | 246 | libreadline-dev \ |
|
247 | 247 | libsqlite3-dev \ |
|
248 | 248 | libssl-dev \ |
|
249 | 249 | netbase \ |
|
250 | 250 | ntfs-3g \ |
|
251 | 251 | nvme-cli \ |
|
252 | 252 | pyflakes \ |
|
253 | 253 | pyflakes3 \ |
|
254 | 254 | pylint \ |
|
255 | 255 | pylint3 \ |
|
256 | 256 | python-all-dev \ |
|
257 | 257 | python-dev \ |
|
258 | 258 | python-docutils \ |
|
259 | 259 | python-fuzzywuzzy \ |
|
260 | 260 | python-pygments \ |
|
261 | 261 | python-subversion \ |
|
262 | 262 | python-vcr \ |
|
263 | 263 | python3-boto3 \ |
|
264 | 264 | python3-dev \ |
|
265 | 265 | python3-docutils \ |
|
266 | 266 | python3-fuzzywuzzy \ |
|
267 | 267 | python3-pygments \ |
|
268 | 268 | python3-vcr \ |
|
269 | python3-venv \ | |
|
269 | 270 | rsync \ |
|
270 | 271 | sqlite3 \ |
|
271 | 272 | subversion \ |
|
272 | 273 | tcl-dev \ |
|
273 | 274 | tk-dev \ |
|
274 | 275 | tla \ |
|
275 | 276 | unzip \ |
|
276 | 277 | uuid-dev \ |
|
277 | 278 | vim \ |
|
278 | 279 | virtualenv \ |
|
279 | 280 | wget \ |
|
280 | 281 | xfsprogs \ |
|
281 | 282 | zip \ |
|
282 | 283 | zlib1g-dev" |
|
283 | 284 | |
|
284 | 285 | if [ "LSB_RELEASE" = "stretch" ]; then |
|
285 | 286 | PACKAGES="$PACKAGES linux-perf" |
|
286 | 287 | elif [ "$DISTRO" = "Ubuntu" ]; then |
|
287 | 288 | PACKAGES="$PACKAGES linux-tools-common" |
|
288 | 289 | fi |
|
289 | 290 | |
|
290 | 291 | # Monotone only available in older releases. |
|
291 | 292 | if [ "$LSB_RELEASE" = "stretch" -o "$LSB_RELEASE" = "xenial" ]; then |
|
292 | 293 | PACKAGES="$PACKAGES monotone" |
|
293 | 294 | fi |
|
294 | 295 | |
|
295 | 296 | sudo DEBIAN_FRONTEND=noninteractive apt-get -yq install --no-install-recommends $PACKAGES |
|
296 | 297 | |
|
297 | 298 | # Create clang-format symlink so test harness finds it. |
|
298 | 299 | sudo update-alternatives --install /usr/bin/clang-format clang-format \ |
|
299 | 300 | /usr/bin/clang-format-6.0 1000 |
|
300 | 301 | |
|
301 | 302 | sudo mkdir /hgdev |
|
302 | 303 | # Will be normalized to hg:hg later. |
|
303 | 304 | sudo chown `whoami` /hgdev |
|
304 | 305 | |
|
305 | 306 | {install_rust} |
|
306 | 307 | |
|
307 | 308 | cp requirements-py2.txt /hgdev/requirements-py2.txt |
|
308 | 309 | cp requirements-py3.txt /hgdev/requirements-py3.txt |
|
309 | 310 | |
|
310 | 311 | # Disable the pip version check because it uses the network and can |
|
311 | 312 | # be annoying. |
|
312 | 313 | cat << EOF | sudo tee -a /etc/pip.conf |
|
313 | 314 | [global] |
|
314 | 315 | disable-pip-version-check = True |
|
315 | 316 | EOF |
|
316 | 317 | |
|
317 | 318 | {install_pythons} |
|
318 | 319 | {bootstrap_virtualenv} |
|
319 | 320 | |
|
320 | 321 | /hgdev/venv-bootstrap/bin/hg clone https://www.mercurial-scm.org/repo/hg /hgdev/src |
|
321 | 322 | |
|
322 | 323 | # Mark the repo as non-publishing. |
|
323 | 324 | cat >> /hgdev/src/.hg/hgrc << EOF |
|
324 | 325 | [phases] |
|
325 | 326 | publish = false |
|
326 | 327 | EOF |
|
327 | 328 | |
|
328 | 329 | sudo chown -R hg:hg /hgdev |
|
329 | 330 | '''.lstrip() |
|
330 | 331 | .format( |
|
331 | 332 | install_rust=INSTALL_RUST, |
|
332 | 333 | install_pythons=INSTALL_PYTHONS, |
|
333 | 334 | bootstrap_virtualenv=BOOTSTRAP_VIRTUALENV, |
|
334 | 335 | ) |
|
335 | 336 | .replace('\r\n', '\n') |
|
336 | 337 | ) |
|
337 | 338 | |
|
338 | 339 | |
|
339 | 340 | # Prepares /hgdev for operations. |
|
340 | 341 | PREPARE_HGDEV = ''' |
|
341 | 342 | #!/bin/bash |
|
342 | 343 | |
|
343 | 344 | set -e |
|
344 | 345 | |
|
345 | 346 | FS=$1 |
|
346 | 347 | |
|
347 | 348 | ensure_device() { |
|
348 | 349 | if [ -z "${DEVICE}" ]; then |
|
349 | 350 | echo "could not find block device to format" |
|
350 | 351 | exit 1 |
|
351 | 352 | fi |
|
352 | 353 | } |
|
353 | 354 | |
|
354 | 355 | # Determine device to partition for extra filesystem. |
|
355 | 356 | # If only 1 volume is present, it will be the root volume and |
|
356 | 357 | # should be /dev/nvme0. If multiple volumes are present, the |
|
357 | 358 | # root volume could be nvme0 or nvme1. Use whichever one doesn't have |
|
358 | 359 | # a partition. |
|
359 | 360 | if [ -e /dev/nvme1n1 ]; then |
|
360 | 361 | if [ -e /dev/nvme0n1p1 ]; then |
|
361 | 362 | DEVICE=/dev/nvme1n1 |
|
362 | 363 | else |
|
363 | 364 | DEVICE=/dev/nvme0n1 |
|
364 | 365 | fi |
|
365 | 366 | else |
|
366 | 367 | DEVICE= |
|
367 | 368 | fi |
|
368 | 369 | |
|
369 | 370 | sudo mkdir /hgwork |
|
370 | 371 | |
|
371 | 372 | if [ "${FS}" != "default" -a "${FS}" != "tmpfs" ]; then |
|
372 | 373 | ensure_device |
|
373 | 374 | echo "creating ${FS} filesystem on ${DEVICE}" |
|
374 | 375 | fi |
|
375 | 376 | |
|
376 | 377 | if [ "${FS}" = "default" ]; then |
|
377 | 378 | : |
|
378 | 379 | |
|
379 | 380 | elif [ "${FS}" = "btrfs" ]; then |
|
380 | 381 | sudo mkfs.btrfs ${DEVICE} |
|
381 | 382 | sudo mount ${DEVICE} /hgwork |
|
382 | 383 | |
|
383 | 384 | elif [ "${FS}" = "ext3" ]; then |
|
384 | 385 | # lazy_journal_init speeds up filesystem creation at the expense of |
|
385 | 386 | # integrity if things crash. We are an ephemeral instance, so we don't |
|
386 | 387 | # care about integrity. |
|
387 | 388 | sudo mkfs.ext3 -E lazy_journal_init=1 ${DEVICE} |
|
388 | 389 | sudo mount ${DEVICE} /hgwork |
|
389 | 390 | |
|
390 | 391 | elif [ "${FS}" = "ext4" ]; then |
|
391 | 392 | sudo mkfs.ext4 -E lazy_journal_init=1 ${DEVICE} |
|
392 | 393 | sudo mount ${DEVICE} /hgwork |
|
393 | 394 | |
|
394 | 395 | elif [ "${FS}" = "jfs" ]; then |
|
395 | 396 | sudo mkfs.jfs ${DEVICE} |
|
396 | 397 | sudo mount ${DEVICE} /hgwork |
|
397 | 398 | |
|
398 | 399 | elif [ "${FS}" = "tmpfs" ]; then |
|
399 | 400 | echo "creating tmpfs volume in /hgwork" |
|
400 | 401 | sudo mount -t tmpfs -o size=1024M tmpfs /hgwork |
|
401 | 402 | |
|
402 | 403 | elif [ "${FS}" = "xfs" ]; then |
|
403 | 404 | sudo mkfs.xfs ${DEVICE} |
|
404 | 405 | sudo mount ${DEVICE} /hgwork |
|
405 | 406 | |
|
406 | 407 | else |
|
407 | 408 | echo "unsupported filesystem: ${FS}" |
|
408 | 409 | exit 1 |
|
409 | 410 | fi |
|
410 | 411 | |
|
411 | 412 | echo "/hgwork ready" |
|
412 | 413 | |
|
413 | 414 | sudo chown hg:hg /hgwork |
|
414 | 415 | mkdir /hgwork/tmp |
|
415 | 416 | chown hg:hg /hgwork/tmp |
|
416 | 417 | |
|
417 | 418 | rsync -a /hgdev/src /hgwork/ |
|
418 | 419 | '''.lstrip().replace( |
|
419 | 420 | '\r\n', '\n' |
|
420 | 421 | ) |
|
421 | 422 | |
|
422 | 423 | |
|
423 | 424 | HG_UPDATE_CLEAN = ''' |
|
424 | 425 | set -ex |
|
425 | 426 | |
|
426 | 427 | HG=/hgdev/venv-bootstrap/bin/hg |
|
427 | 428 | |
|
428 | 429 | cd /hgwork/src |
|
429 | 430 | ${HG} --config extensions.purge= purge --all |
|
430 | 431 | ${HG} update -C $1 |
|
431 | 432 | ${HG} log -r . |
|
432 | 433 | '''.lstrip().replace( |
|
433 | 434 | '\r\n', '\n' |
|
434 | 435 | ) |
|
435 | 436 | |
|
436 | 437 | |
|
437 | 438 | def prepare_exec_environment(ssh_client, filesystem='default'): |
|
438 | 439 | """Prepare an EC2 instance to execute things. |
|
439 | 440 | |
|
440 | 441 | The AMI has an ``/hgdev`` bootstrapped with various Python installs |
|
441 | 442 | and a clone of the Mercurial repo. |
|
442 | 443 | |
|
443 | 444 | In EC2, EBS volumes launched from snapshots have wonky performance behavior. |
|
444 | 445 | Notably, blocks have to be copied on first access, which makes volume |
|
445 | 446 | I/O extremely slow on fresh volumes. |
|
446 | 447 | |
|
447 | 448 | Furthermore, we may want to run operations, tests, etc on alternative |
|
448 | 449 | filesystems so we examine behavior on different filesystems. |
|
449 | 450 | |
|
450 | 451 | This function is used to facilitate executing operations on alternate |
|
451 | 452 | volumes. |
|
452 | 453 | """ |
|
453 | 454 | sftp = ssh_client.open_sftp() |
|
454 | 455 | |
|
455 | 456 | with sftp.open('/hgdev/prepare-hgdev', 'wb') as fh: |
|
456 | 457 | fh.write(PREPARE_HGDEV) |
|
457 | 458 | fh.chmod(0o0777) |
|
458 | 459 | |
|
459 | 460 | command = 'sudo /hgdev/prepare-hgdev %s' % filesystem |
|
460 | 461 | chan, stdin, stdout = exec_command(ssh_client, command) |
|
461 | 462 | stdin.close() |
|
462 | 463 | |
|
463 | 464 | for line in stdout: |
|
464 | 465 | print(line, end='') |
|
465 | 466 | |
|
466 | 467 | res = chan.recv_exit_status() |
|
467 | 468 | |
|
468 | 469 | if res: |
|
469 | 470 | raise Exception('non-0 exit code updating working directory; %d' % res) |
|
470 | 471 | |
|
471 | 472 | |
|
472 | 473 | def synchronize_hg( |
|
473 | 474 | source_path: pathlib.Path, ec2_instance, revision: str = None |
|
474 | 475 | ): |
|
475 | 476 | """Synchronize a local Mercurial source path to remote EC2 instance.""" |
|
476 | 477 | |
|
477 | 478 | with tempfile.TemporaryDirectory() as temp_dir: |
|
478 | 479 | temp_dir = pathlib.Path(temp_dir) |
|
479 | 480 | |
|
480 | 481 | ssh_dir = temp_dir / '.ssh' |
|
481 | 482 | ssh_dir.mkdir() |
|
482 | 483 | ssh_dir.chmod(0o0700) |
|
483 | 484 | |
|
484 | 485 | public_ip = ec2_instance.public_ip_address |
|
485 | 486 | |
|
486 | 487 | ssh_config = ssh_dir / 'config' |
|
487 | 488 | |
|
488 | 489 | with ssh_config.open('w', encoding='utf-8') as fh: |
|
489 | 490 | fh.write('Host %s\n' % public_ip) |
|
490 | 491 | fh.write(' User hg\n') |
|
491 | 492 | fh.write(' StrictHostKeyChecking no\n') |
|
492 | 493 | fh.write(' UserKnownHostsFile %s\n' % (ssh_dir / 'known_hosts')) |
|
493 | 494 | fh.write(' IdentityFile %s\n' % ec2_instance.ssh_private_key_path) |
|
494 | 495 | |
|
495 | 496 | if not (source_path / '.hg').is_dir(): |
|
496 | 497 | raise Exception( |
|
497 | 498 | '%s is not a Mercurial repository; synchronization ' |
|
498 | 499 | 'not yet supported' % source_path |
|
499 | 500 | ) |
|
500 | 501 | |
|
501 | 502 | env = dict(os.environ) |
|
502 | 503 | env['HGPLAIN'] = '1' |
|
503 | 504 | env['HGENCODING'] = 'utf-8' |
|
504 | 505 | |
|
505 | 506 | hg_bin = source_path / 'hg' |
|
506 | 507 | |
|
507 | 508 | res = subprocess.run( |
|
508 | 509 | ['python2.7', str(hg_bin), 'log', '-r', revision, '-T', '{node}'], |
|
509 | 510 | cwd=str(source_path), |
|
510 | 511 | env=env, |
|
511 | 512 | check=True, |
|
512 | 513 | capture_output=True, |
|
513 | 514 | ) |
|
514 | 515 | |
|
515 | 516 | full_revision = res.stdout.decode('ascii') |
|
516 | 517 | |
|
517 | 518 | args = [ |
|
518 | 519 | 'python2.7', |
|
519 | 520 | str(hg_bin), |
|
520 | 521 | '--config', |
|
521 | 522 | 'ui.ssh=ssh -F %s' % ssh_config, |
|
522 | 523 | '--config', |
|
523 | 524 | 'ui.remotecmd=/hgdev/venv-bootstrap/bin/hg', |
|
524 | 525 | # Also ensure .hgtags changes are present so auto version |
|
525 | 526 | # calculation works. |
|
526 | 527 | 'push', |
|
527 | 528 | '-f', |
|
528 | 529 | '-r', |
|
529 | 530 | full_revision, |
|
530 | 531 | '-r', |
|
531 | 532 | 'file(.hgtags)', |
|
532 | 533 | 'ssh://%s//hgwork/src' % public_ip, |
|
533 | 534 | ] |
|
534 | 535 | |
|
535 | 536 | res = subprocess.run(args, cwd=str(source_path), env=env) |
|
536 | 537 | |
|
537 | 538 | # Allow 1 (no-op) to not trigger error. |
|
538 | 539 | if res.returncode not in (0, 1): |
|
539 | 540 | res.check_returncode() |
|
540 | 541 | |
|
541 | 542 | # TODO support synchronizing dirty working directory. |
|
542 | 543 | |
|
543 | 544 | sftp = ec2_instance.ssh_client.open_sftp() |
|
544 | 545 | |
|
545 | 546 | with sftp.open('/hgdev/hgup', 'wb') as fh: |
|
546 | 547 | fh.write(HG_UPDATE_CLEAN) |
|
547 | 548 | fh.chmod(0o0700) |
|
548 | 549 | |
|
549 | 550 | chan, stdin, stdout = exec_command( |
|
550 | 551 | ec2_instance.ssh_client, '/hgdev/hgup %s' % full_revision |
|
551 | 552 | ) |
|
552 | 553 | stdin.close() |
|
553 | 554 | |
|
554 | 555 | for line in stdout: |
|
555 | 556 | print(line, end='') |
|
556 | 557 | |
|
557 | 558 | res = chan.recv_exit_status() |
|
558 | 559 | |
|
559 | 560 | if res: |
|
560 | 561 | raise Exception( |
|
561 | 562 | 'non-0 exit code updating working directory; %d' % res |
|
562 | 563 | ) |
|
563 | 564 | |
|
564 | 565 | |
|
565 | 566 | def run_tests(ssh_client, python_version, test_flags=None): |
|
566 | 567 | """Run tests on a remote Linux machine via an SSH client.""" |
|
567 | 568 | test_flags = test_flags or [] |
|
568 | 569 | |
|
569 | 570 | print('running tests') |
|
570 | 571 | |
|
571 | 572 | if python_version == 'system2': |
|
572 | 573 | python = '/usr/bin/python2' |
|
573 | 574 | elif python_version == 'system3': |
|
574 | 575 | python = '/usr/bin/python3' |
|
575 | 576 | elif python_version.startswith('pypy'): |
|
576 | 577 | python = '/hgdev/pyenv/shims/%s' % python_version |
|
577 | 578 | else: |
|
578 | 579 | python = '/hgdev/pyenv/shims/python%s' % python_version |
|
579 | 580 | |
|
580 | 581 | test_flags = ' '.join(shlex.quote(a) for a in test_flags) |
|
581 | 582 | |
|
582 | 583 | command = ( |
|
583 | 584 | '/bin/sh -c "export TMPDIR=/hgwork/tmp; ' |
|
584 | 585 | 'cd /hgwork/src/tests && %s run-tests.py %s"' % (python, test_flags) |
|
585 | 586 | ) |
|
586 | 587 | |
|
587 | 588 | chan, stdin, stdout = exec_command(ssh_client, command) |
|
588 | 589 | |
|
589 | 590 | stdin.close() |
|
590 | 591 | |
|
591 | 592 | for line in stdout: |
|
592 | 593 | print(line, end='') |
|
593 | 594 | |
|
594 | 595 | return chan.recv_exit_status() |
@@ -1,149 +1,184 b'' | |||
|
1 | 1 | # |
|
2 | 2 | # This file is autogenerated by pip-compile |
|
3 | 3 | # To update, run: |
|
4 | 4 | # |
|
5 | 5 | # pip-compile --generate-hashes --output-file=contrib/automation/linux-requirements-py3.txt contrib/automation/linux-requirements.txt.in |
|
6 | 6 | # |
|
7 | appdirs==1.4.3 \ | |
|
8 | --hash=sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92 \ | |
|
9 | --hash=sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e \ | |
|
10 | # via black | |
|
7 | 11 | astroid==2.2.5 \ |
|
8 | 12 | --hash=sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4 \ |
|
9 | 13 | --hash=sha256:b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4 \ |
|
10 | 14 | # via pylint |
|
15 | attrs==19.3.0 \ | |
|
16 | --hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \ | |
|
17 | --hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72 \ | |
|
18 | # via black | |
|
19 | black==19.10b0 ; python_version >= "3.6" and platform_python_implementation != "PyPy" \ | |
|
20 | --hash=sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b \ | |
|
21 | --hash=sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539 | |
|
22 | click==7.0 \ | |
|
23 | --hash=sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13 \ | |
|
24 | --hash=sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7 \ | |
|
25 | # via black | |
|
11 | 26 | docutils==0.15.2 \ |
|
12 | 27 | --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ |
|
13 | 28 | --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ |
|
14 | 29 | --hash=sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99 |
|
15 | 30 | fuzzywuzzy==0.17.0 \ |
|
16 | 31 | --hash=sha256:5ac7c0b3f4658d2743aa17da53a55598144edbc5bee3c6863840636e6926f254 \ |
|
17 | 32 | --hash=sha256:6f49de47db00e1c71d40ad16da42284ac357936fa9b66bea1df63fed07122d62 |
|
18 | 33 | idna==2.8 \ |
|
19 | 34 | --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ |
|
20 | 35 | --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c \ |
|
21 | 36 | # via yarl |
|
22 | 37 | isort==4.3.21 \ |
|
23 | 38 | --hash=sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1 \ |
|
24 | 39 | --hash=sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd \ |
|
25 | 40 | # via pylint |
|
26 | 41 | lazy-object-proxy==1.4.1 \ |
|
27 | 42 | --hash=sha256:159a745e61422217881c4de71f9eafd9d703b93af95618635849fe469a283661 \ |
|
28 | 43 | --hash=sha256:23f63c0821cc96a23332e45dfaa83266feff8adc72b9bcaef86c202af765244f \ |
|
29 | 44 | --hash=sha256:3b11be575475db2e8a6e11215f5aa95b9ec14de658628776e10d96fa0b4dac13 \ |
|
30 | 45 | --hash=sha256:3f447aff8bc61ca8b42b73304f6a44fa0d915487de144652816f950a3f1ab821 \ |
|
31 | 46 | --hash=sha256:4ba73f6089cd9b9478bc0a4fa807b47dbdb8fad1d8f31a0f0a5dbf26a4527a71 \ |
|
32 | 47 | --hash=sha256:4f53eadd9932055eac465bd3ca1bd610e4d7141e1278012bd1f28646aebc1d0e \ |
|
33 | 48 | --hash=sha256:64483bd7154580158ea90de5b8e5e6fc29a16a9b4db24f10193f0c1ae3f9d1ea \ |
|
34 | 49 | --hash=sha256:6f72d42b0d04bfee2397aa1862262654b56922c20a9bb66bb76b6f0e5e4f9229 \ |
|
35 | 50 | --hash=sha256:7c7f1ec07b227bdc561299fa2328e85000f90179a2f44ea30579d38e037cb3d4 \ |
|
36 | 51 | --hash=sha256:7c8b1ba1e15c10b13cad4171cfa77f5bb5ec2580abc5a353907780805ebe158e \ |
|
37 | 52 | --hash=sha256:8559b94b823f85342e10d3d9ca4ba5478168e1ac5658a8a2f18c991ba9c52c20 \ |
|
38 | 53 | --hash=sha256:a262c7dfb046f00e12a2bdd1bafaed2408114a89ac414b0af8755c696eb3fc16 \ |
|
39 | 54 | --hash=sha256:acce4e3267610c4fdb6632b3886fe3f2f7dd641158a843cf6b6a68e4ce81477b \ |
|
40 | 55 | --hash=sha256:be089bb6b83fac7f29d357b2dc4cf2b8eb8d98fe9d9ff89f9ea6012970a853c7 \ |
|
41 | 56 | --hash=sha256:bfab710d859c779f273cc48fb86af38d6e9210f38287df0069a63e40b45a2f5c \ |
|
42 | 57 | --hash=sha256:c10d29019927301d524a22ced72706380de7cfc50f767217485a912b4c8bd82a \ |
|
43 | 58 | --hash=sha256:dd6e2b598849b3d7aee2295ac765a578879830fb8966f70be8cd472e6069932e \ |
|
44 | 59 | --hash=sha256:e408f1eacc0a68fed0c08da45f31d0ebb38079f043328dce69ff133b95c29dc1 \ |
|
45 | 60 | # via astroid |
|
46 | 61 | mccabe==0.6.1 \ |
|
47 | 62 | --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \ |
|
48 | 63 | --hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f \ |
|
49 | 64 | # via pylint |
|
50 | 65 | multidict==4.5.2 \ |
|
51 | 66 | --hash=sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f \ |
|
52 | 67 | --hash=sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3 \ |
|
53 | 68 | --hash=sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef \ |
|
54 | 69 | --hash=sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b \ |
|
55 | 70 | --hash=sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73 \ |
|
56 | 71 | --hash=sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc \ |
|
57 | 72 | --hash=sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3 \ |
|
58 | 73 | --hash=sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd \ |
|
59 | 74 | --hash=sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351 \ |
|
60 | 75 | --hash=sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941 \ |
|
61 | 76 | --hash=sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d \ |
|
62 | 77 | --hash=sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1 \ |
|
63 | 78 | --hash=sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b \ |
|
64 | 79 | --hash=sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a \ |
|
65 | 80 | --hash=sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3 \ |
|
66 | 81 | --hash=sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7 \ |
|
67 | 82 | --hash=sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0 \ |
|
68 | 83 | --hash=sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0 \ |
|
69 | 84 | --hash=sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014 \ |
|
70 | 85 | --hash=sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5 \ |
|
71 | 86 | --hash=sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036 \ |
|
72 | 87 | --hash=sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d \ |
|
73 | 88 | --hash=sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a \ |
|
74 | 89 | --hash=sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce \ |
|
75 | 90 | --hash=sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1 \ |
|
76 | 91 | --hash=sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a \ |
|
77 | 92 | --hash=sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9 \ |
|
78 | 93 | --hash=sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7 \ |
|
79 | 94 | --hash=sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b \ |
|
80 | 95 | # via yarl |
|
96 | pathspec==0.6.0 \ | |
|
97 | --hash=sha256:e285ccc8b0785beadd4c18e5708b12bb8fcf529a1e61215b3feff1d1e559ea5c \ | |
|
98 | # via black | |
|
81 | 99 | pyflakes==2.1.1 \ |
|
82 | 100 | --hash=sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0 \ |
|
83 | 101 | --hash=sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2 |
|
84 | 102 | pygments==2.4.2 \ |
|
85 | 103 | --hash=sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127 \ |
|
86 | 104 | --hash=sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297 |
|
87 | 105 | pylint==2.3.1 \ |
|
88 | 106 | --hash=sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09 \ |
|
89 | 107 | --hash=sha256:723e3db49555abaf9bf79dc474c6b9e2935ad82230b10c1138a71ea41ac0fff1 |
|
90 | 108 | python-levenshtein==0.12.0 \ |
|
91 | 109 | --hash=sha256:033a11de5e3d19ea25c9302d11224e1a1898fe5abd23c61c7c360c25195e3eb1 |
|
92 | 110 | pyyaml==5.1.2 \ |
|
93 | 111 | --hash=sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9 \ |
|
94 | 112 | --hash=sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4 \ |
|
95 | 113 | --hash=sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8 \ |
|
96 | 114 | --hash=sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696 \ |
|
97 | 115 | --hash=sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34 \ |
|
98 | 116 | --hash=sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9 \ |
|
99 | 117 | --hash=sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73 \ |
|
100 | 118 | --hash=sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299 \ |
|
101 | 119 | --hash=sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b \ |
|
102 | 120 | --hash=sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae \ |
|
103 | 121 | --hash=sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681 \ |
|
104 | 122 | --hash=sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41 \ |
|
105 | 123 | --hash=sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8 \ |
|
106 | 124 | # via vcrpy |
|
125 | regex==2019.11.1 \ | |
|
126 | --hash=sha256:15454b37c5a278f46f7aa2d9339bda450c300617ca2fca6558d05d870245edc7 \ | |
|
127 | --hash=sha256:1ad40708c255943a227e778b022c6497c129ad614bb7a2a2f916e12e8a359ee7 \ | |
|
128 | --hash=sha256:5e00f65cc507d13ab4dfa92c1232d004fa202c1d43a32a13940ab8a5afe2fb96 \ | |
|
129 | --hash=sha256:604dc563a02a74d70ae1f55208ddc9bfb6d9f470f6d1a5054c4bd5ae58744ab1 \ | |
|
130 | --hash=sha256:720e34a539a76a1fedcebe4397290604cc2bdf6f81eca44adb9fb2ea071c0c69 \ | |
|
131 | --hash=sha256:7caf47e4a9ac6ef08cabd3442cc4ca3386db141fb3c8b2a7e202d0470028e910 \ | |
|
132 | --hash=sha256:c31eaf28c6fe75ea329add0022efeed249e37861c19681960f99bbc7db981fb2 \ | |
|
133 | --hash=sha256:c7393597191fc2043c744db021643549061e12abe0b3ff5c429d806de7b93b66 \ | |
|
134 | --hash=sha256:d2b302f8cdd82c8f48e9de749d1d17f85ce9a0f082880b9a4859f66b07037dc6 \ | |
|
135 | --hash=sha256:e3d8dd0ec0ea280cf89026b0898971f5750a7bd92cb62c51af5a52abd020054a \ | |
|
136 | --hash=sha256:ec032cbfed59bd5a4b8eab943c310acfaaa81394e14f44454ad5c9eba4f24a74 \ | |
|
137 | # via black | |
|
107 | 138 | six==1.12.0 \ |
|
108 | 139 | --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ |
|
109 | 140 | --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 \ |
|
110 | 141 | # via astroid, vcrpy |
|
142 | toml==0.10.0 \ | |
|
143 | --hash=sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c \ | |
|
144 | --hash=sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e \ | |
|
145 | # via black | |
|
111 | 146 | typed-ast==1.4.0 ; python_version >= "3.0" and platform_python_implementation != "PyPy" \ |
|
112 | 147 | --hash=sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e \ |
|
113 | 148 | --hash=sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e \ |
|
114 | 149 | --hash=sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0 \ |
|
115 | 150 | --hash=sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c \ |
|
116 | 151 | --hash=sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631 \ |
|
117 | 152 | --hash=sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4 \ |
|
118 | 153 | --hash=sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34 \ |
|
119 | 154 | --hash=sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b \ |
|
120 | 155 | --hash=sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a \ |
|
121 | 156 | --hash=sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233 \ |
|
122 | 157 | --hash=sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1 \ |
|
123 | 158 | --hash=sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36 \ |
|
124 | 159 | --hash=sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d \ |
|
125 | 160 | --hash=sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a \ |
|
126 | 161 | --hash=sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12 |
|
127 | 162 | vcrpy==2.0.1 \ |
|
128 | 163 | --hash=sha256:127e79cf7b569d071d1bd761b83f7b62b2ce2a2eb63ceca7aa67cba8f2602ea3 \ |
|
129 | 164 | --hash=sha256:57be64aa8e9883a4117d0b15de28af62275c001abcdb00b6dc2d4406073d9a4f |
|
130 | 165 | wrapt==1.11.2 \ |
|
131 | 166 | --hash=sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1 \ |
|
132 | 167 | # via astroid, vcrpy |
|
133 | 168 | yarl==1.3.0 \ |
|
134 | 169 | --hash=sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9 \ |
|
135 | 170 | --hash=sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f \ |
|
136 | 171 | --hash=sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb \ |
|
137 | 172 | --hash=sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320 \ |
|
138 | 173 | --hash=sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842 \ |
|
139 | 174 | --hash=sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0 \ |
|
140 | 175 | --hash=sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829 \ |
|
141 | 176 | --hash=sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310 \ |
|
142 | 177 | --hash=sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4 \ |
|
143 | 178 | --hash=sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8 \ |
|
144 | 179 | --hash=sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1 \ |
|
145 | 180 | # via vcrpy |
|
146 | 181 | |
|
147 | 182 | # WARNING: The following packages were not pinned, but pip requires them to be |
|
148 | 183 | # pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag. |
|
149 |
# setuptools==41. |
|
|
184 | # setuptools==41.6.0 # via python-levenshtein |
@@ -1,12 +1,14 b'' | |||
|
1 | # black pulls in typed-ast, which doesn't install on PyPy. | |
|
2 | black ; python_version >= '3.6' and platform_python_implementation != 'PyPy' | |
|
1 | 3 | # Bazaar doesn't work with Python 3 nor PyPy. |
|
2 | 4 | bzr ; python_version <= '2.7' and platform_python_implementation == 'CPython' |
|
3 | 5 | docutils |
|
4 | 6 | fuzzywuzzy |
|
5 | 7 | pyflakes |
|
6 | 8 | pygments |
|
7 | 9 | pylint |
|
8 | 10 | # Needed to avoid warnings from fuzzywuzzy. |
|
9 | 11 | python-Levenshtein |
|
10 | 12 | # typed-ast dependency doesn't install on PyPy. |
|
11 | 13 | typed-ast ; python_version >= '3.0' and platform_python_implementation != 'PyPy' |
|
12 | 14 | vcrpy |
@@ -1,345 +1,345 b'' | |||
|
1 | 1 | #!/usr/bin/env python3 |
|
2 | 2 | # |
|
3 | 3 | # byteify-strings.py - transform string literals to be Python 3 safe |
|
4 | 4 | # |
|
5 | 5 | # Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com> |
|
6 | 6 | # |
|
7 | 7 | # This software may be used and distributed according to the terms of the |
|
8 | 8 | # GNU General Public License version 2 or any later version. |
|
9 | 9 | |
|
10 | 10 | from __future__ import absolute_import, print_function |
|
11 | 11 | |
|
12 | 12 | import argparse |
|
13 | 13 | import contextlib |
|
14 | 14 | import errno |
|
15 | 15 | import os |
|
16 | 16 | import sys |
|
17 | 17 | import tempfile |
|
18 | 18 | import token |
|
19 | 19 | import tokenize |
|
20 | 20 | |
|
21 | 21 | |
|
22 | 22 | def adjusttokenpos(t, ofs): |
|
23 | 23 | """Adjust start/end column of the given token""" |
|
24 | 24 | return t._replace( |
|
25 | 25 | start=(t.start[0], t.start[1] + ofs), end=(t.end[0], t.end[1] + ofs) |
|
26 | 26 | ) |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | def replacetokens(tokens, opts): |
|
30 | 30 | """Transform a stream of tokens from raw to Python 3. |
|
31 | 31 | |
|
32 | 32 | Returns a generator of possibly rewritten tokens. |
|
33 | 33 | |
|
34 | 34 | The input token list may be mutated as part of processing. However, |
|
35 | 35 | its changes do not necessarily match the output token stream. |
|
36 | 36 | """ |
|
37 | 37 | sysstrtokens = set() |
|
38 | 38 | |
|
39 | 39 | # The following utility functions access the tokens list and i index of |
|
40 | 40 | # the for i, t enumerate(tokens) loop below |
|
41 | 41 | def _isop(j, *o): |
|
42 | 42 | """Assert that tokens[j] is an OP with one of the given values""" |
|
43 | 43 | try: |
|
44 | 44 | return tokens[j].type == token.OP and tokens[j].string in o |
|
45 | 45 | except IndexError: |
|
46 | 46 | return False |
|
47 | 47 | |
|
48 | 48 | def _findargnofcall(n): |
|
49 | 49 | """Find arg n of a call expression (start at 0) |
|
50 | 50 | |
|
51 | 51 | Returns index of the first token of that argument, or None if |
|
52 | 52 | there is not that many arguments. |
|
53 | 53 | |
|
54 | 54 | Assumes that token[i + 1] is '('. |
|
55 | 55 | |
|
56 | 56 | """ |
|
57 | 57 | nested = 0 |
|
58 | 58 | for j in range(i + 2, len(tokens)): |
|
59 | 59 | if _isop(j, ')', ']', '}'): |
|
60 | 60 | # end of call, tuple, subscription or dict / set |
|
61 | 61 | nested -= 1 |
|
62 | 62 | if nested < 0: |
|
63 | 63 | return None |
|
64 | 64 | elif n == 0: |
|
65 | 65 | # this is the starting position of arg |
|
66 | 66 | return j |
|
67 | 67 | elif _isop(j, '(', '[', '{'): |
|
68 | 68 | nested += 1 |
|
69 | 69 | elif _isop(j, ',') and nested == 0: |
|
70 | 70 | n -= 1 |
|
71 | 71 | |
|
72 | 72 | return None |
|
73 | 73 | |
|
74 | 74 | def _ensuresysstr(j): |
|
75 | 75 | """Make sure the token at j is a system string |
|
76 | 76 | |
|
77 | 77 | Remember the given token so the string transformer won't add |
|
78 | 78 | the byte prefix. |
|
79 | 79 | |
|
80 | 80 | Ignores tokens that are not strings. Assumes bounds checking has |
|
81 | 81 | already been done. |
|
82 | 82 | |
|
83 | 83 | """ |
|
84 | 84 | k = j |
|
85 | 85 | currtoken = tokens[k] |
|
86 | 86 | while currtoken.type in (token.STRING, token.NEWLINE, tokenize.NL): |
|
87 | 87 | k += 1 |
|
88 | 88 | if currtoken.type == token.STRING and currtoken.string.startswith( |
|
89 | 89 | ("'", '"') |
|
90 | 90 | ): |
|
91 | 91 | sysstrtokens.add(currtoken) |
|
92 | 92 | try: |
|
93 | 93 | currtoken = tokens[k] |
|
94 | 94 | except IndexError: |
|
95 | 95 | break |
|
96 | 96 | |
|
97 | 97 | def _isitemaccess(j): |
|
98 | 98 | """Assert the next tokens form an item access on `tokens[j]` and that |
|
99 | 99 | `tokens[j]` is a name. |
|
100 | 100 | """ |
|
101 | 101 | try: |
|
102 | 102 | return ( |
|
103 | 103 | tokens[j].type == token.NAME |
|
104 | 104 | and _isop(j + 1, '[') |
|
105 | 105 | and tokens[j + 2].type == token.STRING |
|
106 | 106 | and _isop(j + 3, ']') |
|
107 | 107 | ) |
|
108 | 108 | except IndexError: |
|
109 | 109 | return False |
|
110 | 110 | |
|
111 | 111 | def _ismethodcall(j, *methodnames): |
|
112 | 112 | """Assert the next tokens form a call to `methodname` with a string |
|
113 | 113 | as first argument on `tokens[j]` and that `tokens[j]` is a name. |
|
114 | 114 | """ |
|
115 | 115 | try: |
|
116 | 116 | return ( |
|
117 | 117 | tokens[j].type == token.NAME |
|
118 | 118 | and _isop(j + 1, '.') |
|
119 | 119 | and tokens[j + 2].type == token.NAME |
|
120 | 120 | and tokens[j + 2].string in methodnames |
|
121 | 121 | and _isop(j + 3, '(') |
|
122 | 122 | and tokens[j + 4].type == token.STRING |
|
123 | 123 | ) |
|
124 | 124 | except IndexError: |
|
125 | 125 | return False |
|
126 | 126 | |
|
127 | 127 | coldelta = 0 # column increment for new opening parens |
|
128 | 128 | coloffset = -1 # column offset for the current line (-1: TBD) |
|
129 | 129 | parens = [(0, 0, 0, -1)] # stack of (line, end-column, column-offset, type) |
|
130 | 130 | ignorenextline = False # don't transform the next line |
|
131 | 131 | insideignoreblock = False # don't transform until turned off |
|
132 | 132 | for i, t in enumerate(tokens): |
|
133 | 133 | # Compute the column offset for the current line, such that |
|
134 | 134 | # the current line will be aligned to the last opening paren |
|
135 | 135 | # as before. |
|
136 | 136 | if coloffset < 0: |
|
137 | 137 | lastparen = parens[-1] |
|
138 | 138 | if t.start[1] == lastparen[1]: |
|
139 | 139 | coloffset = lastparen[2] |
|
140 | 140 | elif t.start[1] + 1 == lastparen[1] and lastparen[3] not in ( |
|
141 | 141 | token.NEWLINE, |
|
142 | 142 | tokenize.NL, |
|
143 | 143 | ): |
|
144 | 144 | # fix misaligned indent of s/util.Abort/error.Abort/ |
|
145 | 145 | coloffset = lastparen[2] + (lastparen[1] - t.start[1]) |
|
146 | 146 | else: |
|
147 | 147 | coloffset = 0 |
|
148 | 148 | |
|
149 | 149 | # Reset per-line attributes at EOL. |
|
150 | 150 | if t.type in (token.NEWLINE, tokenize.NL): |
|
151 | 151 | yield adjusttokenpos(t, coloffset) |
|
152 | 152 | coldelta = 0 |
|
153 | 153 | coloffset = -1 |
|
154 | 154 | if not insideignoreblock: |
|
155 | 155 | ignorenextline = ( |
|
156 | 156 | tokens[i - 1].type == token.COMMENT |
|
157 | 157 | and tokens[i - 1].string == "# no-py3-transform" |
|
158 | 158 | ) |
|
159 | 159 | continue |
|
160 | 160 | |
|
161 | 161 | if t.type == token.COMMENT: |
|
162 | 162 | if t.string == "# py3-transform: off": |
|
163 | 163 | insideignoreblock = True |
|
164 | 164 | if t.string == "# py3-transform: on": |
|
165 | 165 | insideignoreblock = False |
|
166 | 166 | |
|
167 | 167 | if ignorenextline or insideignoreblock: |
|
168 | 168 | yield adjusttokenpos(t, coloffset) |
|
169 | 169 | continue |
|
170 | 170 | |
|
171 | 171 | # Remember the last paren position. |
|
172 | 172 | if _isop(i, '(', '[', '{'): |
|
173 | 173 | parens.append(t.end + (coloffset + coldelta, tokens[i + 1].type)) |
|
174 | 174 | elif _isop(i, ')', ']', '}'): |
|
175 | 175 | parens.pop() |
|
176 | 176 | |
|
177 | 177 | # Convert most string literals to byte literals. String literals |
|
178 | 178 | # in Python 2 are bytes. String literals in Python 3 are unicode. |
|
179 | 179 | # Most strings in Mercurial are bytes and unicode strings are rare. |
|
180 | 180 | # Rather than rewrite all string literals to use ``b''`` to indicate |
|
181 | 181 | # byte strings, we apply this token transformer to insert the ``b`` |
|
182 | 182 | # prefix nearly everywhere. |
|
183 | 183 | if t.type == token.STRING and t not in sysstrtokens: |
|
184 | 184 | s = t.string |
|
185 | 185 | |
|
186 | 186 | # Preserve docstrings as string literals. This is inconsistent |
|
187 | 187 | # with regular unprefixed strings. However, the |
|
188 | 188 | # "from __future__" parsing (which allows a module docstring to |
|
189 | 189 | # exist before it) doesn't properly handle the docstring if it |
|
190 | 190 | # is b''' prefixed, leading to a SyntaxError. We leave all |
|
191 | 191 | # docstrings as unprefixed to avoid this. This means Mercurial |
|
192 | 192 | # components touching docstrings need to handle unicode, |
|
193 | 193 | # unfortunately. |
|
194 | 194 | if s[0:3] in ("'''", '"""'): |
|
195 | 195 | # If it's assigned to something, it's not a docstring |
|
196 | 196 | if not _isop(i - 1, '='): |
|
197 | 197 | yield adjusttokenpos(t, coloffset) |
|
198 | 198 | continue |
|
199 | 199 | |
|
200 | 200 | # If the first character isn't a quote, it is likely a string |
|
201 | 201 | # prefixing character (such as 'b', 'u', or 'r'. Ignore. |
|
202 | 202 | if s[0] not in ("'", '"'): |
|
203 | 203 | yield adjusttokenpos(t, coloffset) |
|
204 | 204 | continue |
|
205 | 205 | |
|
206 | 206 | # String literal. Prefix to make a b'' string. |
|
207 | 207 | yield adjusttokenpos(t._replace(string='b%s' % t.string), coloffset) |
|
208 | 208 | coldelta += 1 |
|
209 | 209 | continue |
|
210 | 210 | |
|
211 | 211 | # This looks like a function call. |
|
212 | 212 | if t.type == token.NAME and _isop(i + 1, '('): |
|
213 | 213 | fn = t.string |
|
214 | 214 | |
|
215 | 215 | # *attr() builtins don't accept byte strings to 2nd argument. |
|
216 | 216 | if fn in ( |
|
217 | 217 | 'getattr', |
|
218 | 218 | 'setattr', |
|
219 | 219 | 'hasattr', |
|
220 | 220 | 'safehasattr', |
|
221 | 221 | 'wrapfunction', |
|
222 | 222 | 'wrapclass', |
|
223 | 223 | 'addattr', |
|
224 | 224 | ) and (opts['allow-attr-methods'] or not _isop(i - 1, '.')): |
|
225 | 225 | arg1idx = _findargnofcall(1) |
|
226 | 226 | if arg1idx is not None: |
|
227 | 227 | _ensuresysstr(arg1idx) |
|
228 | 228 | |
|
229 | 229 | # .encode() and .decode() on str/bytes/unicode don't accept |
|
230 | 230 | # byte strings on Python 3. |
|
231 | 231 | elif fn in ('encode', 'decode') and _isop(i - 1, '.'): |
|
232 | 232 | for argn in range(2): |
|
233 | 233 | argidx = _findargnofcall(argn) |
|
234 | 234 | if argidx is not None: |
|
235 | 235 | _ensuresysstr(argidx) |
|
236 | 236 | |
|
237 | 237 | # It changes iteritems/values to items/values as they are not |
|
238 | 238 | # present in Python 3 world. |
|
239 | 239 | elif opts['dictiter'] and fn in ('iteritems', 'itervalues'): |
|
240 | 240 | yield adjusttokenpos(t._replace(string=fn[4:]), coloffset) |
|
241 | 241 | continue |
|
242 | 242 | |
|
243 | 243 | if t.type == token.NAME and t.string in opts['treat-as-kwargs']: |
|
244 | 244 | if _isitemaccess(i): |
|
245 | 245 | _ensuresysstr(i + 2) |
|
246 | 246 | if _ismethodcall(i, 'get', 'pop', 'setdefault', 'popitem'): |
|
247 | 247 | _ensuresysstr(i + 4) |
|
248 | 248 | |
|
249 | 249 | # Looks like "if __name__ == '__main__'". |
|
250 | 250 | if ( |
|
251 | 251 | t.type == token.NAME |
|
252 | 252 | and t.string == '__name__' |
|
253 | 253 | and _isop(i + 1, '==') |
|
254 | 254 | ): |
|
255 | 255 | _ensuresysstr(i + 2) |
|
256 | 256 | |
|
257 | 257 | # Emit unmodified token. |
|
258 | 258 | yield adjusttokenpos(t, coloffset) |
|
259 | 259 | |
|
260 | 260 | |
|
261 | 261 | def process(fin, fout, opts): |
|
262 | 262 | tokens = tokenize.tokenize(fin.readline) |
|
263 | 263 | tokens = replacetokens(list(tokens), opts) |
|
264 | 264 | fout.write(tokenize.untokenize(tokens)) |
|
265 | 265 | |
|
266 | 266 | |
|
267 | 267 | def tryunlink(fname): |
|
268 | 268 | try: |
|
269 | 269 | os.unlink(fname) |
|
270 | 270 | except OSError as err: |
|
271 | 271 | if err.errno != errno.ENOENT: |
|
272 | 272 | raise |
|
273 | 273 | |
|
274 | 274 | |
|
275 | 275 | @contextlib.contextmanager |
|
276 | 276 | def editinplace(fname): |
|
277 | 277 | n = os.path.basename(fname) |
|
278 | 278 | d = os.path.dirname(fname) |
|
279 | 279 | fp = tempfile.NamedTemporaryFile( |
|
280 | 280 | prefix='.%s-' % n, suffix='~', dir=d, delete=False |
|
281 | 281 | ) |
|
282 | 282 | try: |
|
283 | 283 | yield fp |
|
284 | 284 | fp.close() |
|
285 | 285 | if os.name == 'nt': |
|
286 | 286 | tryunlink(fname) |
|
287 | 287 | os.rename(fp.name, fname) |
|
288 | 288 | finally: |
|
289 | 289 | fp.close() |
|
290 | 290 | tryunlink(fp.name) |
|
291 | 291 | |
|
292 | 292 | |
|
293 | 293 | def main(): |
|
294 | 294 | ap = argparse.ArgumentParser() |
|
295 | 295 | ap.add_argument( |
|
296 | 296 | '--version', action='version', version='Byteify strings 1.0' |
|
297 | 297 | ) |
|
298 | 298 | ap.add_argument( |
|
299 | 299 | '-i', |
|
300 | 300 | '--inplace', |
|
301 | 301 | action='store_true', |
|
302 | 302 | default=False, |
|
303 | 303 | help='edit files in place', |
|
304 | 304 | ) |
|
305 | 305 | ap.add_argument( |
|
306 | 306 | '--dictiter', |
|
307 | 307 | action='store_true', |
|
308 | 308 | default=False, |
|
309 | 309 | help='rewrite iteritems() and itervalues()', |
|
310 | 310 | ), |
|
311 | 311 | ap.add_argument( |
|
312 | 312 | '--allow-attr-methods', |
|
313 | 313 | action='store_true', |
|
314 | 314 | default=False, |
|
315 | 315 | help='also handle attr*() when they are methods', |
|
316 | 316 | ), |
|
317 | 317 | ap.add_argument( |
|
318 | 318 | '--treat-as-kwargs', |
|
319 | 319 | nargs="+", |
|
320 | 320 | default=[], |
|
321 | 321 | help="ignore kwargs-like objects", |
|
322 | 322 | ), |
|
323 | 323 | ap.add_argument('files', metavar='FILE', nargs='+', help='source file') |
|
324 | 324 | args = ap.parse_args() |
|
325 | 325 | opts = { |
|
326 | 326 | 'dictiter': args.dictiter, |
|
327 | 327 | 'treat-as-kwargs': set(args.treat_as_kwargs), |
|
328 | 328 | 'allow-attr-methods': args.allow_attr_methods, |
|
329 | 329 | } |
|
330 | 330 | for fname in args.files: |
|
331 | 331 | if args.inplace: |
|
332 | 332 | with editinplace(fname) as fout: |
|
333 | 333 | with open(fname, 'rb') as fin: |
|
334 | 334 | process(fin, fout, opts) |
|
335 | 335 | else: |
|
336 | 336 | with open(fname, 'rb') as fin: |
|
337 | 337 | fout = sys.stdout.buffer |
|
338 | 338 | process(fin, fout, opts) |
|
339 | 339 | |
|
340 | 340 | |
|
341 | 341 | if __name__ == '__main__': |
|
342 |
if sys.version_info |
|
|
343 | print('This script must be run under Python 3.') | |
|
342 | if sys.version_info[0:2] < (3, 7): | |
|
343 | print('This script must be run under Python 3.7+') | |
|
344 | 344 | sys.exit(3) |
|
345 | 345 | main() |
@@ -1,15 +1,9 b'' | |||
|
1 | 1 | [fix] |
|
2 | 2 | clang-format:command = clang-format --style file -i |
|
3 | 3 | clang-format:pattern = (**.c or **.cc or **.h) and not "listfile:contrib/clang-format-ignorelist" |
|
4 | 4 | |
|
5 | 5 | rustfmt:command = rustfmt {rootpath} |
|
6 | 6 | rustfmt:pattern = set:**.rs |
|
7 | 7 | |
|
8 | # We use black, but currently with | |
|
9 | # https://github.com/psf/black/pull/826 applied. For now | |
|
10 | # contrib/grey.py is our fork of black. You need to pip install | |
|
11 | # git+https://github.com/python/black/@d9e71a75ccfefa3d9156a64c03313a0d4ad981e5 | |
|
12 | # to have the dependencies for grey. | |
|
13 | # | |
|
14 | # black:command = python3.7 contrib/grey.py --config=black.toml - | |
|
15 | # black:pattern = set:**.py - hgext/fsmonitor/pywatchman/** - mercurial/thirdparty/** - "contrib/python-zstandard/** - contrib/grey.py" | |
|
8 | black:command = black --config=black.toml - | |
|
9 | black:pattern = set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**" |
@@ -1,813 +1,818 b'' | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | |
|
3 | 3 | from __future__ import absolute_import, print_function |
|
4 | 4 | |
|
5 | 5 | import ast |
|
6 | 6 | import collections |
|
7 | import io | |
|
7 | 8 | import os |
|
8 | 9 | import sys |
|
9 | 10 | |
|
10 | 11 | # Import a minimal set of stdlib modules needed for list_stdlib_modules() |
|
11 | 12 | # to work when run from a virtualenv. The modules were chosen empirically |
|
12 | 13 | # so that the return value matches the return value without virtualenv. |
|
13 | 14 | if True: # disable lexical sorting checks |
|
14 | 15 | try: |
|
15 | 16 | import BaseHTTPServer as basehttpserver |
|
16 | 17 | except ImportError: |
|
17 | 18 | basehttpserver = None |
|
18 | 19 | import zlib |
|
19 | 20 | |
|
20 | 21 | import testparseutil |
|
21 | 22 | |
|
22 | 23 | # Whitelist of modules that symbols can be directly imported from. |
|
23 | 24 | allowsymbolimports = ( |
|
24 | 25 | '__future__', |
|
25 | 26 | 'bzrlib', |
|
26 | 27 | 'hgclient', |
|
27 | 28 | 'mercurial', |
|
28 | 29 | 'mercurial.hgweb.common', |
|
29 | 30 | 'mercurial.hgweb.request', |
|
30 | 31 | 'mercurial.i18n', |
|
31 | 32 | 'mercurial.interfaces', |
|
32 | 33 | 'mercurial.node', |
|
33 | 34 | 'mercurial.pycompat', |
|
34 | 35 | # for revlog to re-export constant to extensions |
|
35 | 36 | 'mercurial.revlogutils.constants', |
|
36 | 37 | 'mercurial.revlogutils.flagutil', |
|
37 | 38 | # for cffi modules to re-export pure functions |
|
38 | 39 | 'mercurial.pure.base85', |
|
39 | 40 | 'mercurial.pure.bdiff', |
|
40 | 41 | 'mercurial.pure.mpatch', |
|
41 | 42 | 'mercurial.pure.osutil', |
|
42 | 43 | 'mercurial.pure.parsers', |
|
43 | 44 | # third-party imports should be directly imported |
|
44 | 45 | 'mercurial.thirdparty', |
|
45 | 46 | 'mercurial.thirdparty.attr', |
|
46 | 47 | 'mercurial.thirdparty.zope', |
|
47 | 48 | 'mercurial.thirdparty.zope.interface', |
|
48 | 49 | ) |
|
49 | 50 | |
|
50 | 51 | # Whitelist of symbols that can be directly imported. |
|
51 | 52 | directsymbols = ('demandimport',) |
|
52 | 53 | |
|
53 | 54 | # Modules that must be aliased because they are commonly confused with |
|
54 | 55 | # common variables and can create aliasing and readability issues. |
|
55 | 56 | requirealias = { |
|
56 | 57 | 'ui': 'uimod', |
|
57 | 58 | } |
|
58 | 59 | |
|
59 | 60 | |
|
60 | 61 | def usingabsolute(root): |
|
61 | 62 | """Whether absolute imports are being used.""" |
|
62 | 63 | if sys.version_info[0] >= 3: |
|
63 | 64 | return True |
|
64 | 65 | |
|
65 | 66 | for node in ast.walk(root): |
|
66 | 67 | if isinstance(node, ast.ImportFrom): |
|
67 | 68 | if node.module == '__future__': |
|
68 | 69 | for n in node.names: |
|
69 | 70 | if n.name == 'absolute_import': |
|
70 | 71 | return True |
|
71 | 72 | |
|
72 | 73 | return False |
|
73 | 74 | |
|
74 | 75 | |
|
75 | 76 | def walklocal(root): |
|
76 | 77 | """Recursively yield all descendant nodes but not in a different scope""" |
|
77 | 78 | todo = collections.deque(ast.iter_child_nodes(root)) |
|
78 | 79 | yield root, False |
|
79 | 80 | while todo: |
|
80 | 81 | node = todo.popleft() |
|
81 | 82 | newscope = isinstance(node, ast.FunctionDef) |
|
82 | 83 | if not newscope: |
|
83 | 84 | todo.extend(ast.iter_child_nodes(node)) |
|
84 | 85 | yield node, newscope |
|
85 | 86 | |
|
86 | 87 | |
|
87 | 88 | def dotted_name_of_path(path): |
|
88 | 89 | """Given a relative path to a source file, return its dotted module name. |
|
89 | 90 | |
|
90 | 91 | >>> dotted_name_of_path('mercurial/error.py') |
|
91 | 92 | 'mercurial.error' |
|
92 | 93 | >>> dotted_name_of_path('zlibmodule.so') |
|
93 | 94 | 'zlib' |
|
94 | 95 | """ |
|
95 | 96 | parts = path.replace(os.sep, '/').split('/') |
|
96 | 97 | parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so |
|
97 | 98 | if parts[-1].endswith('module'): |
|
98 | 99 | parts[-1] = parts[-1][:-6] |
|
99 | 100 | return '.'.join(parts) |
|
100 | 101 | |
|
101 | 102 | |
|
102 | 103 | def fromlocalfunc(modulename, localmods): |
|
103 | 104 | """Get a function to examine which locally defined module the |
|
104 | 105 | target source imports via a specified name. |
|
105 | 106 | |
|
106 | 107 | `modulename` is an `dotted_name_of_path()`-ed source file path, |
|
107 | 108 | which may have `.__init__` at the end of it, of the target source. |
|
108 | 109 | |
|
109 | 110 | `localmods` is a set of absolute `dotted_name_of_path()`-ed source file |
|
110 | 111 | paths of locally defined (= Mercurial specific) modules. |
|
111 | 112 | |
|
112 | 113 | This function assumes that module names not existing in |
|
113 | 114 | `localmods` are from the Python standard library. |
|
114 | 115 | |
|
115 | 116 | This function returns the function, which takes `name` argument, |
|
116 | 117 | and returns `(absname, dottedpath, hassubmod)` tuple if `name` |
|
117 | 118 | matches against locally defined module. Otherwise, it returns |
|
118 | 119 | False. |
|
119 | 120 | |
|
120 | 121 | It is assumed that `name` doesn't have `.__init__`. |
|
121 | 122 | |
|
122 | 123 | `absname` is an absolute module name of specified `name` |
|
123 | 124 | (e.g. "hgext.convert"). This can be used to compose prefix for sub |
|
124 | 125 | modules or so. |
|
125 | 126 | |
|
126 | 127 | `dottedpath` is a `dotted_name_of_path()`-ed source file path |
|
127 | 128 | (e.g. "hgext.convert.__init__") of `name`. This is used to look |
|
128 | 129 | module up in `localmods` again. |
|
129 | 130 | |
|
130 | 131 | `hassubmod` is whether it may have sub modules under it (for |
|
131 | 132 | convenient, even though this is also equivalent to "absname != |
|
132 | 133 | dottednpath") |
|
133 | 134 | |
|
134 | 135 | >>> localmods = {'foo.__init__', 'foo.foo1', |
|
135 | 136 | ... 'foo.bar.__init__', 'foo.bar.bar1', |
|
136 | 137 | ... 'baz.__init__', 'baz.baz1'} |
|
137 | 138 | >>> fromlocal = fromlocalfunc('foo.xxx', localmods) |
|
138 | 139 | >>> # relative |
|
139 | 140 | >>> fromlocal('foo1') |
|
140 | 141 | ('foo.foo1', 'foo.foo1', False) |
|
141 | 142 | >>> fromlocal('bar') |
|
142 | 143 | ('foo.bar', 'foo.bar.__init__', True) |
|
143 | 144 | >>> fromlocal('bar.bar1') |
|
144 | 145 | ('foo.bar.bar1', 'foo.bar.bar1', False) |
|
145 | 146 | >>> # absolute |
|
146 | 147 | >>> fromlocal('baz') |
|
147 | 148 | ('baz', 'baz.__init__', True) |
|
148 | 149 | >>> fromlocal('baz.baz1') |
|
149 | 150 | ('baz.baz1', 'baz.baz1', False) |
|
150 | 151 | >>> # unknown = maybe standard library |
|
151 | 152 | >>> fromlocal('os') |
|
152 | 153 | False |
|
153 | 154 | >>> fromlocal(None, 1) |
|
154 | 155 | ('foo', 'foo.__init__', True) |
|
155 | 156 | >>> fromlocal('foo1', 1) |
|
156 | 157 | ('foo.foo1', 'foo.foo1', False) |
|
157 | 158 | >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods) |
|
158 | 159 | >>> fromlocal2(None, 2) |
|
159 | 160 | ('foo', 'foo.__init__', True) |
|
160 | 161 | >>> fromlocal2('bar2', 1) |
|
161 | 162 | False |
|
162 | 163 | >>> fromlocal2('bar', 2) |
|
163 | 164 | ('foo.bar', 'foo.bar.__init__', True) |
|
164 | 165 | """ |
|
165 | 166 | if not isinstance(modulename, str): |
|
166 | 167 | modulename = modulename.decode('ascii') |
|
167 | 168 | prefix = '.'.join(modulename.split('.')[:-1]) |
|
168 | 169 | if prefix: |
|
169 | 170 | prefix += '.' |
|
170 | 171 | |
|
171 | 172 | def fromlocal(name, level=0): |
|
172 | 173 | # name is false value when relative imports are used. |
|
173 | 174 | if not name: |
|
174 | 175 | # If relative imports are used, level must not be absolute. |
|
175 | 176 | assert level > 0 |
|
176 | 177 | candidates = ['.'.join(modulename.split('.')[:-level])] |
|
177 | 178 | else: |
|
178 | 179 | if not level: |
|
179 | 180 | # Check relative name first. |
|
180 | 181 | candidates = [prefix + name, name] |
|
181 | 182 | else: |
|
182 | 183 | candidates = [ |
|
183 | 184 | '.'.join(modulename.split('.')[:-level]) + '.' + name |
|
184 | 185 | ] |
|
185 | 186 | |
|
186 | 187 | for n in candidates: |
|
187 | 188 | if n in localmods: |
|
188 | 189 | return (n, n, False) |
|
189 | 190 | dottedpath = n + '.__init__' |
|
190 | 191 | if dottedpath in localmods: |
|
191 | 192 | return (n, dottedpath, True) |
|
192 | 193 | return False |
|
193 | 194 | |
|
194 | 195 | return fromlocal |
|
195 | 196 | |
|
196 | 197 | |
|
197 | 198 | def populateextmods(localmods): |
|
198 | 199 | """Populate C extension modules based on pure modules""" |
|
199 | 200 | newlocalmods = set(localmods) |
|
200 | 201 | for n in localmods: |
|
201 | 202 | if n.startswith('mercurial.pure.'): |
|
202 | 203 | m = n[len('mercurial.pure.') :] |
|
203 | 204 | newlocalmods.add('mercurial.cext.' + m) |
|
204 | 205 | newlocalmods.add('mercurial.cffi._' + m) |
|
205 | 206 | return newlocalmods |
|
206 | 207 | |
|
207 | 208 | |
|
208 | 209 | def list_stdlib_modules(): |
|
209 | 210 | """List the modules present in the stdlib. |
|
210 | 211 | |
|
211 | 212 | >>> py3 = sys.version_info[0] >= 3 |
|
212 | 213 | >>> mods = set(list_stdlib_modules()) |
|
213 | 214 | >>> 'BaseHTTPServer' in mods or py3 |
|
214 | 215 | True |
|
215 | 216 | |
|
216 | 217 | os.path isn't really a module, so it's missing: |
|
217 | 218 | |
|
218 | 219 | >>> 'os.path' in mods |
|
219 | 220 | False |
|
220 | 221 | |
|
221 | 222 | sys requires special treatment, because it's baked into the |
|
222 | 223 | interpreter, but it should still appear: |
|
223 | 224 | |
|
224 | 225 | >>> 'sys' in mods |
|
225 | 226 | True |
|
226 | 227 | |
|
227 | 228 | >>> 'collections' in mods |
|
228 | 229 | True |
|
229 | 230 | |
|
230 | 231 | >>> 'cStringIO' in mods or py3 |
|
231 | 232 | True |
|
232 | 233 | |
|
233 | 234 | >>> 'cffi' in mods |
|
234 | 235 | True |
|
235 | 236 | """ |
|
236 | 237 | for m in sys.builtin_module_names: |
|
237 | 238 | yield m |
|
238 | 239 | # These modules only exist on windows, but we should always |
|
239 | 240 | # consider them stdlib. |
|
240 | 241 | for m in ['msvcrt', '_winreg']: |
|
241 | 242 | yield m |
|
242 | 243 | yield '__builtin__' |
|
243 | 244 | yield 'builtins' # python3 only |
|
244 | 245 | yield 'importlib.abc' # python3 only |
|
245 | 246 | yield 'importlib.machinery' # python3 only |
|
246 | 247 | yield 'importlib.util' # python3 only |
|
247 | 248 | for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only |
|
248 | 249 | yield m |
|
249 | 250 | for m in 'cPickle', 'datetime': # in Python (not C) on PyPy |
|
250 | 251 | yield m |
|
251 | 252 | for m in ['cffi']: |
|
252 | 253 | yield m |
|
253 | 254 | stdlib_prefixes = {sys.prefix, sys.exec_prefix} |
|
254 | 255 | # We need to supplement the list of prefixes for the search to work |
|
255 | 256 | # when run from within a virtualenv. |
|
256 | 257 | for mod in (basehttpserver, zlib): |
|
257 | 258 | if mod is None: |
|
258 | 259 | continue |
|
259 | 260 | try: |
|
260 | 261 | # Not all module objects have a __file__ attribute. |
|
261 | 262 | filename = mod.__file__ |
|
262 | 263 | except AttributeError: |
|
263 | 264 | continue |
|
264 | 265 | dirname = os.path.dirname(filename) |
|
265 | 266 | for prefix in stdlib_prefixes: |
|
266 | 267 | if dirname.startswith(prefix): |
|
267 | 268 | # Then this directory is redundant. |
|
268 | 269 | break |
|
269 | 270 | else: |
|
270 | 271 | stdlib_prefixes.add(dirname) |
|
271 | 272 | sourceroot = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) |
|
272 | 273 | for libpath in sys.path: |
|
273 | 274 | # We want to walk everything in sys.path that starts with something in |
|
274 | 275 | # stdlib_prefixes, but not directories from the hg sources. |
|
275 | 276 | if os.path.abspath(libpath).startswith(sourceroot) or not any( |
|
276 | 277 | libpath.startswith(p) for p in stdlib_prefixes |
|
277 | 278 | ): |
|
278 | 279 | continue |
|
279 | 280 | for top, dirs, files in os.walk(libpath): |
|
280 | 281 | for i, d in reversed(list(enumerate(dirs))): |
|
281 | 282 | if ( |
|
282 | 283 | not os.path.exists(os.path.join(top, d, '__init__.py')) |
|
283 | 284 | or top == libpath |
|
284 | 285 | and d in ('hgdemandimport', 'hgext', 'mercurial') |
|
285 | 286 | ): |
|
286 | 287 | del dirs[i] |
|
287 | 288 | for name in files: |
|
288 | 289 | if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')): |
|
289 | 290 | continue |
|
290 | 291 | if name.startswith('__init__.py'): |
|
291 | 292 | full_path = top |
|
292 | 293 | else: |
|
293 | 294 | full_path = os.path.join(top, name) |
|
294 | 295 | rel_path = full_path[len(libpath) + 1 :] |
|
295 | 296 | mod = dotted_name_of_path(rel_path) |
|
296 | 297 | yield mod |
|
297 | 298 | |
|
298 | 299 | |
|
299 | 300 | stdlib_modules = set(list_stdlib_modules()) |
|
300 | 301 | |
|
301 | 302 | |
|
302 | 303 | def imported_modules(source, modulename, f, localmods, ignore_nested=False): |
|
303 | 304 | """Given the source of a file as a string, yield the names |
|
304 | 305 | imported by that file. |
|
305 | 306 | |
|
306 | 307 | Args: |
|
307 | 308 | source: The python source to examine as a string. |
|
308 | 309 | modulename: of specified python source (may have `__init__`) |
|
309 | 310 | localmods: set of locally defined module names (may have `__init__`) |
|
310 | 311 | ignore_nested: If true, import statements that do not start in |
|
311 | 312 | column zero will be ignored. |
|
312 | 313 | |
|
313 | 314 | Returns: |
|
314 | 315 | A list of absolute module names imported by the given source. |
|
315 | 316 | |
|
316 | 317 | >>> f = 'foo/xxx.py' |
|
317 | 318 | >>> modulename = 'foo.xxx' |
|
318 | 319 | >>> localmods = {'foo.__init__': True, |
|
319 | 320 | ... 'foo.foo1': True, 'foo.foo2': True, |
|
320 | 321 | ... 'foo.bar.__init__': True, 'foo.bar.bar1': True, |
|
321 | 322 | ... 'baz.__init__': True, 'baz.baz1': True } |
|
322 | 323 | >>> # standard library (= not locally defined ones) |
|
323 | 324 | >>> sorted(imported_modules( |
|
324 | 325 | ... 'from stdlib1 import foo, bar; import stdlib2', |
|
325 | 326 | ... modulename, f, localmods)) |
|
326 | 327 | [] |
|
327 | 328 | >>> # relative importing |
|
328 | 329 | >>> sorted(imported_modules( |
|
329 | 330 | ... 'import foo1; from bar import bar1', |
|
330 | 331 | ... modulename, f, localmods)) |
|
331 | 332 | ['foo.bar.bar1', 'foo.foo1'] |
|
332 | 333 | >>> sorted(imported_modules( |
|
333 | 334 | ... 'from bar.bar1 import name1, name2, name3', |
|
334 | 335 | ... modulename, f, localmods)) |
|
335 | 336 | ['foo.bar.bar1'] |
|
336 | 337 | >>> # absolute importing |
|
337 | 338 | >>> sorted(imported_modules( |
|
338 | 339 | ... 'from baz import baz1, name1', |
|
339 | 340 | ... modulename, f, localmods)) |
|
340 | 341 | ['baz.__init__', 'baz.baz1'] |
|
341 | 342 | >>> # mixed importing, even though it shouldn't be recommended |
|
342 | 343 | >>> sorted(imported_modules( |
|
343 | 344 | ... 'import stdlib, foo1, baz', |
|
344 | 345 | ... modulename, f, localmods)) |
|
345 | 346 | ['baz.__init__', 'foo.foo1'] |
|
346 | 347 | >>> # ignore_nested |
|
347 | 348 | >>> sorted(imported_modules( |
|
348 | 349 | ... '''import foo |
|
349 | 350 | ... def wat(): |
|
350 | 351 | ... import bar |
|
351 | 352 | ... ''', modulename, f, localmods)) |
|
352 | 353 | ['foo.__init__', 'foo.bar.__init__'] |
|
353 | 354 | >>> sorted(imported_modules( |
|
354 | 355 | ... '''import foo |
|
355 | 356 | ... def wat(): |
|
356 | 357 | ... import bar |
|
357 | 358 | ... ''', modulename, f, localmods, ignore_nested=True)) |
|
358 | 359 | ['foo.__init__'] |
|
359 | 360 | """ |
|
360 | 361 | fromlocal = fromlocalfunc(modulename, localmods) |
|
361 | 362 | for node in ast.walk(ast.parse(source, f)): |
|
362 | 363 | if ignore_nested and getattr(node, 'col_offset', 0) > 0: |
|
363 | 364 | continue |
|
364 | 365 | if isinstance(node, ast.Import): |
|
365 | 366 | for n in node.names: |
|
366 | 367 | found = fromlocal(n.name) |
|
367 | 368 | if not found: |
|
368 | 369 | # this should import standard library |
|
369 | 370 | continue |
|
370 | 371 | yield found[1] |
|
371 | 372 | elif isinstance(node, ast.ImportFrom): |
|
372 | 373 | found = fromlocal(node.module, node.level) |
|
373 | 374 | if not found: |
|
374 | 375 | # this should import standard library |
|
375 | 376 | continue |
|
376 | 377 | |
|
377 | 378 | absname, dottedpath, hassubmod = found |
|
378 | 379 | if not hassubmod: |
|
379 | 380 | # "dottedpath" is not a package; must be imported |
|
380 | 381 | yield dottedpath |
|
381 | 382 | # examination of "node.names" should be redundant |
|
382 | 383 | # e.g.: from mercurial.node import nullid, nullrev |
|
383 | 384 | continue |
|
384 | 385 | |
|
385 | 386 | modnotfound = False |
|
386 | 387 | prefix = absname + '.' |
|
387 | 388 | for n in node.names: |
|
388 | 389 | found = fromlocal(prefix + n.name) |
|
389 | 390 | if not found: |
|
390 | 391 | # this should be a function or a property of "node.module" |
|
391 | 392 | modnotfound = True |
|
392 | 393 | continue |
|
393 | 394 | yield found[1] |
|
394 | 395 | if modnotfound: |
|
395 | 396 | # "dottedpath" is a package, but imported because of non-module |
|
396 | 397 | # lookup |
|
397 | 398 | yield dottedpath |
|
398 | 399 | |
|
399 | 400 | |
|
400 | 401 | def verify_import_convention(module, source, localmods): |
|
401 | 402 | """Verify imports match our established coding convention. |
|
402 | 403 | |
|
403 | 404 | We have 2 conventions: legacy and modern. The modern convention is in |
|
404 | 405 | effect when using absolute imports. |
|
405 | 406 | |
|
406 | 407 | The legacy convention only looks for mixed imports. The modern convention |
|
407 | 408 | is much more thorough. |
|
408 | 409 | """ |
|
409 | 410 | root = ast.parse(source) |
|
410 | 411 | absolute = usingabsolute(root) |
|
411 | 412 | |
|
412 | 413 | if absolute: |
|
413 | 414 | return verify_modern_convention(module, root, localmods) |
|
414 | 415 | else: |
|
415 | 416 | return verify_stdlib_on_own_line(root) |
|
416 | 417 | |
|
417 | 418 | |
|
418 | 419 | def verify_modern_convention(module, root, localmods, root_col_offset=0): |
|
419 | 420 | """Verify a file conforms to the modern import convention rules. |
|
420 | 421 | |
|
421 | 422 | The rules of the modern convention are: |
|
422 | 423 | |
|
423 | 424 | * Ordering is stdlib followed by local imports. Each group is lexically |
|
424 | 425 | sorted. |
|
425 | 426 | * Importing multiple modules via "import X, Y" is not allowed: use |
|
426 | 427 | separate import statements. |
|
427 | 428 | * Importing multiple modules via "from X import ..." is allowed if using |
|
428 | 429 | parenthesis and one entry per line. |
|
429 | 430 | * Only 1 relative import statement per import level ("from .", "from ..") |
|
430 | 431 | is allowed. |
|
431 | 432 | * Relative imports from higher levels must occur before lower levels. e.g. |
|
432 | 433 | "from .." must be before "from .". |
|
433 | 434 | * Imports from peer packages should use relative import (e.g. do not |
|
434 | 435 | "import mercurial.foo" from a "mercurial.*" module). |
|
435 | 436 | * Symbols can only be imported from specific modules (see |
|
436 | 437 | `allowsymbolimports`). For other modules, first import the module then |
|
437 | 438 | assign the symbol to a module-level variable. In addition, these imports |
|
438 | 439 | must be performed before other local imports. This rule only |
|
439 | 440 | applies to import statements outside of any blocks. |
|
440 | 441 | * Relative imports from the standard library are not allowed, unless that |
|
441 | 442 | library is also a local module. |
|
442 | 443 | * Certain modules must be aliased to alternate names to avoid aliasing |
|
443 | 444 | and readability problems. See `requirealias`. |
|
444 | 445 | """ |
|
445 | 446 | if not isinstance(module, str): |
|
446 | 447 | module = module.decode('ascii') |
|
447 | 448 | topmodule = module.split('.')[0] |
|
448 | 449 | fromlocal = fromlocalfunc(module, localmods) |
|
449 | 450 | |
|
450 | 451 | # Whether a local/non-stdlib import has been performed. |
|
451 | 452 | seenlocal = None |
|
452 | 453 | # Whether a local/non-stdlib, non-symbol import has been seen. |
|
453 | 454 | seennonsymbollocal = False |
|
454 | 455 | # The last name to be imported (for sorting). |
|
455 | 456 | lastname = None |
|
456 | 457 | laststdlib = None |
|
457 | 458 | # Relative import levels encountered so far. |
|
458 | 459 | seenlevels = set() |
|
459 | 460 | |
|
460 | 461 | for node, newscope in walklocal(root): |
|
461 | 462 | |
|
462 | 463 | def msg(fmt, *args): |
|
463 | 464 | return (fmt % args, node.lineno) |
|
464 | 465 | |
|
465 | 466 | if newscope: |
|
466 | 467 | # Check for local imports in function |
|
467 | 468 | for r in verify_modern_convention( |
|
468 | 469 | module, node, localmods, node.col_offset + 4 |
|
469 | 470 | ): |
|
470 | 471 | yield r |
|
471 | 472 | elif isinstance(node, ast.Import): |
|
472 | 473 | # Disallow "import foo, bar" and require separate imports |
|
473 | 474 | # for each module. |
|
474 | 475 | if len(node.names) > 1: |
|
475 | 476 | yield msg( |
|
476 | 477 | 'multiple imported names: %s', |
|
477 | 478 | ', '.join(n.name for n in node.names), |
|
478 | 479 | ) |
|
479 | 480 | |
|
480 | 481 | name = node.names[0].name |
|
481 | 482 | asname = node.names[0].asname |
|
482 | 483 | |
|
483 | 484 | stdlib = name in stdlib_modules |
|
484 | 485 | |
|
485 | 486 | # Ignore sorting rules on imports inside blocks. |
|
486 | 487 | if node.col_offset == root_col_offset: |
|
487 | 488 | if lastname and name < lastname and laststdlib == stdlib: |
|
488 | 489 | yield msg( |
|
489 | 490 | 'imports not lexically sorted: %s < %s', name, lastname |
|
490 | 491 | ) |
|
491 | 492 | |
|
492 | 493 | lastname = name |
|
493 | 494 | laststdlib = stdlib |
|
494 | 495 | |
|
495 | 496 | # stdlib imports should be before local imports. |
|
496 | 497 | if stdlib and seenlocal and node.col_offset == root_col_offset: |
|
497 | 498 | yield msg( |
|
498 | 499 | 'stdlib import "%s" follows local import: %s', |
|
499 | 500 | name, |
|
500 | 501 | seenlocal, |
|
501 | 502 | ) |
|
502 | 503 | |
|
503 | 504 | if not stdlib: |
|
504 | 505 | seenlocal = name |
|
505 | 506 | |
|
506 | 507 | # Import of sibling modules should use relative imports. |
|
507 | 508 | topname = name.split('.')[0] |
|
508 | 509 | if topname == topmodule: |
|
509 | 510 | yield msg('import should be relative: %s', name) |
|
510 | 511 | |
|
511 | 512 | if name in requirealias and asname != requirealias[name]: |
|
512 | 513 | yield msg( |
|
513 | 514 | '%s module must be "as" aliased to %s', |
|
514 | 515 | name, |
|
515 | 516 | requirealias[name], |
|
516 | 517 | ) |
|
517 | 518 | |
|
518 | 519 | elif isinstance(node, ast.ImportFrom): |
|
519 | 520 | # Resolve the full imported module name. |
|
520 | 521 | if node.level > 0: |
|
521 | 522 | fullname = '.'.join(module.split('.')[: -node.level]) |
|
522 | 523 | if node.module: |
|
523 | 524 | fullname += '.%s' % node.module |
|
524 | 525 | else: |
|
525 | 526 | assert node.module |
|
526 | 527 | fullname = node.module |
|
527 | 528 | |
|
528 | 529 | topname = fullname.split('.')[0] |
|
529 | 530 | if topname == topmodule: |
|
530 | 531 | yield msg('import should be relative: %s', fullname) |
|
531 | 532 | |
|
532 | 533 | # __future__ is special since it needs to come first and use |
|
533 | 534 | # symbol import. |
|
534 | 535 | if fullname != '__future__': |
|
535 | 536 | if not fullname or ( |
|
536 | 537 | fullname in stdlib_modules |
|
537 | 538 | and fullname not in localmods |
|
538 | 539 | and fullname + '.__init__' not in localmods |
|
539 | 540 | ): |
|
540 | 541 | yield msg('relative import of stdlib module') |
|
541 | 542 | else: |
|
542 | 543 | seenlocal = fullname |
|
543 | 544 | |
|
544 | 545 | # Direct symbol import is only allowed from certain modules and |
|
545 | 546 | # must occur before non-symbol imports. |
|
546 | 547 | found = fromlocal(node.module, node.level) |
|
547 | 548 | if found and found[2]: # node.module is a package |
|
548 | 549 | prefix = found[0] + '.' |
|
549 | 550 | symbols = ( |
|
550 | 551 | n.name for n in node.names if not fromlocal(prefix + n.name) |
|
551 | 552 | ) |
|
552 | 553 | else: |
|
553 | 554 | symbols = (n.name for n in node.names) |
|
554 | 555 | symbols = [sym for sym in symbols if sym not in directsymbols] |
|
555 | 556 | if node.module and node.col_offset == root_col_offset: |
|
556 | 557 | if symbols and fullname not in allowsymbolimports: |
|
557 | 558 | yield msg( |
|
558 | 559 | 'direct symbol import %s from %s', |
|
559 | 560 | ', '.join(symbols), |
|
560 | 561 | fullname, |
|
561 | 562 | ) |
|
562 | 563 | |
|
563 | 564 | if symbols and seennonsymbollocal: |
|
564 | 565 | yield msg( |
|
565 | 566 | 'symbol import follows non-symbol import: %s', fullname |
|
566 | 567 | ) |
|
567 | 568 | if not symbols and fullname not in stdlib_modules: |
|
568 | 569 | seennonsymbollocal = True |
|
569 | 570 | |
|
570 | 571 | if not node.module: |
|
571 | 572 | assert node.level |
|
572 | 573 | |
|
573 | 574 | # Only allow 1 group per level. |
|
574 | 575 | if ( |
|
575 | 576 | node.level in seenlevels |
|
576 | 577 | and node.col_offset == root_col_offset |
|
577 | 578 | ): |
|
578 | 579 | yield msg( |
|
579 | 580 | 'multiple "from %s import" statements', '.' * node.level |
|
580 | 581 | ) |
|
581 | 582 | |
|
582 | 583 | # Higher-level groups come before lower-level groups. |
|
583 | 584 | if any(node.level > l for l in seenlevels): |
|
584 | 585 | yield msg( |
|
585 | 586 | 'higher-level import should come first: %s', fullname |
|
586 | 587 | ) |
|
587 | 588 | |
|
588 | 589 | seenlevels.add(node.level) |
|
589 | 590 | |
|
590 | 591 | # Entries in "from .X import ( ... )" lists must be lexically |
|
591 | 592 | # sorted. |
|
592 | 593 | lastentryname = None |
|
593 | 594 | |
|
594 | 595 | for n in node.names: |
|
595 | 596 | if lastentryname and n.name < lastentryname: |
|
596 | 597 | yield msg( |
|
597 | 598 | 'imports from %s not lexically sorted: %s < %s', |
|
598 | 599 | fullname, |
|
599 | 600 | n.name, |
|
600 | 601 | lastentryname, |
|
601 | 602 | ) |
|
602 | 603 | |
|
603 | 604 | lastentryname = n.name |
|
604 | 605 | |
|
605 | 606 | if n.name in requirealias and n.asname != requirealias[n.name]: |
|
606 | 607 | yield msg( |
|
607 | 608 | '%s from %s must be "as" aliased to %s', |
|
608 | 609 | n.name, |
|
609 | 610 | fullname, |
|
610 | 611 | requirealias[n.name], |
|
611 | 612 | ) |
|
612 | 613 | |
|
613 | 614 | |
|
614 | 615 | def verify_stdlib_on_own_line(root): |
|
615 | 616 | """Given some python source, verify that stdlib imports are done |
|
616 | 617 | in separate statements from relative local module imports. |
|
617 | 618 | |
|
618 | 619 | >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo'))) |
|
619 | 620 | [('mixed imports\\n stdlib: sys\\n relative: foo', 1)] |
|
620 | 621 | >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os'))) |
|
621 | 622 | [] |
|
622 | 623 | >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar'))) |
|
623 | 624 | [] |
|
624 | 625 | """ |
|
625 | 626 | for node in ast.walk(root): |
|
626 | 627 | if isinstance(node, ast.Import): |
|
627 | 628 | from_stdlib = {False: [], True: []} |
|
628 | 629 | for n in node.names: |
|
629 | 630 | from_stdlib[n.name in stdlib_modules].append(n.name) |
|
630 | 631 | if from_stdlib[True] and from_stdlib[False]: |
|
631 | 632 | yield ( |
|
632 | 633 | 'mixed imports\n stdlib: %s\n relative: %s' |
|
633 | 634 | % ( |
|
634 | 635 | ', '.join(sorted(from_stdlib[True])), |
|
635 | 636 | ', '.join(sorted(from_stdlib[False])), |
|
636 | 637 | ), |
|
637 | 638 | node.lineno, |
|
638 | 639 | ) |
|
639 | 640 | |
|
640 | 641 | |
|
641 | 642 | class CircularImport(Exception): |
|
642 | 643 | pass |
|
643 | 644 | |
|
644 | 645 | |
|
645 | 646 | def checkmod(mod, imports): |
|
646 | 647 | shortest = {} |
|
647 | 648 | visit = [[mod]] |
|
648 | 649 | while visit: |
|
649 | 650 | path = visit.pop(0) |
|
650 | 651 | for i in sorted(imports.get(path[-1], [])): |
|
651 | 652 | if len(path) < shortest.get(i, 1000): |
|
652 | 653 | shortest[i] = len(path) |
|
653 | 654 | if i in path: |
|
654 | 655 | if i == path[0]: |
|
655 | 656 | raise CircularImport(path) |
|
656 | 657 | continue |
|
657 | 658 | visit.append(path + [i]) |
|
658 | 659 | |
|
659 | 660 | |
|
660 | 661 | def rotatecycle(cycle): |
|
661 | 662 | """arrange a cycle so that the lexicographically first module listed first |
|
662 | 663 | |
|
663 | 664 | >>> rotatecycle(['foo', 'bar']) |
|
664 | 665 | ['bar', 'foo', 'bar'] |
|
665 | 666 | """ |
|
666 | 667 | lowest = min(cycle) |
|
667 | 668 | idx = cycle.index(lowest) |
|
668 | 669 | return cycle[idx:] + cycle[:idx] + [lowest] |
|
669 | 670 | |
|
670 | 671 | |
|
671 | 672 | def find_cycles(imports): |
|
672 | 673 | """Find cycles in an already-loaded import graph. |
|
673 | 674 | |
|
674 | 675 | All module names recorded in `imports` should be absolute one. |
|
675 | 676 | |
|
676 | 677 | >>> from __future__ import print_function |
|
677 | 678 | >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'], |
|
678 | 679 | ... 'top.bar': ['top.baz', 'sys'], |
|
679 | 680 | ... 'top.baz': ['top.foo'], |
|
680 | 681 | ... 'top.qux': ['top.foo']} |
|
681 | 682 | >>> print('\\n'.join(sorted(find_cycles(imports)))) |
|
682 | 683 | top.bar -> top.baz -> top.foo -> top.bar |
|
683 | 684 | top.foo -> top.qux -> top.foo |
|
684 | 685 | """ |
|
685 | 686 | cycles = set() |
|
686 | 687 | for mod in sorted(imports.keys()): |
|
687 | 688 | try: |
|
688 | 689 | checkmod(mod, imports) |
|
689 | 690 | except CircularImport as e: |
|
690 | 691 | cycle = e.args[0] |
|
691 | 692 | cycles.add(" -> ".join(rotatecycle(cycle))) |
|
692 | 693 | return cycles |
|
693 | 694 | |
|
694 | 695 | |
|
695 | 696 | def _cycle_sortkey(c): |
|
696 | 697 | return len(c), c |
|
697 | 698 | |
|
698 | 699 | |
|
699 | 700 | def embedded(f, modname, src): |
|
700 | 701 | """Extract embedded python code |
|
701 | 702 | |
|
702 | 703 | >>> def _forcestr(thing): |
|
703 | 704 | ... if not isinstance(thing, str): |
|
704 | 705 | ... return thing.decode('ascii') |
|
705 | 706 | ... return thing |
|
706 | 707 | >>> def test(fn, lines): |
|
707 | 708 | ... for s, m, f, l in embedded(fn, b"example", lines): |
|
708 | 709 | ... print("%s %s %d" % (_forcestr(m), _forcestr(f), l)) |
|
709 | 710 | ... print(repr(_forcestr(s))) |
|
710 | 711 | >>> lines = [ |
|
711 | 712 | ... 'comment', |
|
712 | 713 | ... ' >>> from __future__ import print_function', |
|
713 | 714 | ... " >>> ' multiline", |
|
714 | 715 | ... " ... string'", |
|
715 | 716 | ... ' ', |
|
716 | 717 | ... 'comment', |
|
717 | 718 | ... ' $ cat > foo.py <<EOF', |
|
718 | 719 | ... ' > from __future__ import print_function', |
|
719 | 720 | ... ' > EOF', |
|
720 | 721 | ... ] |
|
721 | 722 | >>> test(b"example.t", lines) |
|
722 | 723 | example[2] doctest.py 1 |
|
723 | 724 | "from __future__ import print_function\\n' multiline\\nstring'\\n\\n" |
|
724 | 725 | example[8] foo.py 7 |
|
725 | 726 | 'from __future__ import print_function\\n' |
|
726 | 727 | """ |
|
727 | 728 | errors = [] |
|
728 | 729 | for name, starts, ends, code in testparseutil.pyembedded(f, src, errors): |
|
729 | 730 | if not name: |
|
730 | 731 | # use 'doctest.py', in order to make already existing |
|
731 | 732 | # doctest above pass instantly |
|
732 | 733 | name = 'doctest.py' |
|
733 | 734 | # "starts" is "line number" (1-origin), but embedded() is |
|
734 | 735 | # expected to return "line offset" (0-origin). Therefore, this |
|
735 | 736 | # yields "starts - 1". |
|
736 | 737 | if not isinstance(modname, str): |
|
737 | 738 | modname = modname.decode('utf8') |
|
738 | 739 | yield code, "%s[%d]" % (modname, starts), name, starts - 1 |
|
739 | 740 | |
|
740 | 741 | |
|
741 | 742 | def sources(f, modname): |
|
742 | 743 | """Yields possibly multiple sources from a filepath |
|
743 | 744 | |
|
744 | 745 | input: filepath, modulename |
|
745 | 746 | yields: script(string), modulename, filepath, linenumber |
|
746 | 747 | |
|
747 | 748 | For embedded scripts, the modulename and filepath will be different |
|
748 | 749 | from the function arguments. linenumber is an offset relative to |
|
749 | 750 | the input file. |
|
750 | 751 | """ |
|
751 | 752 | py = False |
|
752 | 753 | if not f.endswith('.t'): |
|
753 | 754 | with open(f, 'rb') as src: |
|
754 | 755 | yield src.read(), modname, f, 0 |
|
755 | 756 | py = True |
|
756 | 757 | if py or f.endswith('.t'): |
|
757 | with open(f, 'r') as src: | |
|
758 | # Strictly speaking we should sniff for the magic header that denotes | |
|
759 | # Python source file encoding. But in reality we don't use anything | |
|
760 | # other than ASCII (mainly) and UTF-8 (in a few exceptions), so | |
|
761 | # simplicity is fine. | |
|
762 | with io.open(f, 'r', encoding='utf-8') as src: | |
|
758 | 763 | for script, modname, t, line in embedded(f, modname, src): |
|
759 | 764 | yield script, modname.encode('utf8'), t, line |
|
760 | 765 | |
|
761 | 766 | |
|
762 | 767 | def main(argv): |
|
763 | 768 | if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2): |
|
764 | 769 | print('Usage: %s {-|file [file] [file] ...}') |
|
765 | 770 | return 1 |
|
766 | 771 | if argv[1] == '-': |
|
767 | 772 | argv = argv[:1] |
|
768 | 773 | argv.extend(l.rstrip() for l in sys.stdin.readlines()) |
|
769 | 774 | localmodpaths = {} |
|
770 | 775 | used_imports = {} |
|
771 | 776 | any_errors = False |
|
772 | 777 | for source_path in argv[1:]: |
|
773 | 778 | modname = dotted_name_of_path(source_path) |
|
774 | 779 | localmodpaths[modname] = source_path |
|
775 | 780 | localmods = populateextmods(localmodpaths) |
|
776 | 781 | for localmodname, source_path in sorted(localmodpaths.items()): |
|
777 | 782 | if not isinstance(localmodname, bytes): |
|
778 | 783 | # This is only safe because all hg's files are ascii |
|
779 | 784 | localmodname = localmodname.encode('ascii') |
|
780 | 785 | for src, modname, name, line in sources(source_path, localmodname): |
|
781 | 786 | try: |
|
782 | 787 | used_imports[modname] = sorted( |
|
783 | 788 | imported_modules( |
|
784 | 789 | src, modname, name, localmods, ignore_nested=True |
|
785 | 790 | ) |
|
786 | 791 | ) |
|
787 | 792 | for error, lineno in verify_import_convention( |
|
788 | 793 | modname, src, localmods |
|
789 | 794 | ): |
|
790 | 795 | any_errors = True |
|
791 | 796 | print('%s:%d: %s' % (source_path, lineno + line, error)) |
|
792 | 797 | except SyntaxError as e: |
|
793 | 798 | print( |
|
794 | 799 | '%s:%d: SyntaxError: %s' % (source_path, e.lineno + line, e) |
|
795 | 800 | ) |
|
796 | 801 | cycles = find_cycles(used_imports) |
|
797 | 802 | if cycles: |
|
798 | 803 | firstmods = set() |
|
799 | 804 | for c in sorted(cycles, key=_cycle_sortkey): |
|
800 | 805 | first = c.split()[0] |
|
801 | 806 | # As a rough cut, ignore any cycle that starts with the |
|
802 | 807 | # same module as some other cycle. Otherwise we see lots |
|
803 | 808 | # of cycles that are effectively duplicates. |
|
804 | 809 | if first in firstmods: |
|
805 | 810 | continue |
|
806 | 811 | print('Import cycle:', c) |
|
807 | 812 | firstmods.add(first) |
|
808 | 813 | any_errors = True |
|
809 | 814 | return any_errors != 0 |
|
810 | 815 | |
|
811 | 816 | |
|
812 | 817 | if __name__ == '__main__': |
|
813 | 818 | sys.exit(int(main(sys.argv))) |
@@ -1,200 +1,200 b'' | |||
|
1 | 1 | # install-dependencies.ps1 - Install Windows dependencies for building Mercurial |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.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 | # This script can be used to bootstrap a Mercurial build environment on |
|
9 | 9 | # Windows. |
|
10 | 10 | # |
|
11 | 11 | # The script makes a lot of assumptions about how things should work. |
|
12 | 12 | # For example, the install location of Python is hardcoded to c:\hgdev\*. |
|
13 | 13 | # |
|
14 | 14 | # The script should be executed from a PowerShell with elevated privileges |
|
15 | 15 | # if you don't want to see a UAC prompt for various installers. |
|
16 | 16 | # |
|
17 | 17 | # The script is tested on Windows 10 and Windows Server 2019 (in EC2). |
|
18 | 18 | |
|
19 | 19 | $VS_BUILD_TOOLS_URL = "https://download.visualstudio.microsoft.com/download/pr/a1603c02-8a66-4b83-b821-811e3610a7c4/aa2db8bb39e0cbd23e9940d8951e0bc3/vs_buildtools.exe" |
|
20 | 20 | $VS_BUILD_TOOLS_SHA256 = "911E292B8E6E5F46CBC17003BDCD2D27A70E616E8D5E6E69D5D489A605CAA139" |
|
21 | 21 | |
|
22 | 22 | $VC9_PYTHON_URL = "https://download.microsoft.com/download/7/9/6/796EF2E4-801B-4FC4-AB28-B59FBF6D907B/VCForPython27.msi" |
|
23 | 23 | $VC9_PYTHON_SHA256 = "070474db76a2e625513a5835df4595df9324d820f9cc97eab2a596dcbc2f5cbf" |
|
24 | 24 | |
|
25 |
$PYTHON27_x64_URL = "https://www.python.org/ftp/python/2.7.1 |
|
|
26 | $PYTHON27_x64_SHA256 = "7c0f45993019152d46041a7db4b947b919558fdb7a8f67bcd0535bc98d42b603" | |
|
27 |
$PYTHON27_X86_URL = "https://www.python.org/ftp/python/2.7.1 |
|
|
28 | $PYTHON27_X86_SHA256 = "d57dc3e1ba490aee856c28b4915d09e3f49442461e46e481bc6b2d18207831d7" | |
|
25 | $PYTHON27_x64_URL = "https://www.python.org/ftp/python/2.7.17/python-2.7.17.amd64.msi" | |
|
26 | $PYTHON27_x64_SHA256 = "3b934447e3620e51d2daf5b2f258c9b617bcc686ca2f777a49aa3b47893abf1b" | |
|
27 | $PYTHON27_X86_URL = "https://www.python.org/ftp/python/2.7.17/python-2.7.17.msi" | |
|
28 | $PYTHON27_X86_SHA256 = "a4e3a321517c6b0c2693d6f712a0d18c82600b3d0c759c299b3d14384a17f863" | |
|
29 | 29 | |
|
30 | 30 | $PYTHON35_x86_URL = "https://www.python.org/ftp/python/3.5.4/python-3.5.4.exe" |
|
31 | 31 | $PYTHON35_x86_SHA256 = "F27C2D67FD9688E4970F3BFF799BB9D722A0D6C2C13B04848E1F7D620B524B0E" |
|
32 | 32 | $PYTHON35_x64_URL = "https://www.python.org/ftp/python/3.5.4/python-3.5.4-amd64.exe" |
|
33 | 33 | $PYTHON35_x64_SHA256 = "9B7741CC32357573A77D2EE64987717E527628C38FD7EAF3E2AACA853D45A1EE" |
|
34 | 34 | |
|
35 | 35 | $PYTHON36_x86_URL = "https://www.python.org/ftp/python/3.6.8/python-3.6.8.exe" |
|
36 | 36 | $PYTHON36_x86_SHA256 = "89871D432BC06E4630D7B64CB1A8451E53C80E68DE29029976B12AAD7DBFA5A0" |
|
37 | 37 | $PYTHON36_x64_URL = "https://www.python.org/ftp/python/3.6.8/python-3.6.8-amd64.exe" |
|
38 | 38 | $PYTHON36_x64_SHA256 = "96088A58B7C43BC83B84E6B67F15E8706C614023DD64F9A5A14E81FF824ADADC" |
|
39 | 39 | |
|
40 |
$PYTHON37_x86_URL = "https://www.python.org/ftp/python/3.7. |
|
|
41 | $PYTHON37_x86_SHA256 = "9a30ab5568ba37bfbcae5cdee19e9dc30765c42cf066f605221563ff8b20ee34" | |
|
42 |
$PYTHON37_X64_URL = "https://www.python.org/ftp/python/3.7. |
|
|
43 | $PYTHON37_x64_SHA256 = "bab92f987320975c7826171a072bfd64f8f0941aaf2cdeba6924b7025c9968a3" | |
|
40 | $PYTHON37_x86_URL = "https://www.python.org/ftp/python/3.7.5/python-3.7.5.exe" | |
|
41 | $PYTHON37_x86_SHA256 = "3c2ae8f72b48e6e0c2b482206e322bf5d0344ff91abc3b3c200cec9e275c7168" | |
|
42 | $PYTHON37_X64_URL = "https://www.python.org/ftp/python/3.7.5/python-3.7.5-amd64.exe" | |
|
43 | $PYTHON37_x64_SHA256 = "f3d60c127e7a92ed547efa3321bf70cd96b75c53bf4b903147015257c1314981" | |
|
44 | 44 | |
|
45 | 45 | $PYTHON38_x86_URL = "https://www.python.org/ftp/python/3.8.0/python-3.8.0.exe" |
|
46 | 46 | $PYTHON38_x86_SHA256 = "b471908de5e10d8fb5c3351a5affb1172da7790c533e0c9ffbaeec9c11611b15" |
|
47 | 47 | $PYTHON38_x64_URL = "https://www.python.org/ftp/python/3.8.0/python-3.8.0-amd64.exe" |
|
48 | 48 | $PYTHON38_x64_SHA256 = "a9bbc6088a3e4c7112826e21bfee6277f7b6d93259f7c57176139231bb7071e4" |
|
49 | 49 | |
|
50 | 50 | # PIP 19.2.3. |
|
51 | 51 | $PIP_URL = "https://github.com/pypa/get-pip/raw/309a56c5fd94bd1134053a541cb4657a4e47e09d/get-pip.py" |
|
52 | 52 | $PIP_SHA256 = "57e3643ff19f018f8a00dfaa6b7e4620e3c1a7a2171fd218425366ec006b3bfe" |
|
53 | 53 | |
|
54 | 54 | $VIRTUALENV_URL = "https://files.pythonhosted.org/packages/66/f0/6867af06d2e2f511e4e1d7094ff663acdebc4f15d4a0cb0fed1007395124/virtualenv-16.7.5.tar.gz" |
|
55 | 55 | $VIRTUALENV_SHA256 = "f78d81b62d3147396ac33fc9d77579ddc42cc2a98dd9ea38886f616b33bc7fb2" |
|
56 | 56 | |
|
57 | 57 | $INNO_SETUP_URL = "http://files.jrsoftware.org/is/5/innosetup-5.6.1-unicode.exe" |
|
58 | 58 | $INNO_SETUP_SHA256 = "27D49E9BC769E9D1B214C153011978DB90DC01C2ACD1DDCD9ED7B3FE3B96B538" |
|
59 | 59 | |
|
60 | 60 | $MINGW_BIN_URL = "https://osdn.net/frs/redir.php?m=constant&f=mingw%2F68260%2Fmingw-get-0.6.3-mingw32-pre-20170905-1-bin.zip" |
|
61 | 61 | $MINGW_BIN_SHA256 = "2AB8EFD7C7D1FC8EAF8B2FA4DA4EEF8F3E47768284C021599BC7435839A046DF" |
|
62 | 62 | |
|
63 | 63 | $MERCURIAL_WHEEL_FILENAME = "mercurial-5.1.2-cp27-cp27m-win_amd64.whl" |
|
64 | 64 | $MERCURIAL_WHEEL_URL = "https://files.pythonhosted.org/packages/6d/47/e031e47f7fe9b16e4e3383da47e2b0a7eae6e603996bc67a03ec4fa1b3f4/$MERCURIAL_WHEEL_FILENAME" |
|
65 | 65 | $MERCURIAL_WHEEL_SHA256 = "1d18c7f6ca1456f0f62ee65c9a50c14cbba48ce6e924930cdb10537f5c9eaf5f" |
|
66 | 66 | |
|
67 | 67 | # Writing progress slows down downloads substantially. So disable it. |
|
68 | 68 | $progressPreference = 'silentlyContinue' |
|
69 | 69 | |
|
70 | 70 | function Secure-Download($url, $path, $sha256) { |
|
71 | 71 | if (Test-Path -Path $path) { |
|
72 | 72 | Get-FileHash -Path $path -Algorithm SHA256 -OutVariable hash |
|
73 | 73 | |
|
74 | 74 | if ($hash.Hash -eq $sha256) { |
|
75 | 75 | Write-Output "SHA256 of $path verified as $sha256" |
|
76 | 76 | return |
|
77 | 77 | } |
|
78 | 78 | |
|
79 | 79 | Write-Output "hash mismatch on $path; downloading again" |
|
80 | 80 | } |
|
81 | 81 | |
|
82 | 82 | Write-Output "downloading $url to $path" |
|
83 | 83 | Invoke-WebRequest -Uri $url -OutFile $path |
|
84 | 84 | Get-FileHash -Path $path -Algorithm SHA256 -OutVariable hash |
|
85 | 85 | |
|
86 | 86 | if ($hash.Hash -ne $sha256) { |
|
87 | 87 | Remove-Item -Path $path |
|
88 | 88 | throw "hash mismatch when downloading $url; got $($hash.Hash), expected $sha256" |
|
89 | 89 | } |
|
90 | 90 | } |
|
91 | 91 | |
|
92 | 92 | function Invoke-Process($path, $arguments) { |
|
93 | 93 | $p = Start-Process -FilePath $path -ArgumentList $arguments -Wait -PassThru -WindowStyle Hidden |
|
94 | 94 | |
|
95 | 95 | if ($p.ExitCode -ne 0) { |
|
96 | 96 | throw "process exited non-0: $($p.ExitCode)" |
|
97 | 97 | } |
|
98 | 98 | } |
|
99 | 99 | |
|
100 | 100 | function Install-Python3($name, $installer, $dest, $pip) { |
|
101 | 101 | Write-Output "installing $name" |
|
102 | 102 | |
|
103 | 103 | # We hit this when running the script as part of Simple Systems Manager in |
|
104 | 104 | # EC2. The Python 3 installer doesn't seem to like per-user installs |
|
105 | 105 | # when running as the SYSTEM user. So enable global installs if executed in |
|
106 | 106 | # this mode. |
|
107 | 107 | if ($env:USERPROFILE -eq "C:\Windows\system32\config\systemprofile") { |
|
108 | 108 | Write-Output "running with SYSTEM account; installing for all users" |
|
109 | 109 | $allusers = "1" |
|
110 | 110 | } |
|
111 | 111 | else { |
|
112 | 112 | $allusers = "0" |
|
113 | 113 | } |
|
114 | 114 | |
|
115 | 115 | Invoke-Process $installer "/quiet TargetDir=${dest} InstallAllUsers=${allusers} AssociateFiles=0 CompileAll=0 PrependPath=0 Include_doc=0 Include_launcher=0 InstallLauncherAllUsers=0 Include_pip=0 Include_test=0" |
|
116 | 116 | Invoke-Process ${dest}\python.exe $pip |
|
117 | 117 | } |
|
118 | 118 | |
|
119 | 119 | function Install-Dependencies($prefix) { |
|
120 | 120 | if (!(Test-Path -Path $prefix\assets)) { |
|
121 | 121 | New-Item -Path $prefix\assets -ItemType Directory |
|
122 | 122 | } |
|
123 | 123 | |
|
124 | 124 | $pip = "${prefix}\assets\get-pip.py" |
|
125 | 125 | |
|
126 | 126 | Secure-Download $VC9_PYTHON_URL ${prefix}\assets\VCForPython27.msi $VC9_PYTHON_SHA256 |
|
127 | 127 | Secure-Download $PYTHON27_x86_URL ${prefix}\assets\python27-x86.msi $PYTHON27_x86_SHA256 |
|
128 | 128 | Secure-Download $PYTHON27_x64_URL ${prefix}\assets\python27-x64.msi $PYTHON27_x64_SHA256 |
|
129 | 129 | Secure-Download $PYTHON35_x86_URL ${prefix}\assets\python35-x86.exe $PYTHON35_x86_SHA256 |
|
130 | 130 | Secure-Download $PYTHON35_x64_URL ${prefix}\assets\python35-x64.exe $PYTHON35_x64_SHA256 |
|
131 | 131 | Secure-Download $PYTHON36_x86_URL ${prefix}\assets\python36-x86.exe $PYTHON36_x86_SHA256 |
|
132 | 132 | Secure-Download $PYTHON36_x64_URL ${prefix}\assets\python36-x64.exe $PYTHON36_x64_SHA256 |
|
133 | 133 | Secure-Download $PYTHON37_x86_URL ${prefix}\assets\python37-x86.exe $PYTHON37_x86_SHA256 |
|
134 | 134 | Secure-Download $PYTHON37_x64_URL ${prefix}\assets\python37-x64.exe $PYTHON37_x64_SHA256 |
|
135 | 135 | Secure-Download $PYTHON38_x86_URL ${prefix}\assets\python38-x86.exe $PYTHON38_x86_SHA256 |
|
136 | 136 | Secure-Download $PYTHON38_x64_URL ${prefix}\assets\python38-x64.exe $PYTHON38_x64_SHA256 |
|
137 | 137 | Secure-Download $PIP_URL ${pip} $PIP_SHA256 |
|
138 | 138 | Secure-Download $VIRTUALENV_URL ${prefix}\assets\virtualenv.tar.gz $VIRTUALENV_SHA256 |
|
139 | 139 | Secure-Download $VS_BUILD_TOOLS_URL ${prefix}\assets\vs_buildtools.exe $VS_BUILD_TOOLS_SHA256 |
|
140 | 140 | Secure-Download $INNO_SETUP_URL ${prefix}\assets\InnoSetup.exe $INNO_SETUP_SHA256 |
|
141 | 141 | Secure-Download $MINGW_BIN_URL ${prefix}\assets\mingw-get-bin.zip $MINGW_BIN_SHA256 |
|
142 | 142 | Secure-Download $MERCURIAL_WHEEL_URL ${prefix}\assets\${MERCURIAL_WHEEL_FILENAME} $MERCURIAL_WHEEL_SHA256 |
|
143 | 143 | |
|
144 | 144 | Write-Output "installing Python 2.7 32-bit" |
|
145 | 145 | Invoke-Process msiexec.exe "/i ${prefix}\assets\python27-x86.msi /l* ${prefix}\assets\python27-x86.log /q TARGETDIR=${prefix}\python27-x86 ALLUSERS=" |
|
146 | 146 | Invoke-Process ${prefix}\python27-x86\python.exe ${prefix}\assets\get-pip.py |
|
147 | 147 | Invoke-Process ${prefix}\python27-x86\Scripts\pip.exe "install ${prefix}\assets\virtualenv.tar.gz" |
|
148 | 148 | |
|
149 | 149 | Write-Output "installing Python 2.7 64-bit" |
|
150 | 150 | Invoke-Process msiexec.exe "/i ${prefix}\assets\python27-x64.msi /l* ${prefix}\assets\python27-x64.log /q TARGETDIR=${prefix}\python27-x64 ALLUSERS=" |
|
151 | 151 | Invoke-Process ${prefix}\python27-x64\python.exe ${prefix}\assets\get-pip.py |
|
152 | 152 | Invoke-Process ${prefix}\python27-x64\Scripts\pip.exe "install ${prefix}\assets\virtualenv.tar.gz" |
|
153 | 153 | |
|
154 | 154 | Install-Python3 "Python 3.5 32-bit" ${prefix}\assets\python35-x86.exe ${prefix}\python35-x86 ${pip} |
|
155 | 155 | Install-Python3 "Python 3.5 64-bit" ${prefix}\assets\python35-x64.exe ${prefix}\python35-x64 ${pip} |
|
156 | 156 | Install-Python3 "Python 3.6 32-bit" ${prefix}\assets\python36-x86.exe ${prefix}\python36-x86 ${pip} |
|
157 | 157 | Install-Python3 "Python 3.6 64-bit" ${prefix}\assets\python36-x64.exe ${prefix}\python36-x64 ${pip} |
|
158 | 158 | Install-Python3 "Python 3.7 32-bit" ${prefix}\assets\python37-x86.exe ${prefix}\python37-x86 ${pip} |
|
159 | 159 | Install-Python3 "Python 3.7 64-bit" ${prefix}\assets\python37-x64.exe ${prefix}\python37-x64 ${pip} |
|
160 | 160 | Install-Python3 "Python 3.8 32-bit" ${prefix}\assets\python38-x86.exe ${prefix}\python38-x86 ${pip} |
|
161 | 161 | Install-Python3 "Python 3.8 64-bit" ${prefix}\assets\python38-x64.exe ${prefix}\python38-x64 ${pip} |
|
162 | 162 | |
|
163 | 163 | Write-Output "installing Visual Studio 2017 Build Tools and SDKs" |
|
164 | 164 | Invoke-Process ${prefix}\assets\vs_buildtools.exe "--quiet --wait --norestart --nocache --channelUri https://aka.ms/vs/15/release/channel --add Microsoft.VisualStudio.Workload.MSBuildTools --add Microsoft.VisualStudio.Component.Windows10SDK.17763 --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.Windows10SDK --add Microsoft.VisualStudio.Component.VC.140" |
|
165 | 165 | |
|
166 | 166 | Write-Output "installing Visual C++ 9.0 for Python 2.7" |
|
167 | 167 | Invoke-Process msiexec.exe "/i ${prefix}\assets\VCForPython27.msi /l* ${prefix}\assets\VCForPython27.log /q" |
|
168 | 168 | |
|
169 | 169 | Write-Output "installing Inno Setup" |
|
170 | 170 | Invoke-Process ${prefix}\assets\InnoSetup.exe "/SP- /VERYSILENT /SUPPRESSMSGBOXES" |
|
171 | 171 | |
|
172 | 172 | Write-Output "extracting MinGW base archive" |
|
173 | 173 | Expand-Archive -Path ${prefix}\assets\mingw-get-bin.zip -DestinationPath "${prefix}\MinGW" -Force |
|
174 | 174 | |
|
175 | 175 | Write-Output "updating MinGW package catalogs" |
|
176 | 176 | Invoke-Process ${prefix}\MinGW\bin\mingw-get.exe "update" |
|
177 | 177 | |
|
178 | 178 | Write-Output "installing MinGW packages" |
|
179 | 179 | Invoke-Process ${prefix}\MinGW\bin\mingw-get.exe "install msys-base msys-coreutils msys-diffutils msys-unzip" |
|
180 | 180 | |
|
181 | 181 | # Construct a virtualenv useful for bootstrapping. It conveniently contains a |
|
182 | 182 | # Mercurial install. |
|
183 | 183 | Write-Output "creating bootstrap virtualenv with Mercurial" |
|
184 | 184 | Invoke-Process "$prefix\python27-x64\Scripts\virtualenv.exe" "${prefix}\venv-bootstrap" |
|
185 | 185 | Invoke-Process "${prefix}\venv-bootstrap\Scripts\pip.exe" "install ${prefix}\assets\${MERCURIAL_WHEEL_FILENAME}" |
|
186 | 186 | } |
|
187 | 187 | |
|
188 | 188 | function Clone-Mercurial-Repo($prefix, $repo_url, $dest) { |
|
189 | 189 | Write-Output "cloning $repo_url to $dest" |
|
190 | 190 | # TODO Figure out why CA verification isn't working in EC2 and remove |
|
191 | 191 | # --insecure. |
|
192 | 192 | Invoke-Process "${prefix}\venv-bootstrap\Scripts\hg.exe" "clone --insecure $repo_url $dest" |
|
193 | 193 | |
|
194 | 194 | # Mark repo as non-publishing by default for convenience. |
|
195 | 195 | Add-Content -Path "$dest\.hg\hgrc" -Value "`n[phases]`npublish = false" |
|
196 | 196 | } |
|
197 | 197 | |
|
198 | 198 | $prefix = "c:\hgdev" |
|
199 | 199 | Install-Dependencies $prefix |
|
200 | 200 | Clone-Mercurial-Repo $prefix "https://www.mercurial-scm.org/repo/hg" $prefix\src |
@@ -1,145 +1,136 b'' | |||
|
1 | 1 | $(eval HGROOT := $(shell cd ../..; pwd)) |
|
2 | 2 | |
|
3 | 3 | DEBIAN_CODENAMES := \ |
|
4 | 4 | stretch \ |
|
5 | 5 | buster \ |
|
6 | 6 | bullseye |
|
7 | 7 | |
|
8 | 8 | UBUNTU_CODENAMES := \ |
|
9 | 9 | xenial \ |
|
10 | 10 | bionic \ |
|
11 | 11 | cosmic \ |
|
12 | 12 | disco |
|
13 | 13 | |
|
14 |
FEDORA_RELEASE |
|
|
15 | 20 \ | |
|
16 | 21 \ | |
|
17 | 28 \ | |
|
18 | 29 | |
|
14 | FEDORA_RELEASE := 31 | |
|
19 | 15 | |
|
20 | 16 | CENTOS_RELEASES := \ |
|
21 | 17 | 5 \ |
|
22 | 18 | 6 \ |
|
23 | 7 | |
|
19 | 7 \ | |
|
20 | 8 | |
|
24 | 21 | |
|
25 | 22 | # Build a Python for these CentOS releases. |
|
26 | 23 | CENTOS_WITH_PYTHON_RELEASES := 5 6 |
|
24 | CENTOS_WITH_NONVERSIONED_PYTHON := 5 6 7 | |
|
27 | 25 | |
|
28 | 26 | help: |
|
29 | 27 | @echo 'Packaging Make Targets' |
|
30 | 28 | @echo '' |
|
31 | 29 | @echo 'docker-centos{$(strip $(CENTOS_RELEASES))}' |
|
32 | 30 | @echo ' Build an RPM for a specific CentOS version using Docker.' |
|
33 | 31 | @echo '' |
|
34 | 32 | @echo 'docker-debian-{$(strip $(DEBIAN_CODENAMES))}' |
|
35 | 33 | @echo ' Build Debian packages specific to a Debian distro using Docker.' |
|
36 | 34 | @echo '' |
|
37 |
@echo 'docker-fedora |
|
|
38 |
@echo ' Build an RPM for a |
|
|
35 | @echo 'docker-fedora' | |
|
36 | @echo ' Build an RPM for a Fedora $(FEDORA_RELEASE) using Docker.' | |
|
39 | 37 | @echo '' |
|
40 | 38 | @echo 'docker-ubuntu-{$(strip $(UBUNTU_CODENAMES))}' |
|
41 | 39 | @echo ' Build Debian package specific to an Ubuntu distro using Docker.' |
|
42 | 40 | @echo '' |
|
43 | 41 | @echo 'docker-ubuntu-{$(strip $(UBUNTU_CODENAMES))}-ppa' |
|
44 | 42 | @echo ' Build a source-only Debian package specific to an Ubuntu distro' |
|
45 | 43 | @echo ' using Docker.' |
|
46 | 44 | @echo '' |
|
47 | 45 | @echo 'linux-wheels' |
|
48 | 46 | @echo ' Build Linux manylinux wheels using Docker.' |
|
49 | 47 | @echo '' |
|
50 | 48 | @echo 'linux-wheels-{x86_64, i686}' |
|
51 | 49 | @echo ' Build Linux manylinux wheels for a specific architecture using Docker' |
|
52 | 50 | @echo '' |
|
53 | 51 | @echo 'deb' |
|
54 | 52 | @echo ' Build a Debian package locally targeting the current system' |
|
55 | 53 | @echo '' |
|
56 | 54 | @echo 'ppa' |
|
57 | 55 | @echo ' Build a Debian source package locally targeting the current system' |
|
58 | 56 | @echo '' |
|
59 | 57 | @echo 'centos{$(strip $(CENTOS_RELEASES))}' |
|
60 | 58 | @echo ' Build an RPM for a specific CentOS version locally' |
|
61 | 59 | @echo '' |
|
62 | @echo 'fedora{$(strip $(FEDORA_RELEASES))}' | |
|
63 |
@echo ' Build an RPM for |
|
|
60 | @echo 'fedora' | |
|
61 | @echo ' Build an RPM for Fedora $(FEDORA_RELEASE) locally' | |
|
64 | 62 | |
|
65 | 63 | .PHONY: help |
|
66 | 64 | |
|
67 | 65 | .PHONY: deb |
|
68 | 66 | deb: |
|
69 | 67 | ./builddeb |
|
70 | 68 | |
|
71 | 69 | .PHONY: ppa |
|
72 | 70 | ppa: |
|
73 | 71 | ./builddeb --source-only |
|
74 | 72 | |
|
75 | 73 | # Debian targets. |
|
76 | 74 | define debian_targets = |
|
77 | 75 | .PHONY: docker-debian-$(1) |
|
78 | 76 | docker-debian-$(1): |
|
79 | 77 | ./dockerdeb debian $(1) |
|
80 | 78 | |
|
81 | 79 | endef |
|
82 | 80 | |
|
83 | 81 | $(foreach codename,$(DEBIAN_CODENAMES),$(eval $(call debian_targets,$(codename)))) |
|
84 | 82 | |
|
85 | 83 | # Ubuntu targets. |
|
86 | 84 | define ubuntu_targets = |
|
87 | 85 | .PHONY: docker-ubuntu-$(1) |
|
88 | 86 | docker-ubuntu-$(1): |
|
89 | 87 | ./dockerdeb ubuntu $(1) |
|
90 | 88 | |
|
91 | 89 | .PHONY: docker-ubuntu-$(1)-ppa |
|
92 | 90 | docker-ubuntu-$(1)-ppa: |
|
93 | 91 | ./dockerdeb ubuntu $(1) --source-only |
|
94 | 92 | |
|
95 | 93 | endef |
|
96 | 94 | |
|
97 | 95 | $(foreach codename,$(UBUNTU_CODENAMES),$(eval $(call ubuntu_targets,$(codename)))) |
|
98 | 96 | |
|
99 | 97 | # Fedora targets. |
|
100 | define fedora_targets | |
|
101 | .PHONY: fedora$(1) | |
|
102 | fedora$(1): | |
|
103 | mkdir -p $$(HGROOT)/packages/fedora$(1) | |
|
98 | .PHONY: fedora | |
|
99 | fedora: | |
|
100 | mkdir -p $(HGROOT)/packages/fedora$(FEDORA_RELEASE) | |
|
104 | 101 | ./buildrpm |
|
105 |
cp |
|
|
106 |
cp |
|
|
102 | cp $(HGROOT)/contrib/packaging/rpmbuild/RPMS/*/* $(HGROOT)/packages/fedora$(FEDORA_RELEASE) | |
|
103 | cp $(HGROOT)/contrib/packaging/rpmbuild/SRPMS/* $(HGROOT)/packages/fedora$(FEDORA_RELEASE) | |
|
107 | 104 | rm -rf $(HGROOT)/rpmbuild |
|
108 | 105 | |
|
109 |
.PHONY: docker-fedora |
|
|
110 |
docker-fedora |
|
|
111 | mkdir -p $$(HGROOT)/packages/fedora$(1) | |
|
112 | ./dockerrpm fedora$(1) | |
|
113 | ||
|
114 | endef | |
|
115 | ||
|
116 | $(foreach release,$(FEDORA_RELEASES),$(eval $(call fedora_targets,$(release)))) | |
|
106 | .PHONY: docker-fedora | |
|
107 | docker-fedora: | |
|
108 | ./dockerrpm fedora$(FEDORA_RELEASE) | |
|
117 | 109 | |
|
118 | 110 | # CentOS targets. |
|
119 | 111 | define centos_targets |
|
120 | 112 | .PHONY: centos$(1) |
|
121 | 113 | centos$(1): |
|
122 | 114 | mkdir -p $$(HGROOT)/packages/centos$(1) |
|
123 | ./buildrpm $$(if $$(filter $(1),$$(CENTOS_WITH_PYTHON_RELEASES)),--withpython) | |
|
115 | ./buildrpm $$(if $$(filter $(1),$$(CENTOS_WITH_PYTHON_RELEASES)),--withpython,$$(if $$(filter $(1),$$(CENTOS_WITH_NONVERSIONED_PYTHON)),--python python,)) | |
|
124 | 116 | cp $$(HGROOT)/contrib/packaging/rpmbuild/RPMS/*/* $$(HGROOT)/packages/centos$(1) |
|
125 | 117 | cp $$(HGROOT)/contrib/packaging/rpmbuild/SRPMS/* $$(HGROOT)/packages/centos$(1) |
|
126 | 118 | |
|
127 | 119 | .PHONY: docker-centos$(1) |
|
128 | 120 | docker-centos$(1): |
|
129 | mkdir -p $$(HGROOT)/packages/centos$(1) | |
|
130 | ./dockerrpm centos$(1) $$(if $$(filter $(1),$$(CENTOS_WITH_PYTHON_RELEASES)),--withpython) | |
|
121 | ./dockerrpm centos$(1) $$(if $$(filter $(1),$$(CENTOS_WITH_PYTHON_RELEASES)),--withpython,$$(if $$(filter $(1),$$(CENTOS_WITH_NONVERSIONED_PYTHON)),--python python,)) | |
|
131 | 122 | |
|
132 | 123 | endef |
|
133 | 124 | |
|
134 | 125 | $(foreach release,$(CENTOS_RELEASES),$(eval $(call centos_targets,$(release)))) |
|
135 | 126 | |
|
136 | 127 | .PHONY: linux-wheels |
|
137 | 128 | linux-wheels: linux-wheels-x86_64 linux-wheels-i686 |
|
138 | 129 | |
|
139 | 130 | .PHONY: linux-wheels-x86_64 |
|
140 | 131 | linux-wheels-x86_64: |
|
141 | 132 | docker run -e "HGTEST_JOBS=$(shell nproc)" --rm -ti -v `pwd`/../..:/src quay.io/pypa/manylinux1_x86_64 /src/contrib/packaging/build-linux-wheels.sh |
|
142 | 133 | |
|
143 | 134 | .PHONY: linux-wheels-i686 |
|
144 | 135 | linux-wheels-i686: |
|
145 | 136 | docker run -e "HGTEST_JOBS=$(shell nproc)" --rm -ti -v `pwd`/../..:/src quay.io/pypa/manylinux1_i686 linux32 /src/contrib/packaging/build-linux-wheels.sh |
@@ -1,115 +1,115 b'' | |||
|
1 | 1 | #!/bin/sh -e |
|
2 | 2 | # |
|
3 | 3 | # Build a Mercurial debian package from the current repo |
|
4 | 4 | # |
|
5 | 5 | # Tested on Jessie (stable as of original script authoring.) |
|
6 | 6 | |
|
7 | 7 | . $(dirname $0)/packagelib.sh |
|
8 | 8 | |
|
9 | 9 | ROOTDIR=$(cd $(dirname $0)/../.. > /dev/null; pwd) |
|
10 | 10 | |
|
11 | 11 | BUILD=1 |
|
12 | 12 | CLEANUP=1 |
|
13 | 13 | DISTID=`(lsb_release -is 2> /dev/null | tr '[:upper:]' '[:lower:]') || echo debian` |
|
14 | 14 | CODENAME=`lsb_release -cs 2> /dev/null || echo unknown` |
|
15 | 15 | DEBFLAGS=-b |
|
16 | 16 | |
|
17 | 17 | cleanup() { |
|
18 | 18 | if [ "$CLEANUP" ]; then |
|
19 | 19 | rm -r "$ROOTDIR/debian"; |
|
20 | 20 | fi |
|
21 | 21 | } |
|
22 | 22 | |
|
23 | 23 | while [ "$1" ]; do |
|
24 | 24 | case "$1" in |
|
25 | 25 | --distid ) |
|
26 | 26 | shift |
|
27 | 27 | DISTID="$1" |
|
28 | 28 | shift |
|
29 | 29 | ;; |
|
30 | 30 | --codename ) |
|
31 | 31 | shift |
|
32 | 32 | CODENAME="$1" |
|
33 | 33 | shift |
|
34 | 34 | ;; |
|
35 | 35 | --cleanup ) |
|
36 | 36 | shift |
|
37 | 37 | BUILD= |
|
38 | 38 | ;; |
|
39 | 39 | --build ) |
|
40 | 40 | shift |
|
41 | 41 | CLEANUP= |
|
42 | 42 | ;; |
|
43 | 43 | --source-only ) |
|
44 | 44 | shift |
|
45 | 45 | DEBFLAGS=-S |
|
46 | 46 | ;; |
|
47 | 47 | * ) |
|
48 | 48 | echo "Invalid parameter $1!" 1>&2 |
|
49 | 49 | exit 1 |
|
50 | 50 | ;; |
|
51 | 51 | esac |
|
52 | 52 | done |
|
53 | 53 | |
|
54 | 54 | cd "$ROOTDIR" |
|
55 | 55 | |
|
56 | 56 | trap 'cleanup' EXIT |
|
57 | 57 | |
|
58 | 58 | set -u |
|
59 | 59 | |
|
60 | 60 | if [ ! -d .hg ]; then |
|
61 | 61 | printf "You are inside %s, which is not the root of a Mercurial repository\n" $(pwd) 1>&2 |
|
62 | 62 | exit 1 |
|
63 | 63 | fi |
|
64 | 64 | |
|
65 | 65 | gethgversion |
|
66 | 66 | debver="$version" |
|
67 | 67 | if [ -n "$type" ] ; then |
|
68 | 68 | debver="$debver~$type" |
|
69 | 69 | fi |
|
70 | 70 | if [ -n "$distance" ] ; then |
|
71 | 71 | debver="$debver+$distance-$CODENAME-$node" |
|
72 | 72 | elif [ "$DEBFLAGS" = "-S" ] ; then |
|
73 | 73 | # for building a ppa (--source-only) for a release (distance == 0), we need |
|
74 | 74 | # to version the distroseries so that we can upload to launchpad |
|
75 | 75 | debver="$debver~${CODENAME}1" |
|
76 | 76 | fi |
|
77 | 77 | |
|
78 | 78 | control=debian/control |
|
79 | 79 | changelog=debian/changelog |
|
80 | 80 | |
|
81 | 81 | if [ "$BUILD" ]; then |
|
82 | 82 | if [ -d debian ] ; then |
|
83 | 83 | printf "Error! debian control directory already exists at %s/debian\n" $(pwd) |
|
84 | 84 | exit 1 |
|
85 | 85 | fi |
|
86 | 86 | |
|
87 | 87 | cp -r "$ROOTDIR"/contrib/packaging/debian debian |
|
88 | 88 | |
|
89 | 89 | sed -i.tmp "s/__VERSION__/$debver/" $changelog |
|
90 | 90 | sed -i.tmp "s/__DATE__/$(date --rfc-2822)/" $changelog |
|
91 | 91 | sed -i.tmp "s/__CODENAME__/$CODENAME/" $changelog |
|
92 | 92 | rm $changelog.tmp |
|
93 | 93 | |
|
94 | 94 | # remove the node from the version string |
|
95 | 95 | SRCFILE="mercurial_$(echo $debver | sed "s,-$node,,").orig.tar.gz" |
|
96 | 96 | "$ROOTDIR/hg" archive $SRCFILE |
|
97 | 97 | mv $SRCFILE .. |
|
98 | 98 | debuild -us -uc -i -I $DEBFLAGS |
|
99 | 99 | if [ $? != 0 ]; then |
|
100 | 100 | echo 'debuild failed!' |
|
101 | 101 | exit 1 |
|
102 | 102 | fi |
|
103 | 103 | |
|
104 | 104 | fi |
|
105 | 105 | if [ "$CLEANUP" ] ; then |
|
106 | 106 | echo |
|
107 | 107 | OUTPUTDIR=${OUTPUTDIR:=packages/$DISTID-$CODENAME} |
|
108 | 108 | mkdir -p "$OUTPUTDIR" |
|
109 | find ../mercurial*.deb ../mercurial_*.build ../mercurial_*.changes \ | |
|
109 | find ../mercurial*.deb ../mercurial_*.build* ../mercurial_*.changes \ | |
|
110 | 110 | ../mercurial*.dsc ../mercurial*.gz \ |
|
111 | 111 | -type f -newer $control -print0 2>/dev/null | \ |
|
112 | 112 | xargs -Inarf -0 mv narf "$OUTPUTDIR" |
|
113 | 113 | echo "Built packages for $debver:" |
|
114 |
find "$ |
|
|
114 | find "$OUTPUTDIR" -type f -newer $control -name '*.deb' | |
|
115 | 115 | fi |
@@ -1,160 +1,163 b'' | |||
|
1 | 1 | #!/bin/bash -e |
|
2 | 2 | # |
|
3 | # Build a Mercurial RPM from the current repo | |
|
4 | # | |
|
5 | # Tested on | |
|
6 | # - Fedora 20 | |
|
7 | # - CentOS 5 | |
|
8 | # - centOS 6 | |
|
3 | # Build a Mercurial RPM from the current repo, mainly for Fedora/CentOS/RHEL | |
|
9 | 4 | |
|
10 | 5 | . $(dirname $0)/packagelib.sh |
|
11 | 6 | |
|
12 | 7 | BUILD=1 |
|
13 | 8 | RPMBUILDDIR="$PWD/rpmbuild" |
|
9 | PYTHONEXE=python3 | |
|
14 | 10 | |
|
15 | 11 | while [ "$1" ]; do |
|
16 | 12 | case "$1" in |
|
17 | 13 | --prepare ) |
|
18 | 14 | shift |
|
19 | 15 | BUILD= |
|
20 | 16 | ;; |
|
17 | --python) | |
|
18 | shift | |
|
19 | PYTHONEXE=$1 | |
|
20 | shift | |
|
21 | ;; | |
|
21 | 22 | --withpython | --with-python) |
|
22 | 23 | shift |
|
23 | 24 | PYTHONVER=2.7.16 |
|
24 | 25 | PYTHONMD5=f1a2ace631068444831d01485466ece0 |
|
26 | PYTHONEXE=python | |
|
25 | 27 | ;; |
|
26 | 28 | --rpmbuilddir ) |
|
27 | 29 | shift |
|
28 | 30 | RPMBUILDDIR="$1" |
|
29 | 31 | shift |
|
30 | 32 | ;; |
|
31 | 33 | * ) |
|
32 | 34 | echo "Invalid parameter $1!" 1>&2 |
|
33 | 35 | exit 1 |
|
34 | 36 | ;; |
|
35 | 37 | esac |
|
36 | 38 | done |
|
37 | 39 | |
|
38 | 40 | cd "`dirname $0`/../.." |
|
39 | 41 | |
|
40 | 42 | specfile=$PWD/contrib/packaging/mercurial.spec |
|
41 | 43 | if [ ! -f $specfile ]; then |
|
42 | 44 | echo "Cannot find $specfile!" 1>&2 |
|
43 | 45 | exit 1 |
|
44 | 46 | fi |
|
45 | 47 | |
|
46 | 48 | if [ ! -d .hg ]; then |
|
47 | 49 | echo 'You are not inside a Mercurial repository!' 1>&2 |
|
48 | 50 | exit 1 |
|
49 | 51 | fi |
|
50 | 52 | |
|
51 | 53 | gethgversion |
|
52 | 54 | |
|
53 | 55 | if [ -z "$type" ] ; then |
|
54 | release=1 | |
|
56 | release=1 | |
|
55 | 57 | else |
|
56 | 58 | release=0.9_$type |
|
57 | 59 | fi |
|
58 | 60 | |
|
59 | 61 | if [ -n "$distance" ] ; then |
|
60 | 62 | release=$release+${distance}_${node} |
|
61 | 63 | fi |
|
62 | 64 | |
|
63 | 65 | if [ "$PYTHONVER" ]; then |
|
64 | 66 | release=$release+$PYTHONVER |
|
65 | 67 | RPMPYTHONVER=$PYTHONVER |
|
66 | 68 | else |
|
67 | 69 | RPMPYTHONVER=%{nil} |
|
68 | 70 | fi |
|
69 | 71 | |
|
70 | 72 | mkdir -p $RPMBUILDDIR/{SOURCES,BUILD,SRPMS,RPMS} |
|
71 | 73 | $HG archive -t tgz $RPMBUILDDIR/SOURCES/mercurial-$version-$release.tar.gz |
|
72 | 74 | if [ "$PYTHONVER" ]; then |
|
73 | 75 | ( |
|
74 | 76 | mkdir -p build |
|
75 | 77 | cd build |
|
76 | 78 | PYTHON_SRCFILE=Python-$PYTHONVER.tgz |
|
77 | 79 | [ -f $PYTHON_SRCFILE ] || curl -Lo $PYTHON_SRCFILE http://www.python.org/ftp/python/$PYTHONVER/$PYTHON_SRCFILE |
|
78 | 80 | if [ "$PYTHONMD5" ]; then |
|
79 | 81 | echo "$PYTHONMD5 $PYTHON_SRCFILE" | md5sum -w -c |
|
80 | 82 | fi |
|
81 | 83 | ln -f $PYTHON_SRCFILE $RPMBUILDDIR/SOURCES/$PYTHON_SRCFILE |
|
82 | 84 | |
|
83 | 85 | DOCUTILSVER=`sed -ne "s/^%global docutilsname docutils-//p" $specfile` |
|
84 | 86 | DOCUTILS_SRCFILE=docutils-$DOCUTILSVER.tar.gz |
|
85 | 87 | [ -f $DOCUTILS_SRCFILE ] || curl -Lo $DOCUTILS_SRCFILE http://downloads.sourceforge.net/project/docutils/docutils/$DOCUTILSVER/$DOCUTILS_SRCFILE |
|
86 | 88 | DOCUTILSMD5=`sed -ne "s/^%global docutilsmd5 //p" $specfile` |
|
87 | 89 | if [ "$DOCUTILSMD5" ]; then |
|
88 | 90 | echo "$DOCUTILSMD5 $DOCUTILS_SRCFILE" | md5sum -w -c |
|
89 | 91 | fi |
|
90 | 92 | ln -f $DOCUTILS_SRCFILE $RPMBUILDDIR/SOURCES/$DOCUTILS_SRCFILE |
|
91 | 93 | ) |
|
92 | 94 | fi |
|
93 | 95 | |
|
94 | 96 | mkdir -p $RPMBUILDDIR/SPECS |
|
95 | 97 | rpmspec=$RPMBUILDDIR/SPECS/mercurial.spec |
|
96 | 98 | |
|
97 | 99 | sed -e "s,^Version:.*,Version: $version," \ |
|
98 | 100 | -e "s,^Release:.*,Release: $release," \ |
|
101 | -e "s/^%global pythonexe .*/%global pythonexe $PYTHONEXE/" \ | |
|
99 | 102 | $specfile > $rpmspec |
|
100 | 103 | |
|
101 | 104 | echo >> $rpmspec |
|
102 | 105 | echo "%changelog" >> $rpmspec |
|
103 | 106 | |
|
104 | 107 | if echo $version | grep '+' > /dev/null 2>&1; then |
|
105 | 108 | latesttag="`echo $version | sed -e 's/+.*//'`" |
|
106 | 109 | $HG log -r .:"$latesttag" -fM \ |
|
107 | 110 | --template '{date|hgdate}\t{author}\t{desc|firstline}\n' | python -c ' |
|
108 | 111 | import sys, time |
|
109 | 112 | |
|
110 | 113 | def datestr(date, format): |
|
111 | 114 | return time.strftime(format, time.gmtime(float(date[0]) - date[1])) |
|
112 | 115 | |
|
113 | 116 | changelog = [] |
|
114 | 117 | for l in sys.stdin.readlines(): |
|
115 | 118 | tok = l.split("\t") |
|
116 | 119 | hgdate = tuple(int(v) for v in tok[0].split()) |
|
117 | 120 | changelog.append((datestr(hgdate, "%F"), tok[1], hgdate, tok[2])) |
|
118 | 121 | prevtitle = "" |
|
119 | 122 | for l in sorted(changelog, reverse=True): |
|
120 | 123 | title = "* %s %s" % (datestr(l[2], "%a %b %d %Y"), l[1]) |
|
121 | 124 | if prevtitle != title: |
|
122 | 125 | prevtitle = title |
|
123 | 126 | |
|
124 |
print |
|
|
125 |
print |
|
|
127 | print(title) | |
|
128 | print("- %s" % l[3].strip()) | |
|
126 | 129 | ' >> $rpmspec |
|
127 | 130 | |
|
128 | 131 | else |
|
129 | 132 | |
|
130 | 133 | $HG log \ |
|
131 | 134 | --template '{date|hgdate}\t{author}\t{desc|firstline}\n' \ |
|
132 | 135 | .hgtags | python -c ' |
|
133 | 136 | import sys, time |
|
134 | 137 | |
|
135 | 138 | def datestr(date, format): |
|
136 | 139 | return time.strftime(format, time.gmtime(float(date[0]) - date[1])) |
|
137 | 140 | |
|
138 | 141 | for l in sys.stdin.readlines(): |
|
139 | 142 | tok = l.split("\t") |
|
140 | 143 | hgdate = tuple(int(v) for v in tok[0].split()) |
|
141 |
print |
|
|
144 | print("* %s %s\n- %s" % (datestr(hgdate, "%a %b %d %Y"), tok[1], tok[2])) | |
|
142 | 145 | ' >> $rpmspec |
|
143 | 146 | |
|
144 | 147 | fi |
|
145 | 148 | |
|
146 | 149 | sed -i \ |
|
147 | 150 | -e "s/^%define withpython.*$/%define withpython $RPMPYTHONVER/" \ |
|
148 | 151 | $rpmspec |
|
149 | 152 | |
|
150 | 153 | if [ "$BUILD" ]; then |
|
151 | 154 | rpmbuild --define "_topdir $RPMBUILDDIR" -ba $rpmspec --clean |
|
152 | 155 | if [ $? = 0 ]; then |
|
153 | 156 | echo |
|
154 | 157 | echo "Built packages for $version-$release:" |
|
155 | 158 | find $RPMBUILDDIR/*RPMS/ -type f -newer $rpmspec |
|
156 | 159 | fi |
|
157 | 160 | else |
|
158 | 161 | echo "Prepared sources for $version-$release $rpmspec are in $RPMBUILDDIR/SOURCES/ - use like:" |
|
159 | 162 | echo "rpmbuild --define '_topdir $RPMBUILDDIR' -ba $rpmspec --clean" |
|
160 | 163 | fi |
@@ -1,54 +1,41 b'' | |||
|
1 | 1 | Source: mercurial |
|
2 | 2 | Section: vcs |
|
3 | 3 | Priority: optional |
|
4 | 4 | Maintainer: Mercurial Developers <mercurial-devel@mercurial-scm.org> |
|
5 | 5 | Build-Depends: |
|
6 | 6 | debhelper (>= 9), |
|
7 | 7 | dh-python, |
|
8 | 8 | less, |
|
9 | 9 | netbase, |
|
10 | python-all, | |
|
11 | python-all-dev, | |
|
12 | python-docutils, | |
|
10 | python3-all, | |
|
11 | python3-all-dev, | |
|
12 | python3-docutils, | |
|
13 | 13 | unzip, |
|
14 | 14 | zip |
|
15 | 15 | Standards-Version: 3.9.4 |
|
16 |
X-Python-Version: >= |
|
|
16 | X-Python3-Version: >= 3.5 | |
|
17 | 17 | |
|
18 | 18 | Package: mercurial |
|
19 | 19 | Depends: |
|
20 | python, | |
|
20 | sensible-utils, | |
|
21 | 21 | ${shlibs:Depends}, |
|
22 | 22 | ${misc:Depends}, |
|
23 | ${python:Depends}, | |
|
24 | mercurial-common (= ${source:Version}) | |
|
23 | ${python3:Depends}, | |
|
24 | Recommends: ca-certificates | |
|
25 | Suggests: wish | |
|
26 | Replaces: mercurial-common | |
|
27 | Breaks: mercurial-common | |
|
25 | 28 | Architecture: any |
|
26 | 29 | Description: fast, easy to use, distributed revision control tool. |
|
27 | 30 | Mercurial is a fast, lightweight Source Control Management system designed |
|
28 | 31 | for efficient handling of very large distributed projects. |
|
29 | 32 | . |
|
30 | 33 | Its features include: |
|
31 | 34 | * O(1) delta-compressed file storage and retrieval scheme |
|
32 | 35 | * Complete cross-indexing of files and changesets for efficient exploration |
|
33 | 36 | of project history |
|
34 | 37 | * Robust SHA1-based integrity checking and append-only storage model |
|
35 | 38 | * Decentralized development model with arbitrary merging between trees |
|
36 | 39 | * Easy-to-use command-line interface |
|
37 | 40 | * Integrated stand-alone web interface |
|
38 | 41 | * Small Python codebase |
|
39 | ||
|
40 | Package: mercurial-common | |
|
41 | Architecture: all | |
|
42 | Depends: | |
|
43 | ${misc:Depends}, | |
|
44 | ${python:Depends}, | |
|
45 | Recommends: mercurial (= ${source:Version}), ca-certificates | |
|
46 | Suggests: wish | |
|
47 | Breaks: mercurial (<< ${source:Version}) | |
|
48 | Replaces: mercurial (<< 2.6.3) | |
|
49 | Description: easy-to-use, scalable distributed version control system (common files) | |
|
50 | Mercurial is a fast, lightweight Source Control Management system designed | |
|
51 | for efficient handling of very large distributed projects. | |
|
52 | . | |
|
53 | This package contains the architecture independent components of Mercurial, | |
|
54 | and is generally useless without the mercurial package. |
@@ -1,44 +1,44 b'' | |||
|
1 | 1 | #!/usr/bin/make -f |
|
2 | 2 | # Uncomment this to turn on verbose mode. |
|
3 | 3 | # export DH_VERBOSE=1 |
|
4 | 4 | |
|
5 | 5 | CPUS=$(shell cat /proc/cpuinfo | grep -E ^processor | wc -l) |
|
6 | 6 | |
|
7 | export HGPYTHON3=1 | |
|
8 | export PYTHON=python3 | |
|
9 | ||
|
7 | 10 | %: |
|
8 |
dh $@ --with python |
|
|
11 | dh $@ --with python3 | |
|
9 | 12 | |
|
10 | 13 | override_dh_auto_test: |
|
11 | 14 | http_proxy='' dh_auto_test -- TESTFLAGS="-j$(CPUS)" |
|
12 | 15 | |
|
13 |
override_dh_python |
|
|
14 | dh_python2 | |
|
15 | find debian/mercurial/usr/share -type d -empty -delete | |
|
16 | override_dh_python3: | |
|
17 | dh_python3 --shebang=/usr/bin/python3 | |
|
18 | ||
|
19 | override_dh_auto_clean: | |
|
20 | $(MAKE) cleanbutpackages | |
|
21 | $(MAKE) -C contrib/chg clean | |
|
16 | 22 | |
|
17 |
override_dh_ |
|
|
18 | python$(PYVERS) setup.py install --root "$(CURDIR)"/debian/mercurial --install-layout=deb | |
|
23 | override_dh_auto_build: | |
|
24 | $(MAKE) all | |
|
25 | $(MAKE) -C contrib/chg all | |
|
26 | ||
|
27 | override_dh_auto_install: | |
|
28 | python3 setup.py install --root "$(CURDIR)"/debian/mercurial --install-layout=deb | |
|
19 | 29 | # chg |
|
20 | 30 | make -C contrib/chg \ |
|
21 | 31 | DESTDIR="$(CURDIR)"/debian/mercurial \ |
|
22 | 32 | PREFIX=/usr \ |
|
23 |
|
|
|
24 | # remove arch-independent python stuff | |
|
25 |
|
|
|
26 | ! -name '*.so' ! -type d -delete , \ | |
|
27 | -type d -empty -delete | |
|
28 | python$(PYVERS) setup.py install --root "$(CURDIR)/debian/mercurial-common" --install-layout=deb | |
|
29 | make install-doc PREFIX="$(CURDIR)"/debian/mercurial-common/usr | |
|
30 | # remove arch-dependent python stuff | |
|
31 | find "$(CURDIR)"/debian/mercurial-common/usr/lib \ | |
|
32 | -name '*.so' ! -type d -delete , \ | |
|
33 | -type d -empty -delete | |
|
34 | cp contrib/hg-ssh "$(CURDIR)"/debian/mercurial-common/usr/bin | |
|
35 | mkdir -p "$(CURDIR)"/debian/mercurial-common/usr/share/mercurial | |
|
36 | cp contrib/hgk "$(CURDIR)"/debian/mercurial-common/usr/share/mercurial | |
|
37 | mkdir -p "$(CURDIR)"/debian/mercurial-common/etc/mercurial/hgrc.d/ | |
|
38 | cp contrib/packaging/debian/*.rc "$(CURDIR)"/debian/mercurial-common/etc/mercurial/hgrc.d/ | |
|
33 | install | |
|
34 | make install-doc PREFIX="$(CURDIR)"/debian/mercurial/usr | |
|
35 | cp contrib/hg-ssh "$(CURDIR)"/debian/mercurial/usr/bin | |
|
36 | mkdir -p "$(CURDIR)"/debian/mercurial/usr/share/mercurial | |
|
37 | cp contrib/hgk "$(CURDIR)"/debian/mercurial/usr/share/mercurial | |
|
38 | mkdir -p "$(CURDIR)"/debian/mercurial/etc/mercurial/hgrc.d/ | |
|
39 | cp contrib/packaging/debian/*.rc "$(CURDIR)"/debian/mercurial/etc/mercurial/hgrc.d/ | |
|
39 | 40 | # completions |
|
40 |
mkdir -p "$(CURDIR)"/debian/mercurial |
|
|
41 |
cp contrib/bash_completion "$(CURDIR)"/debian/mercurial |
|
|
42 |
mkdir -p "$(CURDIR)"/debian/mercurial |
|
|
43 |
cp contrib/zsh_completion "$(CURDIR)"/debian/mercurial |
|
|
44 | rm "$(CURDIR)"/debian/mercurial-common/usr/bin/hg | |
|
41 | mkdir -p "$(CURDIR)"/debian/mercurial/usr/share/bash-completion/completions | |
|
42 | cp contrib/bash_completion "$(CURDIR)"/debian/mercurial/usr/share/bash-completion/completions/hg | |
|
43 | mkdir -p "$(CURDIR)"/debian/mercurial/usr/share/zsh/vendor-completions | |
|
44 | cp contrib/zsh_completion "$(CURDIR)"/debian/mercurial/usr/share/zsh/vendor-completions/_hg |
@@ -1,15 +1,15 b'' | |||
|
1 |
FROM fedora: |
|
|
1 | FROM fedora:%OS_RELEASE% | |
|
2 | 2 | |
|
3 | 3 | RUN groupadd -g 1000 build && \ |
|
4 | 4 | useradd -u 1000 -g 1000 -s /bin/bash -d /build -m build |
|
5 | 5 | |
|
6 | 6 | RUN dnf install -y \ |
|
7 | 7 | gcc \ |
|
8 | 8 | gettext \ |
|
9 | 9 | make \ |
|
10 | python-devel \ | |
|
11 | python-docutils \ | |
|
10 | python3-devel \ | |
|
11 | python3-docutils \ | |
|
12 | 12 | rpm-build |
|
13 | 13 | |
|
14 | 14 | # For creating repo meta data |
|
15 | 15 | RUN dnf install -y createrepo |
@@ -1,47 +1,60 b'' | |||
|
1 | 1 | #!/bin/bash -e |
|
2 | 2 | |
|
3 | 3 | BUILDDIR=$(dirname $0) |
|
4 | 4 | export ROOTDIR=$(cd $BUILDDIR/../..; pwd) |
|
5 | 5 | |
|
6 | 6 | PLATFORM="$1" |
|
7 | 7 | shift # extra params are passed to buildrpm |
|
8 | 8 | |
|
9 | DOCKERFILE="$PLATFORM" | |
|
10 | OS_RELEASE="${PLATFORM//[a-z]/}" | |
|
11 | case "$PLATFORM" in | |
|
12 | fedora*) | |
|
13 | DOCKERFILE="${PLATFORM//[0-9]/}.template" | |
|
14 | ;; | |
|
15 | esac | |
|
16 | ||
|
9 | 17 | DOCKER=$($BUILDDIR/hg-docker docker-path) |
|
10 | 18 | |
|
11 | 19 | CONTAINER=hg-docker-$PLATFORM |
|
12 | 20 | |
|
13 | 21 | if [[ -z "${HG_DOCKER_OWN_USER}" ]]; then |
|
14 | 22 | DOCKERUID=1000 |
|
15 | 23 | DOCKERGID=1000 |
|
16 | 24 | else |
|
17 | 25 | DOCKERUID=$(id -u) |
|
18 | 26 | DOCKERGID=$(id -g) |
|
19 | 27 | fi |
|
20 | 28 | |
|
21 | $BUILDDIR/hg-docker build --build-arg UID=$DOCKERUID --build-arg GID=$DOCKERGID $BUILDDIR/docker/$PLATFORM $CONTAINER | |
|
29 | $BUILDDIR/hg-docker build \ | |
|
30 | --build-arg UID=$DOCKERUID \ | |
|
31 | --build-arg GID=$DOCKERGID \ | |
|
32 | --build-arg OS_RELEASE=${OS_RELEASE:-latest} \ | |
|
33 | $BUILDDIR/docker/$DOCKERFILE $CONTAINER | |
|
22 | 34 | |
|
23 | 35 | RPMBUILDDIR=$ROOTDIR/packages/$PLATFORM |
|
36 | mkdir -p $RPMBUILDDIR | |
|
24 | 37 | $ROOTDIR/contrib/packaging/buildrpm --rpmbuilddir $RPMBUILDDIR --prepare $* |
|
25 | 38 | |
|
26 | 39 | DSHARED=/mnt/shared |
|
27 | 40 | DBUILDUSER=build |
|
28 | 41 | |
|
29 | 42 | $DOCKER run -e http_proxy -e https_proxy -u $DBUILDUSER --rm -v $RPMBUILDDIR:$DSHARED $CONTAINER \ |
|
30 | 43 | rpmbuild --define "_topdir $DSHARED" -ba $DSHARED/SPECS/mercurial.spec --clean |
|
31 | 44 | |
|
32 | 45 | $DOCKER run -e http_proxy -e https_proxy -u $DBUILDUSER --rm -v $RPMBUILDDIR:$DSHARED $CONTAINER \ |
|
33 | 46 | createrepo $DSHARED |
|
34 | 47 | |
|
35 | 48 | cat << EOF > $RPMBUILDDIR/mercurial.repo |
|
36 | 49 | # Place this file in /etc/yum.repos.d/mercurial.repo |
|
37 | 50 | [mercurial] |
|
38 | 51 | name=Mercurial packages for $PLATFORM |
|
39 | 52 | # baseurl=file://$RPMBUILDDIR/ |
|
40 | 53 | baseurl=http://hg.example.com/build/$PLATFORM/ |
|
41 | 54 | skip_if_unavailable=True |
|
42 | 55 | gpgcheck=0 |
|
43 | 56 | enabled=1 |
|
44 | 57 | EOF |
|
45 | 58 | |
|
46 | 59 | echo |
|
47 | 60 | echo "Build complete - results can be found in $RPMBUILDDIR" |
@@ -1,38 +1,120 b'' | |||
|
1 | 1 | # |
|
2 | 2 | # This file is autogenerated by pip-compile |
|
3 | 3 | # To update, run: |
|
4 | 4 | # |
|
5 |
# pip-compile --generate-hashes contrib/packaging/inno/requirements.txt |
|
|
5 | # pip-compile --generate-hashes --output-file=contrib/packaging/inno/requirements.txt contrib/packaging/inno/requirements.txt.in | |
|
6 | 6 | # |
|
7 |
certifi==201 |
|
|
8 | --hash=sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7 \ | |
|
9 | --hash=sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033 \ | |
|
7 | certifi==2019.9.11 \ | |
|
8 | --hash=sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50 \ | |
|
9 | --hash=sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef \ | |
|
10 | 10 | # via dulwich |
|
11 | configparser==3.7.3 \ | |
|
12 | --hash=sha256:27594cf4fc279f321974061ac69164aaebd2749af962ac8686b20503ac0bcf2d \ | |
|
13 | --hash=sha256:9d51fe0a382f05b6b117c5e601fc219fede4a8c71703324af3f7d883aef476a3 \ | |
|
11 | cffi==1.13.1 \ | |
|
12 | --hash=sha256:00d890313797d9fe4420506613384b43099ad7d2b905c0752dbcc3a6f14d80fa \ | |
|
13 | --hash=sha256:0cf9e550ac6c5e57b713437e2f4ac2d7fd0cd10336525a27224f5fc1ec2ee59a \ | |
|
14 | --hash=sha256:0ea23c9c0cdd6778146a50d867d6405693ac3b80a68829966c98dd5e1bbae400 \ | |
|
15 | --hash=sha256:193697c2918ecdb3865acf6557cddf5076bb39f1f654975e087b67efdff83365 \ | |
|
16 | --hash=sha256:1ae14b542bf3b35e5229439c35653d2ef7d8316c1fffb980f9b7647e544baa98 \ | |
|
17 | --hash=sha256:1e389e069450609c6ffa37f21f40cce36f9be7643bbe5051ab1de99d5a779526 \ | |
|
18 | --hash=sha256:263242b6ace7f9cd4ea401428d2d45066b49a700852334fd55311bde36dcda14 \ | |
|
19 | --hash=sha256:33142ae9807665fa6511cfa9857132b2c3ee6ddffb012b3f0933fc11e1e830d5 \ | |
|
20 | --hash=sha256:364f8404034ae1b232335d8c7f7b57deac566f148f7222cef78cf8ae28ef764e \ | |
|
21 | --hash=sha256:47368f69fe6529f8f49a5d146ddee713fc9057e31d61e8b6dc86a6a5e38cecc1 \ | |
|
22 | --hash=sha256:4895640844f17bec32943995dc8c96989226974dfeb9dd121cc45d36e0d0c434 \ | |
|
23 | --hash=sha256:558b3afef987cf4b17abd849e7bedf64ee12b28175d564d05b628a0f9355599b \ | |
|
24 | --hash=sha256:5ba86e1d80d458b338bda676fd9f9d68cb4e7a03819632969cf6d46b01a26730 \ | |
|
25 | --hash=sha256:63424daa6955e6b4c70dc2755897f5be1d719eabe71b2625948b222775ed5c43 \ | |
|
26 | --hash=sha256:6381a7d8b1ebd0bc27c3bc85bc1bfadbb6e6f756b4d4db0aa1425c3719ba26b4 \ | |
|
27 | --hash=sha256:6381ab708158c4e1639da1f2a7679a9bbe3e5a776fc6d1fd808076f0e3145331 \ | |
|
28 | --hash=sha256:6fd58366747debfa5e6163ada468a90788411f10c92597d3b0a912d07e580c36 \ | |
|
29 | --hash=sha256:728ec653964655d65408949b07f9b2219df78badd601d6c49e28d604efe40599 \ | |
|
30 | --hash=sha256:7cfcfda59ef1f95b9f729c56fe8a4041899f96b72685d36ef16a3440a0f85da8 \ | |
|
31 | --hash=sha256:819f8d5197c2684524637f940445c06e003c4a541f9983fd30d6deaa2a5487d8 \ | |
|
32 | --hash=sha256:825ecffd9574557590e3225560a8a9d751f6ffe4a49e3c40918c9969b93395fa \ | |
|
33 | --hash=sha256:9009e917d8f5ef780c2626e29b6bc126f4cb2a4d43ca67aa2b40f2a5d6385e78 \ | |
|
34 | --hash=sha256:9c77564a51d4d914ed5af096cd9843d90c45b784b511723bd46a8a9d09cf16fc \ | |
|
35 | --hash=sha256:a19089fa74ed19c4fe96502a291cfdb89223a9705b1d73b3005df4256976142e \ | |
|
36 | --hash=sha256:a40ed527bffa2b7ebe07acc5a3f782da072e262ca994b4f2085100b5a444bbb2 \ | |
|
37 | --hash=sha256:bb75ba21d5716abc41af16eac1145ab2e471deedde1f22c6f99bd9f995504df0 \ | |
|
38 | --hash=sha256:e22a00c0c81ffcecaf07c2bfb3672fa372c50e2bd1024ffee0da191c1b27fc71 \ | |
|
39 | --hash=sha256:e55b5a746fb77f10c83e8af081979351722f6ea48facea79d470b3731c7b2891 \ | |
|
40 | --hash=sha256:ec2fa3ee81707a5232bf2dfbd6623fdb278e070d596effc7e2d788f2ada71a05 \ | |
|
41 | --hash=sha256:fd82eb4694be712fcae03c717ca2e0fc720657ac226b80bbb597e971fc6928c2 \ | |
|
42 | # via cryptography | |
|
43 | configparser==4.0.2 \ | |
|
44 | --hash=sha256:254c1d9c79f60c45dfde850850883d5aaa7f19a23f13561243a050d5a7c3fe4c \ | |
|
45 | --hash=sha256:c7d282687a5308319bf3d2e7706e575c635b0a470342641c93bea0ea3b5331df \ | |
|
14 | 46 | # via entrypoints |
|
15 | docutils==0.14 \ | |
|
16 | --hash=sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6 \ | |
|
17 | --hash=sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274 \ | |
|
18 | --hash=sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6 | |
|
19 | dulwich==0.19.11 \ | |
|
20 | --hash=sha256:afbe070f6899357e33f63f3f3696e601731fef66c64a489dea1bc9f539f4a725 | |
|
47 | cryptography==2.8 \ | |
|
48 | --hash=sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c \ | |
|
49 | --hash=sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595 \ | |
|
50 | --hash=sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad \ | |
|
51 | --hash=sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651 \ | |
|
52 | --hash=sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2 \ | |
|
53 | --hash=sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff \ | |
|
54 | --hash=sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d \ | |
|
55 | --hash=sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42 \ | |
|
56 | --hash=sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d \ | |
|
57 | --hash=sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e \ | |
|
58 | --hash=sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912 \ | |
|
59 | --hash=sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793 \ | |
|
60 | --hash=sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13 \ | |
|
61 | --hash=sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7 \ | |
|
62 | --hash=sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0 \ | |
|
63 | --hash=sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879 \ | |
|
64 | --hash=sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f \ | |
|
65 | --hash=sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9 \ | |
|
66 | --hash=sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2 \ | |
|
67 | --hash=sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf \ | |
|
68 | --hash=sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8 \ | |
|
69 | # via secretstorage | |
|
70 | docutils==0.15.2 \ | |
|
71 | --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ | |
|
72 | --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ | |
|
73 | --hash=sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99 | |
|
74 | dulwich==0.19.13 \ | |
|
75 | --hash=sha256:0e442f6f96e6d97270a7cca4e75306b6b0228627bdf57dde3759e0e345a6b523 \ | |
|
76 | --hash=sha256:667f49536ccba09d3b90bac80d44048e45566f84b98a5e139cc8c70757a6ae60 \ | |
|
77 | --hash=sha256:82792a9d49b112fa2151fa0fb29b01667855a843ff99325b1c1578a4aec11b57 \ | |
|
78 | --hash=sha256:aa628449c5f594a9a282f4d9e5993fef65481ef5e3b9b6c52ff31200f8f5dc95 \ | |
|
79 | --hash=sha256:ab4668bc4e1996d12eb1910e123a09edcff8e166e7ec46db5aafb5c7e250b99f \ | |
|
80 | --hash=sha256:c35ed2cd5b263ce0d67758ffba590c0466ff13b048457ff060b7d2e6cb55a40e \ | |
|
81 | --hash=sha256:c8b48079a14850cbeb788b38e1061ae6db75061431c1c0f91382460be4c84bbe \ | |
|
82 | --hash=sha256:dfcd9943c69f963dd61a027f480d16f548ea5905c2485be8f4b8f130df2c32de \ | |
|
83 | --hash=sha256:e3693c3238c1a5fc1e4427281c4455d78549f4797f2a7107a5f4443b21efafb4 | |
|
21 | 84 | entrypoints==0.3 \ |
|
22 | 85 | --hash=sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19 \ |
|
23 | 86 | --hash=sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451 \ |
|
24 | 87 | # via keyring |
|
88 | enum34==1.1.6 \ | |
|
89 | --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \ | |
|
90 | --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \ | |
|
91 | --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \ | |
|
92 | --hash=sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1 \ | |
|
93 | # via cryptography | |
|
94 | ipaddress==1.0.23 \ | |
|
95 | --hash=sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc \ | |
|
96 | --hash=sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2 \ | |
|
97 | # via cryptography | |
|
25 | 98 | keyring==18.0.1 \ |
|
26 | 99 | --hash=sha256:67d6cc0132bd77922725fae9f18366bb314fd8f95ff4d323a4df41890a96a838 \ |
|
27 | 100 | --hash=sha256:7b29ebfcf8678c4da531b2478a912eea01e80007e5ddca9ee0c7038cb3489ec6 |
|
28 | pygments==2.3.1 \ | |
|
29 | --hash=sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a \ | |
|
30 | --hash=sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d | |
|
101 | pycparser==2.19 \ | |
|
102 | --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 \ | |
|
103 | # via cffi | |
|
104 | pygments==2.4.2 \ | |
|
105 | --hash=sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127 \ | |
|
106 | --hash=sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297 | |
|
31 | 107 | pywin32-ctypes==0.2.0 \ |
|
32 | 108 | --hash=sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942 \ |
|
33 |
--hash=sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98 |
|
|
109 | --hash=sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98 | |
|
110 | secretstorage==2.3.1 \ | |
|
111 | --hash=sha256:3af65c87765323e6f64c83575b05393f9e003431959c9395d1791d51497f29b6 \ | |
|
34 | 112 | # via keyring |
|
35 |
|
|
|
36 | --hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \ | |
|
37 | --hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22 \ | |
|
113 | six==1.12.0 \ | |
|
114 | --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ | |
|
115 | --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 \ | |
|
116 | # via cryptography | |
|
117 | urllib3==1.25.6 \ | |
|
118 | --hash=sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398 \ | |
|
119 | --hash=sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86 \ | |
|
38 | 120 | # via dulwich |
@@ -1,4 +1,7 b'' | |||
|
1 | 1 | docutils |
|
2 | 2 | dulwich |
|
3 | 3 | keyring |
|
4 | 4 | pygments |
|
5 | # Need to list explicitly so dependency gets pulled in when | |
|
6 | # not running on Windows. | |
|
7 | pywin32-ctypes |
@@ -1,164 +1,172 b'' | |||
|
1 | 1 | %global emacs_lispdir %{_datadir}/emacs/site-lisp |
|
2 | 2 | |
|
3 | 3 | %define withpython %{nil} |
|
4 | 4 | |
|
5 | %global pythonexe python3 | |
|
6 | ||
|
5 | 7 | %if "%{?withpython}" |
|
6 | 8 | |
|
7 | 9 | %global pythonver %{withpython} |
|
8 | 10 | %global pythonname Python-%{withpython} |
|
9 | 11 | %global docutilsname docutils-0.14 |
|
10 | 12 | %global docutilsmd5 c53768d63db3873b7d452833553469de |
|
11 | 13 | %global pythonhg python-hg |
|
12 | 14 | %global hgpyprefix /opt/%{pythonhg} |
|
13 | 15 | # byte compilation will fail on some some Python /test/ files |
|
14 | 16 | %global _python_bytecompile_errors_terminate_build 0 |
|
15 | 17 | |
|
16 | 18 | %else |
|
17 | 19 | |
|
18 |
%global pythonver %(python -c 'import sys;print |
|
|
20 | %global pythonver %(%{pythonexe} -c 'import sys;print(".".join(map(str, sys.version_info[:2])))') | |
|
19 | 21 | |
|
20 | 22 | %endif |
|
21 | 23 | |
|
22 | 24 | Summary: A fast, lightweight Source Control Management system |
|
23 | 25 | Name: mercurial |
|
24 | 26 | Version: snapshot |
|
25 | 27 | Release: 0 |
|
26 | 28 | License: GPLv2+ |
|
27 | 29 | Group: Development/Tools |
|
28 | 30 | URL: https://mercurial-scm.org/ |
|
29 | 31 | Source0: %{name}-%{version}-%{release}.tar.gz |
|
30 | 32 | %if "%{?withpython}" |
|
31 | 33 | Source1: %{pythonname}.tgz |
|
32 | 34 | Source2: %{docutilsname}.tar.gz |
|
33 | 35 | %endif |
|
34 | 36 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root |
|
35 | 37 | |
|
36 | 38 | BuildRequires: make, gcc, gettext |
|
37 | 39 | %if "%{?withpython}" |
|
38 | 40 | BuildRequires: readline-devel, openssl-devel, ncurses-devel, zlib-devel, bzip2-devel |
|
39 | 41 | %else |
|
40 |
BuildRequires: python >= |
|
|
41 |
Requires: python >= |
|
|
42 | BuildRequires: %{pythonexe} >= %{pythonver}, %{pythonexe}-devel, %{pythonexe}-docutils >= 0.5 | |
|
43 | Requires: %{pythonexe} >= %{pythonver} | |
|
42 | 44 | %endif |
|
43 | 45 | # The hgk extension uses the wish tcl interpreter, but we don't enforce it |
|
44 | 46 | #Requires: tk |
|
45 | 47 | |
|
46 | 48 | %description |
|
47 | 49 | Mercurial is a fast, lightweight source control management system designed |
|
48 | 50 | for efficient handling of very large distributed projects. |
|
49 | 51 | |
|
50 | 52 | %prep |
|
51 | 53 | |
|
52 | 54 | %if "%{?withpython}" |
|
53 | 55 | %setup -q -n mercurial-%{version}-%{release} -a1 -a2 |
|
54 | 56 | # despite the comments in cgi.py, we do this to prevent rpmdeps from picking /usr/local/bin/python up |
|
55 | sed -i '1c#! /usr/bin/env python' %{pythonname}/Lib/cgi.py | |
|
57 | sed -i '1c#! /usr/bin/env %{pythonexe}' %{pythonname}/Lib/cgi.py | |
|
56 | 58 | %else |
|
57 | 59 | %setup -q -n mercurial-%{version}-%{release} |
|
58 | 60 | %endif |
|
59 | 61 | |
|
60 | 62 | %build |
|
61 | 63 | |
|
64 | export HGPYTHON3=1 | |
|
65 | ||
|
62 | 66 | %if "%{?withpython}" |
|
63 | 67 | |
|
64 | 68 | PYPATH=$PWD/%{pythonname} |
|
65 | 69 | cd $PYPATH |
|
66 | 70 | ./configure --prefix=%{hgpyprefix} |
|
67 | 71 | make all %{?_smp_mflags} |
|
68 | 72 | cd - |
|
69 | 73 | |
|
70 | 74 | cd %{docutilsname} |
|
71 | 75 | LD_LIBRARY_PATH=$PYPATH $PYPATH/python setup.py build |
|
72 | 76 | cd - |
|
73 | 77 | |
|
74 | 78 | # verify Python environment |
|
75 | 79 | LD_LIBRARY_PATH=$PYPATH PYTHONPATH=$PWD/%{docutilsname} $PYPATH/python -c 'import sys, zlib, bz2, ssl, curses, readline' |
|
76 | 80 | |
|
77 | 81 | # set environment for make |
|
78 | 82 | export PATH=$PYPATH:$PATH |
|
79 | 83 | export LD_LIBRARY_PATH=$PYPATH |
|
80 | 84 | export CFLAGS="-L $PYPATH" |
|
81 | 85 | export PYTHONPATH=$PWD/%{docutilsname} |
|
82 | 86 | |
|
83 | 87 | %endif |
|
84 | 88 | |
|
85 | make all | |
|
89 | make all PYTHON=%{pythonexe} | |
|
86 | 90 | make -C contrib/chg |
|
87 | 91 | |
|
92 | sed -i -e '1s|#!/usr/bin/env python$|#!/usr/bin/env %{pythonexe}|' contrib/hg-ssh | |
|
93 | ||
|
88 | 94 | %install |
|
89 | 95 | rm -rf $RPM_BUILD_ROOT |
|
90 | 96 | |
|
97 | export HGPYTHON3=1 | |
|
98 | ||
|
91 | 99 | %if "%{?withpython}" |
|
92 | 100 | |
|
93 | 101 | PYPATH=$PWD/%{pythonname} |
|
94 | 102 | cd $PYPATH |
|
95 | 103 | make install DESTDIR=$RPM_BUILD_ROOT |
|
96 | 104 | # these .a are not necessary and they are readonly and strip fails - kill them! |
|
97 | 105 | rm -f %{buildroot}%{hgpyprefix}/lib/{,python2.*/config}/libpython2.*.a |
|
98 | 106 | cd - |
|
99 | 107 | |
|
100 | 108 | cd %{docutilsname} |
|
101 | 109 | LD_LIBRARY_PATH=$PYPATH $PYPATH/python setup.py install --root="$RPM_BUILD_ROOT" |
|
102 | 110 | cd - |
|
103 | 111 | |
|
104 | PATH=$PYPATH:$PATH LD_LIBRARY_PATH=$PYPATH make install DESTDIR=$RPM_BUILD_ROOT PREFIX=%{hgpyprefix} MANDIR=%{_mandir} | |
|
112 | PATH=$PYPATH:$PATH LD_LIBRARY_PATH=$PYPATH make install PYTHON=%{pythonexe} DESTDIR=$RPM_BUILD_ROOT PREFIX=%{hgpyprefix} MANDIR=%{_mandir} | |
|
105 | 113 | mkdir -p $RPM_BUILD_ROOT%{_bindir} |
|
106 | 114 | ( cd $RPM_BUILD_ROOT%{_bindir}/ && ln -s ../..%{hgpyprefix}/bin/hg . ) |
|
107 | 115 | ( cd $RPM_BUILD_ROOT%{_bindir}/ && ln -s ../..%{hgpyprefix}/bin/python2.? %{pythonhg} ) |
|
108 | 116 | |
|
109 | 117 | %else |
|
110 | 118 | |
|
111 | make install DESTDIR=$RPM_BUILD_ROOT PREFIX=%{_prefix} MANDIR=%{_mandir} | |
|
119 | make install PYTHON=%{pythonexe} DESTDIR=$RPM_BUILD_ROOT PREFIX=%{_prefix} MANDIR=%{_mandir} | |
|
112 | 120 | |
|
113 | 121 | %endif |
|
114 | 122 | |
|
115 | 123 | install -m 755 contrib/chg/chg $RPM_BUILD_ROOT%{_bindir}/ |
|
116 | 124 | install -m 755 contrib/hgk $RPM_BUILD_ROOT%{_bindir}/ |
|
117 | 125 | install -m 755 contrib/hg-ssh $RPM_BUILD_ROOT%{_bindir}/ |
|
118 | 126 | |
|
119 | 127 | bash_completion_dir=$RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d |
|
120 | 128 | mkdir -p $bash_completion_dir |
|
121 | 129 | install -m 644 contrib/bash_completion $bash_completion_dir/mercurial.sh |
|
122 | 130 | |
|
123 | 131 | zsh_completion_dir=$RPM_BUILD_ROOT%{_datadir}/zsh/site-functions |
|
124 | 132 | mkdir -p $zsh_completion_dir |
|
125 | 133 | install -m 644 contrib/zsh_completion $zsh_completion_dir/_mercurial |
|
126 | 134 | |
|
127 | 135 | mkdir -p $RPM_BUILD_ROOT%{emacs_lispdir} |
|
128 | 136 | install -m 644 contrib/mercurial.el $RPM_BUILD_ROOT%{emacs_lispdir}/ |
|
129 | 137 | install -m 644 contrib/mq.el $RPM_BUILD_ROOT%{emacs_lispdir}/ |
|
130 | 138 | |
|
131 | 139 | mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/mercurial/hgrc.d |
|
132 | 140 | |
|
133 | 141 | %clean |
|
134 | 142 | rm -rf $RPM_BUILD_ROOT |
|
135 | 143 | |
|
136 | 144 | %files |
|
137 | 145 | %defattr(-,root,root,-) |
|
138 | %doc CONTRIBUTORS COPYING doc/README doc/hg*.txt doc/hg*.html *.cgi contrib/*.fcgi | |
|
146 | %doc CONTRIBUTORS COPYING doc/README doc/hg*.txt doc/hg*.html *.cgi contrib/*.fcgi contrib/*.wsgi | |
|
139 | 147 | %doc %attr(644,root,root) %{_mandir}/man?/hg* |
|
140 | 148 | %doc %attr(644,root,root) contrib/*.svg |
|
141 | 149 | %dir %{_datadir}/zsh/ |
|
142 | 150 | %dir %{_datadir}/zsh/site-functions/ |
|
143 | 151 | %{_datadir}/zsh/site-functions/_mercurial |
|
144 | 152 | %dir %{_datadir}/emacs/site-lisp/ |
|
145 | 153 | %{_datadir}/emacs/site-lisp/mercurial.el |
|
146 | 154 | %{_datadir}/emacs/site-lisp/mq.el |
|
147 | 155 | %{_bindir}/hg |
|
148 | 156 | %{_bindir}/chg |
|
149 | 157 | %{_bindir}/hgk |
|
150 | 158 | %{_bindir}/hg-ssh |
|
151 | 159 | %dir %{_sysconfdir}/bash_completion.d/ |
|
152 | 160 | %config(noreplace) %{_sysconfdir}/bash_completion.d/mercurial.sh |
|
153 | 161 | %dir %{_sysconfdir}/mercurial |
|
154 | 162 | %dir %{_sysconfdir}/mercurial/hgrc.d |
|
155 | 163 | %if "%{?withpython}" |
|
156 | 164 | %{_bindir}/%{pythonhg} |
|
157 | 165 | %{hgpyprefix} |
|
158 | 166 | %else |
|
159 | 167 | %{_libdir}/python%{pythonver}/site-packages/%{name}-*-py%{pythonver}.egg-info |
|
160 | 168 | %{_libdir}/python%{pythonver}/site-packages/%{name} |
|
161 | 169 | %{_libdir}/python%{pythonver}/site-packages/hgext |
|
162 | 170 | %{_libdir}/python%{pythonver}/site-packages/hgext3rd |
|
163 | 171 | %{_libdir}/python%{pythonver}/site-packages/hgdemandimport |
|
164 | 172 | %endif |
@@ -1,13 +1,13 b'' | |||
|
1 | 1 | # |
|
2 | 2 | # This file is autogenerated by pip-compile |
|
3 | 3 | # To update, run: |
|
4 | 4 | # |
|
5 |
# pip-compile --generate-hashes contrib/packaging/wix/requirements.txt |
|
|
5 | # pip-compile --generate-hashes --output-file=contrib/packaging/wix/requirements.txt contrib/packaging/wix/requirements.txt.in | |
|
6 | 6 | # |
|
7 |
docutils==0.1 |
|
|
8 | --hash=sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6 \ | |
|
9 | --hash=sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274 \ | |
|
10 | --hash=sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6 | |
|
11 |
pygments==2. |
|
|
12 | --hash=sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a \ | |
|
13 | --hash=sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d | |
|
7 | docutils==0.15.2 \ | |
|
8 | --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ | |
|
9 | --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ | |
|
10 | --hash=sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99 | |
|
11 | pygments==2.4.2 \ | |
|
12 | --hash=sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127 \ | |
|
13 | --hash=sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297 |
@@ -1,87 +1,87 b'' | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | from __future__ import absolute_import |
|
3 | 3 | |
|
4 | 4 | import getopt |
|
5 | 5 | import sys |
|
6 | 6 | |
|
7 | 7 | import hgdemandimport |
|
8 | 8 | hgdemandimport.enable() |
|
9 | 9 | |
|
10 | 10 | from mercurial.i18n import _ |
|
11 | 11 | from mercurial import ( |
|
12 | 12 | context, |
|
13 | 13 | error, |
|
14 | 14 | fancyopts, |
|
15 | 15 | pycompat, |
|
16 | 16 | simplemerge, |
|
17 | 17 | ui as uimod, |
|
18 | 18 | ) |
|
19 | 19 | from mercurial.utils import ( |
|
20 | 20 | procutil, |
|
21 | stringutil | |
|
21 | 22 | ) |
|
22 | 23 | |
|
23 | 24 | options = [(b'L', b'label', [], _(b'labels to use on conflict markers')), |
|
24 | 25 | (b'a', b'text', None, _(b'treat all files as text')), |
|
25 | 26 | (b'p', b'print', None, |
|
26 | 27 | _(b'print results instead of overwriting LOCAL')), |
|
27 | 28 | (b'', b'no-minimal', None, _(b'no effect (DEPRECATED)')), |
|
28 | 29 | (b'h', b'help', None, _(b'display help and exit')), |
|
29 | 30 | (b'q', b'quiet', None, _(b'suppress output'))] |
|
30 | 31 | |
|
31 | 32 | usage = _(b'''simplemerge [OPTS] LOCAL BASE OTHER |
|
32 | 33 | |
|
33 | 34 | Simple three-way file merge utility with a minimal feature set. |
|
34 | 35 | |
|
35 | 36 | Apply to LOCAL the changes necessary to go from BASE to OTHER. |
|
36 | 37 | |
|
37 | 38 | By default, LOCAL is overwritten with the results of this operation. |
|
38 | 39 | ''') |
|
39 | 40 | |
|
40 | 41 | class ParseError(Exception): |
|
41 | 42 | """Exception raised on errors in parsing the command line.""" |
|
42 | 43 | |
|
43 | 44 | def showhelp(): |
|
44 | 45 | pycompat.stdout.write(usage) |
|
45 | 46 | pycompat.stdout.write(b'\noptions:\n') |
|
46 | 47 | |
|
47 | 48 | out_opts = [] |
|
48 | 49 | for shortopt, longopt, default, desc in options: |
|
49 | 50 | out_opts.append((b'%2s%s' % (shortopt and b'-%s' % shortopt, |
|
50 | 51 | longopt and b' --%s' % longopt), |
|
51 | 52 | b'%s' % desc)) |
|
52 | 53 | opts_len = max([len(opt[0]) for opt in out_opts]) |
|
53 | 54 | for first, second in out_opts: |
|
54 | 55 | pycompat.stdout.write(b' %-*s %s\n' % (opts_len, first, second)) |
|
55 | 56 | |
|
56 | 57 | try: |
|
57 | 58 | for fp in (sys.stdin, pycompat.stdout, sys.stderr): |
|
58 | 59 | procutil.setbinary(fp) |
|
59 | 60 | |
|
60 | 61 | opts = {} |
|
61 | 62 | try: |
|
62 | 63 | bargv = [a.encode('utf8') for a in sys.argv[1:]] |
|
63 | 64 | args = fancyopts.fancyopts(bargv, options, opts) |
|
64 | 65 | except getopt.GetoptError as e: |
|
65 | 66 | raise ParseError(e) |
|
66 | 67 | if opts[b'help']: |
|
67 | 68 | showhelp() |
|
68 | 69 | sys.exit(0) |
|
69 | 70 | if len(args) != 3: |
|
70 | 71 | raise ParseError(_(b'wrong number of arguments').decode('utf8')) |
|
71 | 72 | local, base, other = args |
|
72 | 73 | sys.exit(simplemerge.simplemerge(uimod.ui.load(), |
|
73 | 74 | context.arbitraryfilectx(local), |
|
74 | 75 | context.arbitraryfilectx(base), |
|
75 | 76 | context.arbitraryfilectx(other), |
|
76 | 77 | **pycompat.strkwargs(opts))) |
|
77 | 78 | except ParseError as e: |
|
78 | if pycompat.ispy3: | |
|
79 | e = str(e).encode('utf8') | |
|
79 | e = stringutil.forcebytestr(e) | |
|
80 | 80 | pycompat.stdout.write(b"%s: %s\n" % (sys.argv[0].encode('utf8'), e)) |
|
81 | 81 | showhelp() |
|
82 | 82 | sys.exit(1) |
|
83 | 83 | except error.Abort as e: |
|
84 | 84 | pycompat.stderr.write(b"abort: %s\n" % e) |
|
85 | 85 | sys.exit(255) |
|
86 | 86 | except KeyboardInterrupt: |
|
87 | 87 | sys.exit(255) |
@@ -1,124 +1,128 b'' | |||
|
1 | 1 | # demandimportpy3 - global demand-loading of modules for Mercurial |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2017 Facebook Inc. |
|
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 | """Lazy loading for Python 3.6 and above. |
|
9 | 9 | |
|
10 | 10 | This uses the new importlib finder/loader functionality available in Python 3.5 |
|
11 | 11 | and up. The code reuses most of the mechanics implemented inside importlib.util, |
|
12 | 12 | but with a few additions: |
|
13 | 13 | |
|
14 | 14 | * Allow excluding certain modules from lazy imports. |
|
15 | 15 | * Expose an interface that's substantially the same as demandimport for |
|
16 | 16 | Python 2. |
|
17 | 17 | |
|
18 | 18 | This also has some limitations compared to the Python 2 implementation: |
|
19 | 19 | |
|
20 | 20 | * Much of the logic is per-package, not per-module, so any packages loaded |
|
21 | 21 | before demandimport is enabled will not be lazily imported in the future. In |
|
22 | 22 | practice, we only expect builtins to be loaded before demandimport is |
|
23 | 23 | enabled. |
|
24 | 24 | """ |
|
25 | 25 | |
|
26 | 26 | # This line is unnecessary, but it satisfies test-check-py3-compat.t. |
|
27 | 27 | from __future__ import absolute_import |
|
28 | 28 | |
|
29 | 29 | import contextlib |
|
30 | 30 | import importlib.abc |
|
31 | 31 | import importlib.machinery |
|
32 | 32 | import importlib.util |
|
33 | 33 | import sys |
|
34 | 34 | |
|
35 | 35 | from . import tracing |
|
36 | 36 | |
|
37 | 37 | _deactivated = False |
|
38 | 38 | |
|
39 | 39 | |
|
40 | 40 | class _lazyloaderex(importlib.util.LazyLoader): |
|
41 | 41 | """This is a LazyLoader except it also follows the _deactivated global and |
|
42 | 42 | the ignore list. |
|
43 | 43 | """ |
|
44 | 44 | |
|
45 | 45 | def exec_module(self, module): |
|
46 | 46 | """Make the module load lazily.""" |
|
47 | 47 | with tracing.log('demandimport %s', module): |
|
48 | 48 | if _deactivated or module.__name__ in ignores: |
|
49 | 49 | self.loader.exec_module(module) |
|
50 | 50 | else: |
|
51 | 51 | super().exec_module(module) |
|
52 | 52 | |
|
53 | 53 | |
|
54 | 54 | # This is 3.6+ because with Python 3.5 it isn't possible to lazily load |
|
55 | 55 | # extensions. See the discussion in https://bugs.python.org/issue26186 for more. |
|
56 | _extensions_loader = _lazyloaderex.factory( | |
|
57 | importlib.machinery.ExtensionFileLoader | |
|
58 | ) | |
|
56 | if sys.version_info[0:2] >= (3, 6): | |
|
57 | _extensions_loader = _lazyloaderex.factory( | |
|
58 | importlib.machinery.ExtensionFileLoader | |
|
59 | ) | |
|
60 | else: | |
|
61 | _extensions_loader = importlib.machinery.ExtensionFileLoader | |
|
62 | ||
|
59 | 63 | _bytecode_loader = _lazyloaderex.factory( |
|
60 | 64 | importlib.machinery.SourcelessFileLoader |
|
61 | 65 | ) |
|
62 | 66 | _source_loader = _lazyloaderex.factory(importlib.machinery.SourceFileLoader) |
|
63 | 67 | |
|
64 | 68 | |
|
65 | 69 | def _makefinder(path): |
|
66 | 70 | return importlib.machinery.FileFinder( |
|
67 | 71 | path, |
|
68 | 72 | # This is the order in which loaders are passed in in core Python. |
|
69 | 73 | (_extensions_loader, importlib.machinery.EXTENSION_SUFFIXES), |
|
70 | 74 | (_source_loader, importlib.machinery.SOURCE_SUFFIXES), |
|
71 | 75 | (_bytecode_loader, importlib.machinery.BYTECODE_SUFFIXES), |
|
72 | 76 | ) |
|
73 | 77 | |
|
74 | 78 | |
|
75 | 79 | ignores = set() |
|
76 | 80 | |
|
77 | 81 | |
|
78 | 82 | def init(ignoreset): |
|
79 | 83 | global ignores |
|
80 | 84 | ignores = ignoreset |
|
81 | 85 | |
|
82 | 86 | |
|
83 | 87 | def isenabled(): |
|
84 | 88 | return _makefinder in sys.path_hooks and not _deactivated |
|
85 | 89 | |
|
86 | 90 | |
|
87 | 91 | def disable(): |
|
88 | 92 | try: |
|
89 | 93 | while True: |
|
90 | 94 | sys.path_hooks.remove(_makefinder) |
|
91 | 95 | except ValueError: |
|
92 | 96 | pass |
|
93 | 97 | |
|
94 | 98 | |
|
95 | 99 | def enable(): |
|
96 | 100 | sys.path_hooks.insert(0, _makefinder) |
|
97 | 101 | |
|
98 | 102 | |
|
99 | 103 | @contextlib.contextmanager |
|
100 | 104 | def deactivated(): |
|
101 | 105 | # This implementation is a bit different from Python 2's. Python 3 |
|
102 | 106 | # maintains a per-package finder cache in sys.path_importer_cache (see |
|
103 | 107 | # PEP 302). This means that we can't just call disable + enable. |
|
104 | 108 | # If we do that, in situations like: |
|
105 | 109 | # |
|
106 | 110 | # demandimport.enable() |
|
107 | 111 | # ... |
|
108 | 112 | # from foo.bar import mod1 |
|
109 | 113 | # with demandimport.deactivated(): |
|
110 | 114 | # from foo.bar import mod2 |
|
111 | 115 | # |
|
112 | 116 | # mod2 will be imported lazily. (The converse also holds -- whatever finder |
|
113 | 117 | # first gets cached will be used.) |
|
114 | 118 | # |
|
115 | 119 | # Instead, have a global flag the LazyLoader can use. |
|
116 | 120 | global _deactivated |
|
117 | 121 | demandenabled = isenabled() |
|
118 | 122 | if demandenabled: |
|
119 | 123 | _deactivated = True |
|
120 | 124 | try: |
|
121 | 125 | yield |
|
122 | 126 | finally: |
|
123 | 127 | if demandenabled: |
|
124 | 128 | _deactivated = False |
@@ -1,1215 +1,1215 b'' | |||
|
1 | 1 | # bugzilla.py - bugzilla integration for mercurial |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> |
|
4 | 4 | # Copyright 2011-4 Jim Hague <jim.hague@acm.org> |
|
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 | '''hooks for integrating with the Bugzilla bug tracker |
|
10 | 10 | |
|
11 | 11 | This hook extension adds comments on bugs in Bugzilla when changesets |
|
12 | 12 | that refer to bugs by Bugzilla ID are seen. The comment is formatted using |
|
13 | 13 | the Mercurial template mechanism. |
|
14 | 14 | |
|
15 | 15 | The bug references can optionally include an update for Bugzilla of the |
|
16 | 16 | hours spent working on the bug. Bugs can also be marked fixed. |
|
17 | 17 | |
|
18 | 18 | Four basic modes of access to Bugzilla are provided: |
|
19 | 19 | |
|
20 | 20 | 1. Access via the Bugzilla REST-API. Requires bugzilla 5.0 or later. |
|
21 | 21 | |
|
22 | 22 | 2. Access via the Bugzilla XMLRPC interface. Requires Bugzilla 3.4 or later. |
|
23 | 23 | |
|
24 | 24 | 3. Check data via the Bugzilla XMLRPC interface and submit bug change |
|
25 | 25 | via email to Bugzilla email interface. Requires Bugzilla 3.4 or later. |
|
26 | 26 | |
|
27 | 27 | 4. Writing directly to the Bugzilla database. Only Bugzilla installations |
|
28 | 28 | using MySQL are supported. Requires Python MySQLdb. |
|
29 | 29 | |
|
30 | 30 | Writing directly to the database is susceptible to schema changes, and |
|
31 | 31 | relies on a Bugzilla contrib script to send out bug change |
|
32 | 32 | notification emails. This script runs as the user running Mercurial, |
|
33 | 33 | must be run on the host with the Bugzilla install, and requires |
|
34 | 34 | permission to read Bugzilla configuration details and the necessary |
|
35 | 35 | MySQL user and password to have full access rights to the Bugzilla |
|
36 | 36 | database. For these reasons this access mode is now considered |
|
37 | 37 | deprecated, and will not be updated for new Bugzilla versions going |
|
38 | 38 | forward. Only adding comments is supported in this access mode. |
|
39 | 39 | |
|
40 | 40 | Access via XMLRPC needs a Bugzilla username and password to be specified |
|
41 | 41 | in the configuration. Comments are added under that username. Since the |
|
42 | 42 | configuration must be readable by all Mercurial users, it is recommended |
|
43 | 43 | that the rights of that user are restricted in Bugzilla to the minimum |
|
44 | 44 | necessary to add comments. Marking bugs fixed requires Bugzilla 4.0 and later. |
|
45 | 45 | |
|
46 | 46 | Access via XMLRPC/email uses XMLRPC to query Bugzilla, but sends |
|
47 | 47 | email to the Bugzilla email interface to submit comments to bugs. |
|
48 | 48 | The From: address in the email is set to the email address of the Mercurial |
|
49 | 49 | user, so the comment appears to come from the Mercurial user. In the event |
|
50 | 50 | that the Mercurial user email is not recognized by Bugzilla as a Bugzilla |
|
51 | 51 | user, the email associated with the Bugzilla username used to log into |
|
52 | 52 | Bugzilla is used instead as the source of the comment. Marking bugs fixed |
|
53 | 53 | works on all supported Bugzilla versions. |
|
54 | 54 | |
|
55 | 55 | Access via the REST-API needs either a Bugzilla username and password |
|
56 | 56 | or an apikey specified in the configuration. Comments are made under |
|
57 | 57 | the given username or the user associated with the apikey in Bugzilla. |
|
58 | 58 | |
|
59 | 59 | Configuration items common to all access modes: |
|
60 | 60 | |
|
61 | 61 | bugzilla.version |
|
62 | 62 | The access type to use. Values recognized are: |
|
63 | 63 | |
|
64 | 64 | :``restapi``: Bugzilla REST-API, Bugzilla 5.0 and later. |
|
65 | 65 | :``xmlrpc``: Bugzilla XMLRPC interface. |
|
66 | 66 | :``xmlrpc+email``: Bugzilla XMLRPC and email interfaces. |
|
67 | 67 | :``3.0``: MySQL access, Bugzilla 3.0 and later. |
|
68 | 68 | :``2.18``: MySQL access, Bugzilla 2.18 and up to but not |
|
69 | 69 | including 3.0. |
|
70 | 70 | :``2.16``: MySQL access, Bugzilla 2.16 and up to but not |
|
71 | 71 | including 2.18. |
|
72 | 72 | |
|
73 | 73 | bugzilla.regexp |
|
74 | 74 | Regular expression to match bug IDs for update in changeset commit message. |
|
75 | 75 | It must contain one "()" named group ``<ids>`` containing the bug |
|
76 | 76 | IDs separated by non-digit characters. It may also contain |
|
77 | 77 | a named group ``<hours>`` with a floating-point number giving the |
|
78 | 78 | hours worked on the bug. If no named groups are present, the first |
|
79 | 79 | "()" group is assumed to contain the bug IDs, and work time is not |
|
80 | 80 | updated. The default expression matches ``Bug 1234``, ``Bug no. 1234``, |
|
81 | 81 | ``Bug number 1234``, ``Bugs 1234,5678``, ``Bug 1234 and 5678`` and |
|
82 | 82 | variations thereof, followed by an hours number prefixed by ``h`` or |
|
83 | 83 | ``hours``, e.g. ``hours 1.5``. Matching is case insensitive. |
|
84 | 84 | |
|
85 | 85 | bugzilla.fixregexp |
|
86 | 86 | Regular expression to match bug IDs for marking fixed in changeset |
|
87 | 87 | commit message. This must contain a "()" named group ``<ids>` containing |
|
88 | 88 | the bug IDs separated by non-digit characters. It may also contain |
|
89 | 89 | a named group ``<hours>`` with a floating-point number giving the |
|
90 | 90 | hours worked on the bug. If no named groups are present, the first |
|
91 | 91 | "()" group is assumed to contain the bug IDs, and work time is not |
|
92 | 92 | updated. The default expression matches ``Fixes 1234``, ``Fixes bug 1234``, |
|
93 | 93 | ``Fixes bugs 1234,5678``, ``Fixes 1234 and 5678`` and |
|
94 | 94 | variations thereof, followed by an hours number prefixed by ``h`` or |
|
95 | 95 | ``hours``, e.g. ``hours 1.5``. Matching is case insensitive. |
|
96 | 96 | |
|
97 | 97 | bugzilla.fixstatus |
|
98 | 98 | The status to set a bug to when marking fixed. Default ``RESOLVED``. |
|
99 | 99 | |
|
100 | 100 | bugzilla.fixresolution |
|
101 | 101 | The resolution to set a bug to when marking fixed. Default ``FIXED``. |
|
102 | 102 | |
|
103 | 103 | bugzilla.style |
|
104 | 104 | The style file to use when formatting comments. |
|
105 | 105 | |
|
106 | 106 | bugzilla.template |
|
107 | 107 | Template to use when formatting comments. Overrides style if |
|
108 | 108 | specified. In addition to the usual Mercurial keywords, the |
|
109 | 109 | extension specifies: |
|
110 | 110 | |
|
111 | 111 | :``{bug}``: The Bugzilla bug ID. |
|
112 | 112 | :``{root}``: The full pathname of the Mercurial repository. |
|
113 | 113 | :``{webroot}``: Stripped pathname of the Mercurial repository. |
|
114 | 114 | :``{hgweb}``: Base URL for browsing Mercurial repositories. |
|
115 | 115 | |
|
116 | 116 | Default ``changeset {node|short} in repo {root} refers to bug |
|
117 | 117 | {bug}.\\ndetails:\\n\\t{desc|tabindent}`` |
|
118 | 118 | |
|
119 | 119 | bugzilla.strip |
|
120 | 120 | The number of path separator characters to strip from the front of |
|
121 | 121 | the Mercurial repository path (``{root}`` in templates) to produce |
|
122 | 122 | ``{webroot}``. For example, a repository with ``{root}`` |
|
123 | 123 | ``/var/local/my-project`` with a strip of 2 gives a value for |
|
124 | 124 | ``{webroot}`` of ``my-project``. Default 0. |
|
125 | 125 | |
|
126 | 126 | web.baseurl |
|
127 | 127 | Base URL for browsing Mercurial repositories. Referenced from |
|
128 | 128 | templates as ``{hgweb}``. |
|
129 | 129 | |
|
130 | 130 | Configuration items common to XMLRPC+email and MySQL access modes: |
|
131 | 131 | |
|
132 | 132 | bugzilla.usermap |
|
133 | 133 | Path of file containing Mercurial committer email to Bugzilla user email |
|
134 | 134 | mappings. If specified, the file should contain one mapping per |
|
135 | 135 | line:: |
|
136 | 136 | |
|
137 | 137 | committer = Bugzilla user |
|
138 | 138 | |
|
139 | 139 | See also the ``[usermap]`` section. |
|
140 | 140 | |
|
141 | 141 | The ``[usermap]`` section is used to specify mappings of Mercurial |
|
142 | 142 | committer email to Bugzilla user email. See also ``bugzilla.usermap``. |
|
143 | 143 | Contains entries of the form ``committer = Bugzilla user``. |
|
144 | 144 | |
|
145 | 145 | XMLRPC and REST-API access mode configuration: |
|
146 | 146 | |
|
147 | 147 | bugzilla.bzurl |
|
148 | 148 | The base URL for the Bugzilla installation. |
|
149 | 149 | Default ``http://localhost/bugzilla``. |
|
150 | 150 | |
|
151 | 151 | bugzilla.user |
|
152 | 152 | The username to use to log into Bugzilla via XMLRPC. Default |
|
153 | 153 | ``bugs``. |
|
154 | 154 | |
|
155 | 155 | bugzilla.password |
|
156 | 156 | The password for Bugzilla login. |
|
157 | 157 | |
|
158 | 158 | REST-API access mode uses the options listed above as well as: |
|
159 | 159 | |
|
160 | 160 | bugzilla.apikey |
|
161 | 161 | An apikey generated on the Bugzilla instance for api access. |
|
162 | 162 | Using an apikey removes the need to store the user and password |
|
163 | 163 | options. |
|
164 | 164 | |
|
165 | 165 | XMLRPC+email access mode uses the XMLRPC access mode configuration items, |
|
166 | 166 | and also: |
|
167 | 167 | |
|
168 | 168 | bugzilla.bzemail |
|
169 | 169 | The Bugzilla email address. |
|
170 | 170 | |
|
171 | 171 | In addition, the Mercurial email settings must be configured. See the |
|
172 | 172 | documentation in hgrc(5), sections ``[email]`` and ``[smtp]``. |
|
173 | 173 | |
|
174 | 174 | MySQL access mode configuration: |
|
175 | 175 | |
|
176 | 176 | bugzilla.host |
|
177 | 177 | Hostname of the MySQL server holding the Bugzilla database. |
|
178 | 178 | Default ``localhost``. |
|
179 | 179 | |
|
180 | 180 | bugzilla.db |
|
181 | 181 | Name of the Bugzilla database in MySQL. Default ``bugs``. |
|
182 | 182 | |
|
183 | 183 | bugzilla.user |
|
184 | 184 | Username to use to access MySQL server. Default ``bugs``. |
|
185 | 185 | |
|
186 | 186 | bugzilla.password |
|
187 | 187 | Password to use to access MySQL server. |
|
188 | 188 | |
|
189 | 189 | bugzilla.timeout |
|
190 | 190 | Database connection timeout (seconds). Default 5. |
|
191 | 191 | |
|
192 | 192 | bugzilla.bzuser |
|
193 | 193 | Fallback Bugzilla user name to record comments with, if changeset |
|
194 | 194 | committer cannot be found as a Bugzilla user. |
|
195 | 195 | |
|
196 | 196 | bugzilla.bzdir |
|
197 | 197 | Bugzilla install directory. Used by default notify. Default |
|
198 | 198 | ``/var/www/html/bugzilla``. |
|
199 | 199 | |
|
200 | 200 | bugzilla.notify |
|
201 | 201 | The command to run to get Bugzilla to send bug change notification |
|
202 | 202 | emails. Substitutes from a map with 3 keys, ``bzdir``, ``id`` (bug |
|
203 | 203 | id) and ``user`` (committer bugzilla email). Default depends on |
|
204 | 204 | version; from 2.18 it is "cd %(bzdir)s && perl -T |
|
205 | 205 | contrib/sendbugmail.pl %(id)s %(user)s". |
|
206 | 206 | |
|
207 | 207 | Activating the extension:: |
|
208 | 208 | |
|
209 | 209 | [extensions] |
|
210 | 210 | bugzilla = |
|
211 | 211 | |
|
212 | 212 | [hooks] |
|
213 | 213 | # run bugzilla hook on every change pulled or pushed in here |
|
214 | 214 | incoming.bugzilla = python:hgext.bugzilla.hook |
|
215 | 215 | |
|
216 | 216 | Example configurations: |
|
217 | 217 | |
|
218 | 218 | XMLRPC example configuration. This uses the Bugzilla at |
|
219 | 219 | ``http://my-project.org/bugzilla``, logging in as user |
|
220 | 220 | ``bugmail@my-project.org`` with password ``plugh``. It is used with a |
|
221 | 221 | collection of Mercurial repositories in ``/var/local/hg/repos/``, |
|
222 | 222 | with a web interface at ``http://my-project.org/hg``. :: |
|
223 | 223 | |
|
224 | 224 | [bugzilla] |
|
225 | 225 | bzurl=http://my-project.org/bugzilla |
|
226 | 226 | user=bugmail@my-project.org |
|
227 | 227 | password=plugh |
|
228 | 228 | version=xmlrpc |
|
229 | 229 | template=Changeset {node|short} in {root|basename}. |
|
230 | 230 | {hgweb}/{webroot}/rev/{node|short}\\n |
|
231 | 231 | {desc}\\n |
|
232 | 232 | strip=5 |
|
233 | 233 | |
|
234 | 234 | [web] |
|
235 | 235 | baseurl=http://my-project.org/hg |
|
236 | 236 | |
|
237 | 237 | XMLRPC+email example configuration. This uses the Bugzilla at |
|
238 | 238 | ``http://my-project.org/bugzilla``, logging in as user |
|
239 | 239 | ``bugmail@my-project.org`` with password ``plugh``. It is used with a |
|
240 | 240 | collection of Mercurial repositories in ``/var/local/hg/repos/``, |
|
241 | 241 | with a web interface at ``http://my-project.org/hg``. Bug comments |
|
242 | 242 | are sent to the Bugzilla email address |
|
243 | 243 | ``bugzilla@my-project.org``. :: |
|
244 | 244 | |
|
245 | 245 | [bugzilla] |
|
246 | 246 | bzurl=http://my-project.org/bugzilla |
|
247 | 247 | user=bugmail@my-project.org |
|
248 | 248 | password=plugh |
|
249 | 249 | version=xmlrpc+email |
|
250 | 250 | bzemail=bugzilla@my-project.org |
|
251 | 251 | template=Changeset {node|short} in {root|basename}. |
|
252 | 252 | {hgweb}/{webroot}/rev/{node|short}\\n |
|
253 | 253 | {desc}\\n |
|
254 | 254 | strip=5 |
|
255 | 255 | |
|
256 | 256 | [web] |
|
257 | 257 | baseurl=http://my-project.org/hg |
|
258 | 258 | |
|
259 | 259 | [usermap] |
|
260 | 260 | user@emaildomain.com=user.name@bugzilladomain.com |
|
261 | 261 | |
|
262 | 262 | MySQL example configuration. This has a local Bugzilla 3.2 installation |
|
263 | 263 | in ``/opt/bugzilla-3.2``. The MySQL database is on ``localhost``, |
|
264 | 264 | the Bugzilla database name is ``bugs`` and MySQL is |
|
265 | 265 | accessed with MySQL username ``bugs`` password ``XYZZY``. It is used |
|
266 | 266 | with a collection of Mercurial repositories in ``/var/local/hg/repos/``, |
|
267 | 267 | with a web interface at ``http://my-project.org/hg``. :: |
|
268 | 268 | |
|
269 | 269 | [bugzilla] |
|
270 | 270 | host=localhost |
|
271 | 271 | password=XYZZY |
|
272 | 272 | version=3.0 |
|
273 | 273 | bzuser=unknown@domain.com |
|
274 | 274 | bzdir=/opt/bugzilla-3.2 |
|
275 | 275 | template=Changeset {node|short} in {root|basename}. |
|
276 | 276 | {hgweb}/{webroot}/rev/{node|short}\\n |
|
277 | 277 | {desc}\\n |
|
278 | 278 | strip=5 |
|
279 | 279 | |
|
280 | 280 | [web] |
|
281 | 281 | baseurl=http://my-project.org/hg |
|
282 | 282 | |
|
283 | 283 | [usermap] |
|
284 | 284 | user@emaildomain.com=user.name@bugzilladomain.com |
|
285 | 285 | |
|
286 | 286 | All the above add a comment to the Bugzilla bug record of the form:: |
|
287 | 287 | |
|
288 | 288 | Changeset 3b16791d6642 in repository-name. |
|
289 | 289 | http://my-project.org/hg/repository-name/rev/3b16791d6642 |
|
290 | 290 | |
|
291 | 291 | Changeset commit comment. Bug 1234. |
|
292 | 292 | ''' |
|
293 | 293 | |
|
294 | 294 | from __future__ import absolute_import |
|
295 | 295 | |
|
296 | 296 | import json |
|
297 | 297 | import re |
|
298 | 298 | import time |
|
299 | 299 | |
|
300 | 300 | from mercurial.i18n import _ |
|
301 | 301 | from mercurial.node import short |
|
302 | 302 | from mercurial import ( |
|
303 | 303 | error, |
|
304 | 304 | logcmdutil, |
|
305 | 305 | mail, |
|
306 | 306 | pycompat, |
|
307 | 307 | registrar, |
|
308 | 308 | url, |
|
309 | 309 | util, |
|
310 | 310 | ) |
|
311 | 311 | from mercurial.utils import ( |
|
312 | 312 | procutil, |
|
313 | 313 | stringutil, |
|
314 | 314 | ) |
|
315 | 315 | |
|
316 | 316 | xmlrpclib = util.xmlrpclib |
|
317 | 317 | |
|
318 | 318 | # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for |
|
319 | 319 | # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
|
320 | 320 | # be specifying the version(s) of Mercurial they are tested with, or |
|
321 | 321 | # leave the attribute unspecified. |
|
322 | 322 | testedwith = b'ships-with-hg-core' |
|
323 | 323 | |
|
324 | 324 | configtable = {} |
|
325 | 325 | configitem = registrar.configitem(configtable) |
|
326 | 326 | |
|
327 | 327 | configitem( |
|
328 | 328 | b'bugzilla', b'apikey', default=b'', |
|
329 | 329 | ) |
|
330 | 330 | configitem( |
|
331 | 331 | b'bugzilla', b'bzdir', default=b'/var/www/html/bugzilla', |
|
332 | 332 | ) |
|
333 | 333 | configitem( |
|
334 | 334 | b'bugzilla', b'bzemail', default=None, |
|
335 | 335 | ) |
|
336 | 336 | configitem( |
|
337 | 337 | b'bugzilla', b'bzurl', default=b'http://localhost/bugzilla/', |
|
338 | 338 | ) |
|
339 | 339 | configitem( |
|
340 | 340 | b'bugzilla', b'bzuser', default=None, |
|
341 | 341 | ) |
|
342 | 342 | configitem( |
|
343 | 343 | b'bugzilla', b'db', default=b'bugs', |
|
344 | 344 | ) |
|
345 | 345 | configitem( |
|
346 | 346 | b'bugzilla', |
|
347 | 347 | b'fixregexp', |
|
348 | 348 | default=( |
|
349 | 349 | br'fix(?:es)?\s*(?:bugs?\s*)?,?\s*' |
|
350 | 350 | br'(?:nos?\.?|num(?:ber)?s?)?\s*' |
|
351 | 351 | br'(?P<ids>(?:#?\d+\s*(?:,?\s*(?:and)?)?\s*)+)' |
|
352 | 352 | br'\.?\s*(?:h(?:ours?)?\s*(?P<hours>\d*(?:\.\d+)?))?' |
|
353 | 353 | ), |
|
354 | 354 | ) |
|
355 | 355 | configitem( |
|
356 | 356 | b'bugzilla', b'fixresolution', default=b'FIXED', |
|
357 | 357 | ) |
|
358 | 358 | configitem( |
|
359 | 359 | b'bugzilla', b'fixstatus', default=b'RESOLVED', |
|
360 | 360 | ) |
|
361 | 361 | configitem( |
|
362 | 362 | b'bugzilla', b'host', default=b'localhost', |
|
363 | 363 | ) |
|
364 | 364 | configitem( |
|
365 | 365 | b'bugzilla', b'notify', default=configitem.dynamicdefault, |
|
366 | 366 | ) |
|
367 | 367 | configitem( |
|
368 | 368 | b'bugzilla', b'password', default=None, |
|
369 | 369 | ) |
|
370 | 370 | configitem( |
|
371 | 371 | b'bugzilla', |
|
372 | 372 | b'regexp', |
|
373 | 373 | default=( |
|
374 | 374 | br'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*' |
|
375 | 375 | br'(?P<ids>(?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)' |
|
376 | 376 | br'\.?\s*(?:h(?:ours?)?\s*(?P<hours>\d*(?:\.\d+)?))?' |
|
377 | 377 | ), |
|
378 | 378 | ) |
|
379 | 379 | configitem( |
|
380 | 380 | b'bugzilla', b'strip', default=0, |
|
381 | 381 | ) |
|
382 | 382 | configitem( |
|
383 | 383 | b'bugzilla', b'style', default=None, |
|
384 | 384 | ) |
|
385 | 385 | configitem( |
|
386 | 386 | b'bugzilla', b'template', default=None, |
|
387 | 387 | ) |
|
388 | 388 | configitem( |
|
389 | 389 | b'bugzilla', b'timeout', default=5, |
|
390 | 390 | ) |
|
391 | 391 | configitem( |
|
392 | 392 | b'bugzilla', b'user', default=b'bugs', |
|
393 | 393 | ) |
|
394 | 394 | configitem( |
|
395 | 395 | b'bugzilla', b'usermap', default=None, |
|
396 | 396 | ) |
|
397 | 397 | configitem( |
|
398 | 398 | b'bugzilla', b'version', default=None, |
|
399 | 399 | ) |
|
400 | 400 | |
|
401 | 401 | |
|
402 | 402 | class bzaccess(object): |
|
403 | 403 | '''Base class for access to Bugzilla.''' |
|
404 | 404 | |
|
405 | 405 | def __init__(self, ui): |
|
406 | 406 | self.ui = ui |
|
407 | 407 | usermap = self.ui.config(b'bugzilla', b'usermap') |
|
408 | 408 | if usermap: |
|
409 | 409 | self.ui.readconfig(usermap, sections=[b'usermap']) |
|
410 | 410 | |
|
411 | 411 | def map_committer(self, user): |
|
412 | 412 | '''map name of committer to Bugzilla user name.''' |
|
413 | 413 | for committer, bzuser in self.ui.configitems(b'usermap'): |
|
414 | 414 | if committer.lower() == user.lower(): |
|
415 | 415 | return bzuser |
|
416 | 416 | return user |
|
417 | 417 | |
|
418 | 418 | # Methods to be implemented by access classes. |
|
419 | 419 | # |
|
420 | 420 | # 'bugs' is a dict keyed on bug id, where values are a dict holding |
|
421 | 421 | # updates to bug state. Recognized dict keys are: |
|
422 | 422 | # |
|
423 | 423 | # 'hours': Value, float containing work hours to be updated. |
|
424 | 424 | # 'fix': If key present, bug is to be marked fixed. Value ignored. |
|
425 | 425 | |
|
426 | 426 | def filter_real_bug_ids(self, bugs): |
|
427 | 427 | '''remove bug IDs that do not exist in Bugzilla from bugs.''' |
|
428 | 428 | |
|
429 | 429 | def filter_cset_known_bug_ids(self, node, bugs): |
|
430 | 430 | '''remove bug IDs where node occurs in comment text from bugs.''' |
|
431 | 431 | |
|
432 | 432 | def updatebug(self, bugid, newstate, text, committer): |
|
433 | 433 | '''update the specified bug. Add comment text and set new states. |
|
434 | 434 | |
|
435 | 435 | If possible add the comment as being from the committer of |
|
436 | 436 | the changeset. Otherwise use the default Bugzilla user. |
|
437 | 437 | ''' |
|
438 | 438 | |
|
439 | 439 | def notify(self, bugs, committer): |
|
440 | 440 | '''Force sending of Bugzilla notification emails. |
|
441 | 441 | |
|
442 | 442 | Only required if the access method does not trigger notification |
|
443 | 443 | emails automatically. |
|
444 | 444 | ''' |
|
445 | 445 | |
|
446 | 446 | |
|
447 | 447 | # Bugzilla via direct access to MySQL database. |
|
448 | 448 | class bzmysql(bzaccess): |
|
449 | 449 | '''Support for direct MySQL access to Bugzilla. |
|
450 | 450 | |
|
451 | 451 | The earliest Bugzilla version this is tested with is version 2.16. |
|
452 | 452 | |
|
453 | 453 | If your Bugzilla is version 3.4 or above, you are strongly |
|
454 | 454 | recommended to use the XMLRPC access method instead. |
|
455 | 455 | ''' |
|
456 | 456 | |
|
457 | 457 | @staticmethod |
|
458 | 458 | def sql_buglist(ids): |
|
459 | 459 | '''return SQL-friendly list of bug ids''' |
|
460 | 460 | return b'(' + b','.join(map(str, ids)) + b')' |
|
461 | 461 | |
|
462 | 462 | _MySQLdb = None |
|
463 | 463 | |
|
464 | 464 | def __init__(self, ui): |
|
465 | 465 | try: |
|
466 | 466 | import MySQLdb as mysql |
|
467 | 467 | |
|
468 | 468 | bzmysql._MySQLdb = mysql |
|
469 | 469 | except ImportError as err: |
|
470 | 470 | raise error.Abort( |
|
471 | 471 | _(b'python mysql support not available: %s') % err |
|
472 | 472 | ) |
|
473 | 473 | |
|
474 | 474 | bzaccess.__init__(self, ui) |
|
475 | 475 | |
|
476 | 476 | host = self.ui.config(b'bugzilla', b'host') |
|
477 | 477 | user = self.ui.config(b'bugzilla', b'user') |
|
478 | 478 | passwd = self.ui.config(b'bugzilla', b'password') |
|
479 | 479 | db = self.ui.config(b'bugzilla', b'db') |
|
480 | 480 | timeout = int(self.ui.config(b'bugzilla', b'timeout')) |
|
481 | 481 | self.ui.note( |
|
482 | 482 | _(b'connecting to %s:%s as %s, password %s\n') |
|
483 | 483 | % (host, db, user, b'*' * len(passwd)) |
|
484 | 484 | ) |
|
485 | 485 | self.conn = bzmysql._MySQLdb.connect( |
|
486 | 486 | host=host, user=user, passwd=passwd, db=db, connect_timeout=timeout |
|
487 | 487 | ) |
|
488 | 488 | self.cursor = self.conn.cursor() |
|
489 | 489 | self.longdesc_id = self.get_longdesc_id() |
|
490 | 490 | self.user_ids = {} |
|
491 | 491 | self.default_notify = b"cd %(bzdir)s && ./processmail %(id)s %(user)s" |
|
492 | 492 | |
|
493 | 493 | def run(self, *args, **kwargs): |
|
494 | 494 | '''run a query.''' |
|
495 | 495 | self.ui.note(_(b'query: %s %s\n') % (args, kwargs)) |
|
496 | 496 | try: |
|
497 | 497 | self.cursor.execute(*args, **kwargs) |
|
498 | 498 | except bzmysql._MySQLdb.MySQLError: |
|
499 | 499 | self.ui.note(_(b'failed query: %s %s\n') % (args, kwargs)) |
|
500 | 500 | raise |
|
501 | 501 | |
|
502 | 502 | def get_longdesc_id(self): |
|
503 | 503 | '''get identity of longdesc field''' |
|
504 | 504 | self.run(b'select fieldid from fielddefs where name = "longdesc"') |
|
505 | 505 | ids = self.cursor.fetchall() |
|
506 | 506 | if len(ids) != 1: |
|
507 | 507 | raise error.Abort(_(b'unknown database schema')) |
|
508 | 508 | return ids[0][0] |
|
509 | 509 | |
|
510 | 510 | def filter_real_bug_ids(self, bugs): |
|
511 | 511 | '''filter not-existing bugs from set.''' |
|
512 | 512 | self.run( |
|
513 | 513 | b'select bug_id from bugs where bug_id in %s' |
|
514 | 514 | % bzmysql.sql_buglist(bugs.keys()) |
|
515 | 515 | ) |
|
516 | 516 | existing = [id for (id,) in self.cursor.fetchall()] |
|
517 | 517 | for id in bugs.keys(): |
|
518 | 518 | if id not in existing: |
|
519 | 519 | self.ui.status(_(b'bug %d does not exist\n') % id) |
|
520 | 520 | del bugs[id] |
|
521 | 521 | |
|
522 | 522 | def filter_cset_known_bug_ids(self, node, bugs): |
|
523 | 523 | '''filter bug ids that already refer to this changeset from set.''' |
|
524 | 524 | self.run( |
|
525 | 525 | '''select bug_id from longdescs where |
|
526 | 526 | bug_id in %s and thetext like "%%%s%%"''' |
|
527 | 527 | % (bzmysql.sql_buglist(bugs.keys()), short(node)) |
|
528 | 528 | ) |
|
529 | 529 | for (id,) in self.cursor.fetchall(): |
|
530 | 530 | self.ui.status( |
|
531 | 531 | _(b'bug %d already knows about changeset %s\n') |
|
532 | 532 | % (id, short(node)) |
|
533 | 533 | ) |
|
534 | 534 | del bugs[id] |
|
535 | 535 | |
|
536 | 536 | def notify(self, bugs, committer): |
|
537 | 537 | '''tell bugzilla to send mail.''' |
|
538 | 538 | self.ui.status(_(b'telling bugzilla to send mail:\n')) |
|
539 | 539 | (user, userid) = self.get_bugzilla_user(committer) |
|
540 | 540 | for id in bugs.keys(): |
|
541 | 541 | self.ui.status(_(b' bug %s\n') % id) |
|
542 | 542 | cmdfmt = self.ui.config(b'bugzilla', b'notify', self.default_notify) |
|
543 | 543 | bzdir = self.ui.config(b'bugzilla', b'bzdir') |
|
544 | 544 | try: |
|
545 | 545 | # Backwards-compatible with old notify string, which |
|
546 | 546 | # took one string. This will throw with a new format |
|
547 | 547 | # string. |
|
548 | 548 | cmd = cmdfmt % id |
|
549 | 549 | except TypeError: |
|
550 | 550 | cmd = cmdfmt % {b'bzdir': bzdir, b'id': id, b'user': user} |
|
551 | 551 | self.ui.note(_(b'running notify command %s\n') % cmd) |
|
552 | 552 | fp = procutil.popen(b'(%s) 2>&1' % cmd, b'rb') |
|
553 | 553 | out = util.fromnativeeol(fp.read()) |
|
554 | 554 | ret = fp.close() |
|
555 | 555 | if ret: |
|
556 | 556 | self.ui.warn(out) |
|
557 | 557 | raise error.Abort( |
|
558 | 558 | _(b'bugzilla notify command %s') % procutil.explainexit(ret) |
|
559 | 559 | ) |
|
560 | 560 | self.ui.status(_(b'done\n')) |
|
561 | 561 | |
|
562 | 562 | def get_user_id(self, user): |
|
563 | 563 | '''look up numeric bugzilla user id.''' |
|
564 | 564 | try: |
|
565 | 565 | return self.user_ids[user] |
|
566 | 566 | except KeyError: |
|
567 | 567 | try: |
|
568 | 568 | userid = int(user) |
|
569 | 569 | except ValueError: |
|
570 | 570 | self.ui.note(_(b'looking up user %s\n') % user) |
|
571 | 571 | self.run( |
|
572 | 572 | '''select userid from profiles |
|
573 | 573 | where login_name like %s''', |
|
574 | 574 | user, |
|
575 | 575 | ) |
|
576 | 576 | all = self.cursor.fetchall() |
|
577 | 577 | if len(all) != 1: |
|
578 | 578 | raise KeyError(user) |
|
579 | 579 | userid = int(all[0][0]) |
|
580 | 580 | self.user_ids[user] = userid |
|
581 | 581 | return userid |
|
582 | 582 | |
|
583 | 583 | def get_bugzilla_user(self, committer): |
|
584 | 584 | '''See if committer is a registered bugzilla user. Return |
|
585 | 585 | bugzilla username and userid if so. If not, return default |
|
586 | 586 | bugzilla username and userid.''' |
|
587 | 587 | user = self.map_committer(committer) |
|
588 | 588 | try: |
|
589 | 589 | userid = self.get_user_id(user) |
|
590 | 590 | except KeyError: |
|
591 | 591 | try: |
|
592 | 592 | defaultuser = self.ui.config(b'bugzilla', b'bzuser') |
|
593 | 593 | if not defaultuser: |
|
594 | 594 | raise error.Abort( |
|
595 | 595 | _(b'cannot find bugzilla user id for %s') % user |
|
596 | 596 | ) |
|
597 | 597 | userid = self.get_user_id(defaultuser) |
|
598 | 598 | user = defaultuser |
|
599 | 599 | except KeyError: |
|
600 | 600 | raise error.Abort( |
|
601 | 601 | _(b'cannot find bugzilla user id for %s or %s') |
|
602 | 602 | % (user, defaultuser) |
|
603 | 603 | ) |
|
604 | 604 | return (user, userid) |
|
605 | 605 | |
|
606 | 606 | def updatebug(self, bugid, newstate, text, committer): |
|
607 | 607 | '''update bug state with comment text. |
|
608 | 608 | |
|
609 | 609 | Try adding comment as committer of changeset, otherwise as |
|
610 | 610 | default bugzilla user.''' |
|
611 | 611 | if len(newstate) > 0: |
|
612 | 612 | self.ui.warn(_(b"Bugzilla/MySQL cannot update bug state\n")) |
|
613 | 613 | |
|
614 | 614 | (user, userid) = self.get_bugzilla_user(committer) |
|
615 | 615 | now = time.strftime(r'%Y-%m-%d %H:%M:%S') |
|
616 | 616 | self.run( |
|
617 | 617 | '''insert into longdescs |
|
618 | 618 | (bug_id, who, bug_when, thetext) |
|
619 | 619 | values (%s, %s, %s, %s)''', |
|
620 | 620 | (bugid, userid, now, text), |
|
621 | 621 | ) |
|
622 | 622 | self.run( |
|
623 | 623 | '''insert into bugs_activity (bug_id, who, bug_when, fieldid) |
|
624 | 624 | values (%s, %s, %s, %s)''', |
|
625 | 625 | (bugid, userid, now, self.longdesc_id), |
|
626 | 626 | ) |
|
627 | 627 | self.conn.commit() |
|
628 | 628 | |
|
629 | 629 | |
|
630 | 630 | class bzmysql_2_18(bzmysql): |
|
631 | 631 | '''support for bugzilla 2.18 series.''' |
|
632 | 632 | |
|
633 | 633 | def __init__(self, ui): |
|
634 | 634 | bzmysql.__init__(self, ui) |
|
635 | 635 | self.default_notify = ( |
|
636 | 636 | b"cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s" |
|
637 | 637 | ) |
|
638 | 638 | |
|
639 | 639 | |
|
640 | 640 | class bzmysql_3_0(bzmysql_2_18): |
|
641 | 641 | '''support for bugzilla 3.0 series.''' |
|
642 | 642 | |
|
643 | 643 | def __init__(self, ui): |
|
644 | 644 | bzmysql_2_18.__init__(self, ui) |
|
645 | 645 | |
|
646 | 646 | def get_longdesc_id(self): |
|
647 | 647 | '''get identity of longdesc field''' |
|
648 | 648 | self.run(b'select id from fielddefs where name = "longdesc"') |
|
649 | 649 | ids = self.cursor.fetchall() |
|
650 | 650 | if len(ids) != 1: |
|
651 | 651 | raise error.Abort(_(b'unknown database schema')) |
|
652 | 652 | return ids[0][0] |
|
653 | 653 | |
|
654 | 654 | |
|
655 | 655 | # Bugzilla via XMLRPC interface. |
|
656 | 656 | |
|
657 | 657 | |
|
658 | 658 | class cookietransportrequest(object): |
|
659 | 659 | """A Transport request method that retains cookies over its lifetime. |
|
660 | 660 | |
|
661 | 661 | The regular xmlrpclib transports ignore cookies. Which causes |
|
662 | 662 | a bit of a problem when you need a cookie-based login, as with |
|
663 | 663 | the Bugzilla XMLRPC interface prior to 4.4.3. |
|
664 | 664 | |
|
665 | 665 | So this is a helper for defining a Transport which looks for |
|
666 | 666 | cookies being set in responses and saves them to add to all future |
|
667 | 667 | requests. |
|
668 | 668 | """ |
|
669 | 669 | |
|
670 | 670 | # Inspiration drawn from |
|
671 | 671 | # http://blog.godson.in/2010/09/how-to-make-python-xmlrpclib-client.html |
|
672 | 672 | # http://www.itkovian.net/base/transport-class-for-pythons-xml-rpc-lib/ |
|
673 | 673 | |
|
674 | 674 | cookies = [] |
|
675 | 675 | |
|
676 | 676 | def send_cookies(self, connection): |
|
677 | 677 | if self.cookies: |
|
678 | 678 | for cookie in self.cookies: |
|
679 | 679 | connection.putheader(b"Cookie", cookie) |
|
680 | 680 | |
|
681 | 681 | def request(self, host, handler, request_body, verbose=0): |
|
682 | 682 | self.verbose = verbose |
|
683 | 683 | self.accept_gzip_encoding = False |
|
684 | 684 | |
|
685 | 685 | # issue XML-RPC request |
|
686 | 686 | h = self.make_connection(host) |
|
687 | 687 | if verbose: |
|
688 | 688 | h.set_debuglevel(1) |
|
689 | 689 | |
|
690 | 690 | self.send_request(h, handler, request_body) |
|
691 | 691 | self.send_host(h, host) |
|
692 | 692 | self.send_cookies(h) |
|
693 | 693 | self.send_user_agent(h) |
|
694 | 694 | self.send_content(h, request_body) |
|
695 | 695 | |
|
696 | 696 | # Deal with differences between Python 2.6 and 2.7. |
|
697 | 697 | # In the former h is a HTTP(S). In the latter it's a |
|
698 | 698 | # HTTP(S)Connection. Luckily, the 2.6 implementation of |
|
699 | 699 | # HTTP(S) has an underlying HTTP(S)Connection, so extract |
|
700 | 700 | # that and use it. |
|
701 | 701 | try: |
|
702 | 702 | response = h.getresponse() |
|
703 | 703 | except AttributeError: |
|
704 | 704 | response = h._conn.getresponse() |
|
705 | 705 | |
|
706 | 706 | # Add any cookie definitions to our list. |
|
707 | 707 | for header in response.msg.getallmatchingheaders(b"Set-Cookie"): |
|
708 | 708 | val = header.split(b": ", 1)[1] |
|
709 | 709 | cookie = val.split(b";", 1)[0] |
|
710 | 710 | self.cookies.append(cookie) |
|
711 | 711 | |
|
712 | 712 | if response.status != 200: |
|
713 | 713 | raise xmlrpclib.ProtocolError( |
|
714 | 714 | host + handler, |
|
715 | 715 | response.status, |
|
716 | 716 | response.reason, |
|
717 | 717 | response.msg.headers, |
|
718 | 718 | ) |
|
719 | 719 | |
|
720 | 720 | payload = response.read() |
|
721 | 721 | parser, unmarshaller = self.getparser() |
|
722 | 722 | parser.feed(payload) |
|
723 | 723 | parser.close() |
|
724 | 724 | |
|
725 | 725 | return unmarshaller.close() |
|
726 | 726 | |
|
727 | 727 | |
|
728 | 728 | # The explicit calls to the underlying xmlrpclib __init__() methods are |
|
729 | 729 | # necessary. The xmlrpclib.Transport classes are old-style classes, and |
|
730 | 730 | # it turns out their __init__() doesn't get called when doing multiple |
|
731 | 731 | # inheritance with a new-style class. |
|
732 | 732 | class cookietransport(cookietransportrequest, xmlrpclib.Transport): |
|
733 | 733 | def __init__(self, use_datetime=0): |
|
734 | 734 | if util.safehasattr(xmlrpclib.Transport, "__init__"): |
|
735 | 735 | xmlrpclib.Transport.__init__(self, use_datetime) |
|
736 | 736 | |
|
737 | 737 | |
|
738 | 738 | class cookiesafetransport(cookietransportrequest, xmlrpclib.SafeTransport): |
|
739 | 739 | def __init__(self, use_datetime=0): |
|
740 | 740 | if util.safehasattr(xmlrpclib.Transport, "__init__"): |
|
741 | 741 | xmlrpclib.SafeTransport.__init__(self, use_datetime) |
|
742 | 742 | |
|
743 | 743 | |
|
744 | 744 | class bzxmlrpc(bzaccess): |
|
745 | 745 | """Support for access to Bugzilla via the Bugzilla XMLRPC API. |
|
746 | 746 | |
|
747 | 747 | Requires a minimum Bugzilla version 3.4. |
|
748 | 748 | """ |
|
749 | 749 | |
|
750 | 750 | def __init__(self, ui): |
|
751 | 751 | bzaccess.__init__(self, ui) |
|
752 | 752 | |
|
753 | 753 | bzweb = self.ui.config(b'bugzilla', b'bzurl') |
|
754 | 754 | bzweb = bzweb.rstrip(b"/") + b"/xmlrpc.cgi" |
|
755 | 755 | |
|
756 | 756 | user = self.ui.config(b'bugzilla', b'user') |
|
757 | 757 | passwd = self.ui.config(b'bugzilla', b'password') |
|
758 | 758 | |
|
759 | 759 | self.fixstatus = self.ui.config(b'bugzilla', b'fixstatus') |
|
760 | 760 | self.fixresolution = self.ui.config(b'bugzilla', b'fixresolution') |
|
761 | 761 | |
|
762 | 762 | self.bzproxy = xmlrpclib.ServerProxy(bzweb, self.transport(bzweb)) |
|
763 | 763 | ver = self.bzproxy.Bugzilla.version()[b'version'].split(b'.') |
|
764 | 764 | self.bzvermajor = int(ver[0]) |
|
765 | 765 | self.bzverminor = int(ver[1]) |
|
766 | 766 | login = self.bzproxy.User.login( |
|
767 | 767 | {b'login': user, b'password': passwd, b'restrict_login': True} |
|
768 | 768 | ) |
|
769 | 769 | self.bztoken = login.get(b'token', b'') |
|
770 | 770 | |
|
771 | 771 | def transport(self, uri): |
|
772 | 772 | if util.urlreq.urlparse(uri, b"http")[0] == b"https": |
|
773 | 773 | return cookiesafetransport() |
|
774 | 774 | else: |
|
775 | 775 | return cookietransport() |
|
776 | 776 | |
|
777 | 777 | def get_bug_comments(self, id): |
|
778 | 778 | """Return a string with all comment text for a bug.""" |
|
779 | 779 | c = self.bzproxy.Bug.comments( |
|
780 | 780 | {b'ids': [id], b'include_fields': [b'text'], b'token': self.bztoken} |
|
781 | 781 | ) |
|
782 | 782 | return b''.join( |
|
783 | 783 | [t[b'text'] for t in c[b'bugs'][b'%d' % id][b'comments']] |
|
784 | 784 | ) |
|
785 | 785 | |
|
786 | 786 | def filter_real_bug_ids(self, bugs): |
|
787 | 787 | probe = self.bzproxy.Bug.get( |
|
788 | 788 | { |
|
789 | 789 | b'ids': sorted(bugs.keys()), |
|
790 | 790 | b'include_fields': [], |
|
791 | 791 | b'permissive': True, |
|
792 | 792 | b'token': self.bztoken, |
|
793 | 793 | } |
|
794 | 794 | ) |
|
795 | 795 | for badbug in probe[b'faults']: |
|
796 | 796 | id = badbug[b'id'] |
|
797 | 797 | self.ui.status(_(b'bug %d does not exist\n') % id) |
|
798 | 798 | del bugs[id] |
|
799 | 799 | |
|
800 | 800 | def filter_cset_known_bug_ids(self, node, bugs): |
|
801 | 801 | for id in sorted(bugs.keys()): |
|
802 | 802 | if self.get_bug_comments(id).find(short(node)) != -1: |
|
803 | 803 | self.ui.status( |
|
804 | 804 | _(b'bug %d already knows about changeset %s\n') |
|
805 | 805 | % (id, short(node)) |
|
806 | 806 | ) |
|
807 | 807 | del bugs[id] |
|
808 | 808 | |
|
809 | 809 | def updatebug(self, bugid, newstate, text, committer): |
|
810 | 810 | args = {} |
|
811 | 811 | if b'hours' in newstate: |
|
812 | 812 | args[b'work_time'] = newstate[b'hours'] |
|
813 | 813 | |
|
814 | 814 | if self.bzvermajor >= 4: |
|
815 | 815 | args[b'ids'] = [bugid] |
|
816 | 816 | args[b'comment'] = {b'body': text} |
|
817 | 817 | if b'fix' in newstate: |
|
818 | 818 | args[b'status'] = self.fixstatus |
|
819 | 819 | args[b'resolution'] = self.fixresolution |
|
820 | 820 | args[b'token'] = self.bztoken |
|
821 | 821 | self.bzproxy.Bug.update(args) |
|
822 | 822 | else: |
|
823 | 823 | if b'fix' in newstate: |
|
824 | 824 | self.ui.warn( |
|
825 | 825 | _( |
|
826 | 826 | b"Bugzilla/XMLRPC needs Bugzilla 4.0 or later " |
|
827 | 827 | b"to mark bugs fixed\n" |
|
828 | 828 | ) |
|
829 | 829 | ) |
|
830 | 830 | args[b'id'] = bugid |
|
831 | 831 | args[b'comment'] = text |
|
832 | 832 | self.bzproxy.Bug.add_comment(args) |
|
833 | 833 | |
|
834 | 834 | |
|
835 | 835 | class bzxmlrpcemail(bzxmlrpc): |
|
836 | 836 | """Read data from Bugzilla via XMLRPC, send updates via email. |
|
837 | 837 | |
|
838 | 838 | Advantages of sending updates via email: |
|
839 | 839 | 1. Comments can be added as any user, not just logged in user. |
|
840 | 840 | 2. Bug statuses or other fields not accessible via XMLRPC can |
|
841 | 841 | potentially be updated. |
|
842 | 842 | |
|
843 | 843 | There is no XMLRPC function to change bug status before Bugzilla |
|
844 | 844 | 4.0, so bugs cannot be marked fixed via XMLRPC before Bugzilla 4.0. |
|
845 | 845 | But bugs can be marked fixed via email from 3.4 onwards. |
|
846 | 846 | """ |
|
847 | 847 | |
|
848 | 848 | # The email interface changes subtly between 3.4 and 3.6. In 3.4, |
|
849 | 849 | # in-email fields are specified as '@<fieldname> = <value>'. In |
|
850 | 850 | # 3.6 this becomes '@<fieldname> <value>'. And fieldname @bug_id |
|
851 | 851 | # in 3.4 becomes @id in 3.6. 3.6 and 4.0 both maintain backwards |
|
852 | 852 | # compatibility, but rather than rely on this use the new format for |
|
853 | 853 | # 4.0 onwards. |
|
854 | 854 | |
|
855 | 855 | def __init__(self, ui): |
|
856 | 856 | bzxmlrpc.__init__(self, ui) |
|
857 | 857 | |
|
858 | 858 | self.bzemail = self.ui.config(b'bugzilla', b'bzemail') |
|
859 | 859 | if not self.bzemail: |
|
860 | 860 | raise error.Abort(_(b"configuration 'bzemail' missing")) |
|
861 | 861 | mail.validateconfig(self.ui) |
|
862 | 862 | |
|
863 | 863 | def makecommandline(self, fieldname, value): |
|
864 | 864 | if self.bzvermajor >= 4: |
|
865 | 865 | return b"@%s %s" % (fieldname, pycompat.bytestr(value)) |
|
866 | 866 | else: |
|
867 | 867 | if fieldname == b"id": |
|
868 | 868 | fieldname = b"bug_id" |
|
869 | 869 | return b"@%s = %s" % (fieldname, pycompat.bytestr(value)) |
|
870 | 870 | |
|
871 | 871 | def send_bug_modify_email(self, bugid, commands, comment, committer): |
|
872 | 872 | '''send modification message to Bugzilla bug via email. |
|
873 | 873 | |
|
874 | 874 | The message format is documented in the Bugzilla email_in.pl |
|
875 | 875 | specification. commands is a list of command lines, comment is the |
|
876 | 876 | comment text. |
|
877 | 877 | |
|
878 | 878 | To stop users from crafting commit comments with |
|
879 | 879 | Bugzilla commands, specify the bug ID via the message body, rather |
|
880 | 880 | than the subject line, and leave a blank line after it. |
|
881 | 881 | ''' |
|
882 | 882 | user = self.map_committer(committer) |
|
883 | 883 | matches = self.bzproxy.User.get( |
|
884 | 884 | {b'match': [user], b'token': self.bztoken} |
|
885 | 885 | ) |
|
886 | 886 | if not matches[b'users']: |
|
887 | 887 | user = self.ui.config(b'bugzilla', b'user') |
|
888 | 888 | matches = self.bzproxy.User.get( |
|
889 | 889 | {b'match': [user], b'token': self.bztoken} |
|
890 | 890 | ) |
|
891 | 891 | if not matches[b'users']: |
|
892 | 892 | raise error.Abort( |
|
893 | 893 | _(b"default bugzilla user %s email not found") % user |
|
894 | 894 | ) |
|
895 | 895 | user = matches[b'users'][0][b'email'] |
|
896 | 896 | commands.append(self.makecommandline(b"id", bugid)) |
|
897 | 897 | |
|
898 | 898 | text = b"\n".join(commands) + b"\n\n" + comment |
|
899 | 899 | |
|
900 | 900 | _charsets = mail._charsets(self.ui) |
|
901 | 901 | user = mail.addressencode(self.ui, user, _charsets) |
|
902 | 902 | bzemail = mail.addressencode(self.ui, self.bzemail, _charsets) |
|
903 | 903 | msg = mail.mimeencode(self.ui, text, _charsets) |
|
904 | 904 | msg[b'From'] = user |
|
905 | 905 | msg[b'To'] = bzemail |
|
906 | 906 | msg[b'Subject'] = mail.headencode( |
|
907 | 907 | self.ui, b"Bug modification", _charsets |
|
908 | 908 | ) |
|
909 | 909 | sendmail = mail.connect(self.ui) |
|
910 | 910 | sendmail(user, bzemail, msg.as_string()) |
|
911 | 911 | |
|
912 | 912 | def updatebug(self, bugid, newstate, text, committer): |
|
913 | 913 | cmds = [] |
|
914 | 914 | if b'hours' in newstate: |
|
915 | 915 | cmds.append(self.makecommandline(b"work_time", newstate[b'hours'])) |
|
916 | 916 | if b'fix' in newstate: |
|
917 | 917 | cmds.append(self.makecommandline(b"bug_status", self.fixstatus)) |
|
918 | 918 | cmds.append(self.makecommandline(b"resolution", self.fixresolution)) |
|
919 | 919 | self.send_bug_modify_email(bugid, cmds, text, committer) |
|
920 | 920 | |
|
921 | 921 | |
|
922 | 922 | class NotFound(LookupError): |
|
923 | 923 | pass |
|
924 | 924 | |
|
925 | 925 | |
|
926 | 926 | class bzrestapi(bzaccess): |
|
927 | 927 | """Read and write bugzilla data using the REST API available since |
|
928 | 928 | Bugzilla 5.0. |
|
929 | 929 | """ |
|
930 | 930 | |
|
931 | 931 | def __init__(self, ui): |
|
932 | 932 | bzaccess.__init__(self, ui) |
|
933 | 933 | bz = self.ui.config(b'bugzilla', b'bzurl') |
|
934 | 934 | self.bzroot = b'/'.join([bz, b'rest']) |
|
935 | 935 | self.apikey = self.ui.config(b'bugzilla', b'apikey') |
|
936 | 936 | self.user = self.ui.config(b'bugzilla', b'user') |
|
937 | 937 | self.passwd = self.ui.config(b'bugzilla', b'password') |
|
938 | 938 | self.fixstatus = self.ui.config(b'bugzilla', b'fixstatus') |
|
939 | 939 | self.fixresolution = self.ui.config(b'bugzilla', b'fixresolution') |
|
940 | 940 | |
|
941 | 941 | def apiurl(self, targets, include_fields=None): |
|
942 | 942 | url = b'/'.join([self.bzroot] + [pycompat.bytestr(t) for t in targets]) |
|
943 | 943 | qv = {} |
|
944 | 944 | if self.apikey: |
|
945 | 945 | qv[b'api_key'] = self.apikey |
|
946 | 946 | elif self.user and self.passwd: |
|
947 | 947 | qv[b'login'] = self.user |
|
948 | 948 | qv[b'password'] = self.passwd |
|
949 | 949 | if include_fields: |
|
950 | 950 | qv[b'include_fields'] = include_fields |
|
951 | 951 | if qv: |
|
952 | 952 | url = b'%s?%s' % (url, util.urlreq.urlencode(qv)) |
|
953 | 953 | return url |
|
954 | 954 | |
|
955 | 955 | def _fetch(self, burl): |
|
956 | 956 | try: |
|
957 | 957 | resp = url.open(self.ui, burl) |
|
958 |
return |
|
|
958 | return pycompat.json_loads(resp.read()) | |
|
959 | 959 | except util.urlerr.httperror as inst: |
|
960 | 960 | if inst.code == 401: |
|
961 | 961 | raise error.Abort(_(b'authorization failed')) |
|
962 | 962 | if inst.code == 404: |
|
963 | 963 | raise NotFound() |
|
964 | 964 | else: |
|
965 | 965 | raise |
|
966 | 966 | |
|
967 | 967 | def _submit(self, burl, data, method=b'POST'): |
|
968 | 968 | data = json.dumps(data) |
|
969 | 969 | if method == b'PUT': |
|
970 | 970 | |
|
971 | 971 | class putrequest(util.urlreq.request): |
|
972 | 972 | def get_method(self): |
|
973 | 973 | return b'PUT' |
|
974 | 974 | |
|
975 | 975 | request_type = putrequest |
|
976 | 976 | else: |
|
977 | 977 | request_type = util.urlreq.request |
|
978 | 978 | req = request_type(burl, data, {b'Content-Type': b'application/json'}) |
|
979 | 979 | try: |
|
980 | 980 | resp = url.opener(self.ui).open(req) |
|
981 |
return |
|
|
981 | return pycompat.json_loads(resp.read()) | |
|
982 | 982 | except util.urlerr.httperror as inst: |
|
983 | 983 | if inst.code == 401: |
|
984 | 984 | raise error.Abort(_(b'authorization failed')) |
|
985 | 985 | if inst.code == 404: |
|
986 | 986 | raise NotFound() |
|
987 | 987 | else: |
|
988 | 988 | raise |
|
989 | 989 | |
|
990 | 990 | def filter_real_bug_ids(self, bugs): |
|
991 | 991 | '''remove bug IDs that do not exist in Bugzilla from bugs.''' |
|
992 | 992 | badbugs = set() |
|
993 | 993 | for bugid in bugs: |
|
994 | 994 | burl = self.apiurl((b'bug', bugid), include_fields=b'status') |
|
995 | 995 | try: |
|
996 | 996 | self._fetch(burl) |
|
997 | 997 | except NotFound: |
|
998 | 998 | badbugs.add(bugid) |
|
999 | 999 | for bugid in badbugs: |
|
1000 | 1000 | del bugs[bugid] |
|
1001 | 1001 | |
|
1002 | 1002 | def filter_cset_known_bug_ids(self, node, bugs): |
|
1003 | 1003 | '''remove bug IDs where node occurs in comment text from bugs.''' |
|
1004 | 1004 | sn = short(node) |
|
1005 | 1005 | for bugid in bugs.keys(): |
|
1006 | 1006 | burl = self.apiurl( |
|
1007 | 1007 | (b'bug', bugid, b'comment'), include_fields=b'text' |
|
1008 | 1008 | ) |
|
1009 | 1009 | result = self._fetch(burl) |
|
1010 | 1010 | comments = result[b'bugs'][pycompat.bytestr(bugid)][b'comments'] |
|
1011 | 1011 | if any(sn in c[b'text'] for c in comments): |
|
1012 | 1012 | self.ui.status( |
|
1013 | 1013 | _(b'bug %d already knows about changeset %s\n') |
|
1014 | 1014 | % (bugid, sn) |
|
1015 | 1015 | ) |
|
1016 | 1016 | del bugs[bugid] |
|
1017 | 1017 | |
|
1018 | 1018 | def updatebug(self, bugid, newstate, text, committer): |
|
1019 | 1019 | '''update the specified bug. Add comment text and set new states. |
|
1020 | 1020 | |
|
1021 | 1021 | If possible add the comment as being from the committer of |
|
1022 | 1022 | the changeset. Otherwise use the default Bugzilla user. |
|
1023 | 1023 | ''' |
|
1024 | 1024 | bugmod = {} |
|
1025 | 1025 | if b'hours' in newstate: |
|
1026 | 1026 | bugmod[b'work_time'] = newstate[b'hours'] |
|
1027 | 1027 | if b'fix' in newstate: |
|
1028 | 1028 | bugmod[b'status'] = self.fixstatus |
|
1029 | 1029 | bugmod[b'resolution'] = self.fixresolution |
|
1030 | 1030 | if bugmod: |
|
1031 | 1031 | # if we have to change the bugs state do it here |
|
1032 | 1032 | bugmod[b'comment'] = { |
|
1033 | 1033 | b'comment': text, |
|
1034 | 1034 | b'is_private': False, |
|
1035 | 1035 | b'is_markdown': False, |
|
1036 | 1036 | } |
|
1037 | 1037 | burl = self.apiurl((b'bug', bugid)) |
|
1038 | 1038 | self._submit(burl, bugmod, method=b'PUT') |
|
1039 | 1039 | self.ui.debug(b'updated bug %s\n' % bugid) |
|
1040 | 1040 | else: |
|
1041 | 1041 | burl = self.apiurl((b'bug', bugid, b'comment')) |
|
1042 | 1042 | self._submit( |
|
1043 | 1043 | burl, |
|
1044 | 1044 | { |
|
1045 | 1045 | b'comment': text, |
|
1046 | 1046 | b'is_private': False, |
|
1047 | 1047 | b'is_markdown': False, |
|
1048 | 1048 | }, |
|
1049 | 1049 | ) |
|
1050 | 1050 | self.ui.debug(b'added comment to bug %s\n' % bugid) |
|
1051 | 1051 | |
|
1052 | 1052 | def notify(self, bugs, committer): |
|
1053 | 1053 | '''Force sending of Bugzilla notification emails. |
|
1054 | 1054 | |
|
1055 | 1055 | Only required if the access method does not trigger notification |
|
1056 | 1056 | emails automatically. |
|
1057 | 1057 | ''' |
|
1058 | 1058 | pass |
|
1059 | 1059 | |
|
1060 | 1060 | |
|
1061 | 1061 | class bugzilla(object): |
|
1062 | 1062 | # supported versions of bugzilla. different versions have |
|
1063 | 1063 | # different schemas. |
|
1064 | 1064 | _versions = { |
|
1065 | 1065 | b'2.16': bzmysql, |
|
1066 | 1066 | b'2.18': bzmysql_2_18, |
|
1067 | 1067 | b'3.0': bzmysql_3_0, |
|
1068 | 1068 | b'xmlrpc': bzxmlrpc, |
|
1069 | 1069 | b'xmlrpc+email': bzxmlrpcemail, |
|
1070 | 1070 | b'restapi': bzrestapi, |
|
1071 | 1071 | } |
|
1072 | 1072 | |
|
1073 | 1073 | def __init__(self, ui, repo): |
|
1074 | 1074 | self.ui = ui |
|
1075 | 1075 | self.repo = repo |
|
1076 | 1076 | |
|
1077 | 1077 | bzversion = self.ui.config(b'bugzilla', b'version') |
|
1078 | 1078 | try: |
|
1079 | 1079 | bzclass = bugzilla._versions[bzversion] |
|
1080 | 1080 | except KeyError: |
|
1081 | 1081 | raise error.Abort( |
|
1082 | 1082 | _(b'bugzilla version %s not supported') % bzversion |
|
1083 | 1083 | ) |
|
1084 | 1084 | self.bzdriver = bzclass(self.ui) |
|
1085 | 1085 | |
|
1086 | 1086 | self.bug_re = re.compile( |
|
1087 | 1087 | self.ui.config(b'bugzilla', b'regexp'), re.IGNORECASE |
|
1088 | 1088 | ) |
|
1089 | 1089 | self.fix_re = re.compile( |
|
1090 | 1090 | self.ui.config(b'bugzilla', b'fixregexp'), re.IGNORECASE |
|
1091 | 1091 | ) |
|
1092 | 1092 | self.split_re = re.compile(br'\D+') |
|
1093 | 1093 | |
|
1094 | 1094 | def find_bugs(self, ctx): |
|
1095 | 1095 | '''return bugs dictionary created from commit comment. |
|
1096 | 1096 | |
|
1097 | 1097 | Extract bug info from changeset comments. Filter out any that are |
|
1098 | 1098 | not known to Bugzilla, and any that already have a reference to |
|
1099 | 1099 | the given changeset in their comments. |
|
1100 | 1100 | ''' |
|
1101 | 1101 | start = 0 |
|
1102 | 1102 | hours = 0.0 |
|
1103 | 1103 | bugs = {} |
|
1104 | 1104 | bugmatch = self.bug_re.search(ctx.description(), start) |
|
1105 | 1105 | fixmatch = self.fix_re.search(ctx.description(), start) |
|
1106 | 1106 | while True: |
|
1107 | 1107 | bugattribs = {} |
|
1108 | 1108 | if not bugmatch and not fixmatch: |
|
1109 | 1109 | break |
|
1110 | 1110 | if not bugmatch: |
|
1111 | 1111 | m = fixmatch |
|
1112 | 1112 | elif not fixmatch: |
|
1113 | 1113 | m = bugmatch |
|
1114 | 1114 | else: |
|
1115 | 1115 | if bugmatch.start() < fixmatch.start(): |
|
1116 | 1116 | m = bugmatch |
|
1117 | 1117 | else: |
|
1118 | 1118 | m = fixmatch |
|
1119 | 1119 | start = m.end() |
|
1120 | 1120 | if m is bugmatch: |
|
1121 | 1121 | bugmatch = self.bug_re.search(ctx.description(), start) |
|
1122 | 1122 | if b'fix' in bugattribs: |
|
1123 | 1123 | del bugattribs[b'fix'] |
|
1124 | 1124 | else: |
|
1125 | 1125 | fixmatch = self.fix_re.search(ctx.description(), start) |
|
1126 | 1126 | bugattribs[b'fix'] = None |
|
1127 | 1127 | |
|
1128 | 1128 | try: |
|
1129 | 1129 | ids = m.group(b'ids') |
|
1130 | 1130 | except IndexError: |
|
1131 | 1131 | ids = m.group(1) |
|
1132 | 1132 | try: |
|
1133 | 1133 | hours = float(m.group(b'hours')) |
|
1134 | 1134 | bugattribs[b'hours'] = hours |
|
1135 | 1135 | except IndexError: |
|
1136 | 1136 | pass |
|
1137 | 1137 | except TypeError: |
|
1138 | 1138 | pass |
|
1139 | 1139 | except ValueError: |
|
1140 | 1140 | self.ui.status(_(b"%s: invalid hours\n") % m.group(b'hours')) |
|
1141 | 1141 | |
|
1142 | 1142 | for id in self.split_re.split(ids): |
|
1143 | 1143 | if not id: |
|
1144 | 1144 | continue |
|
1145 | 1145 | bugs[int(id)] = bugattribs |
|
1146 | 1146 | if bugs: |
|
1147 | 1147 | self.bzdriver.filter_real_bug_ids(bugs) |
|
1148 | 1148 | if bugs: |
|
1149 | 1149 | self.bzdriver.filter_cset_known_bug_ids(ctx.node(), bugs) |
|
1150 | 1150 | return bugs |
|
1151 | 1151 | |
|
1152 | 1152 | def update(self, bugid, newstate, ctx): |
|
1153 | 1153 | '''update bugzilla bug with reference to changeset.''' |
|
1154 | 1154 | |
|
1155 | 1155 | def webroot(root): |
|
1156 | 1156 | '''strip leading prefix of repo root and turn into |
|
1157 | 1157 | url-safe path.''' |
|
1158 | 1158 | count = int(self.ui.config(b'bugzilla', b'strip')) |
|
1159 | 1159 | root = util.pconvert(root) |
|
1160 | 1160 | while count > 0: |
|
1161 | 1161 | c = root.find(b'/') |
|
1162 | 1162 | if c == -1: |
|
1163 | 1163 | break |
|
1164 | 1164 | root = root[c + 1 :] |
|
1165 | 1165 | count -= 1 |
|
1166 | 1166 | return root |
|
1167 | 1167 | |
|
1168 | 1168 | mapfile = None |
|
1169 | 1169 | tmpl = self.ui.config(b'bugzilla', b'template') |
|
1170 | 1170 | if not tmpl: |
|
1171 | 1171 | mapfile = self.ui.config(b'bugzilla', b'style') |
|
1172 | 1172 | if not mapfile and not tmpl: |
|
1173 | 1173 | tmpl = _( |
|
1174 | 1174 | b'changeset {node|short} in repo {root} refers ' |
|
1175 | 1175 | b'to bug {bug}.\ndetails:\n\t{desc|tabindent}' |
|
1176 | 1176 | ) |
|
1177 | 1177 | spec = logcmdutil.templatespec(tmpl, mapfile) |
|
1178 | 1178 | t = logcmdutil.changesettemplater(self.ui, self.repo, spec) |
|
1179 | 1179 | self.ui.pushbuffer() |
|
1180 | 1180 | t.show( |
|
1181 | 1181 | ctx, |
|
1182 | 1182 | changes=ctx.changeset(), |
|
1183 | 1183 | bug=pycompat.bytestr(bugid), |
|
1184 | 1184 | hgweb=self.ui.config(b'web', b'baseurl'), |
|
1185 | 1185 | root=self.repo.root, |
|
1186 | 1186 | webroot=webroot(self.repo.root), |
|
1187 | 1187 | ) |
|
1188 | 1188 | data = self.ui.popbuffer() |
|
1189 | 1189 | self.bzdriver.updatebug( |
|
1190 | 1190 | bugid, newstate, data, stringutil.email(ctx.user()) |
|
1191 | 1191 | ) |
|
1192 | 1192 | |
|
1193 | 1193 | def notify(self, bugs, committer): |
|
1194 | 1194 | '''ensure Bugzilla users are notified of bug change.''' |
|
1195 | 1195 | self.bzdriver.notify(bugs, committer) |
|
1196 | 1196 | |
|
1197 | 1197 | |
|
1198 | 1198 | def hook(ui, repo, hooktype, node=None, **kwargs): |
|
1199 | 1199 | '''add comment to bugzilla for each changeset that refers to a |
|
1200 | 1200 | bugzilla bug id. only add a comment once per bug, so same change |
|
1201 | 1201 | seen multiple times does not fill bug with duplicate data.''' |
|
1202 | 1202 | if node is None: |
|
1203 | 1203 | raise error.Abort( |
|
1204 | 1204 | _(b'hook type %s does not pass a changeset id') % hooktype |
|
1205 | 1205 | ) |
|
1206 | 1206 | try: |
|
1207 | 1207 | bz = bugzilla(ui, repo) |
|
1208 | 1208 | ctx = repo[node] |
|
1209 | 1209 | bugs = bz.find_bugs(ctx) |
|
1210 | 1210 | if bugs: |
|
1211 | 1211 | for bug in bugs: |
|
1212 | 1212 | bz.update(bug, bugs[bug], ctx) |
|
1213 | 1213 | bz.notify(bugs, stringutil.email(ctx.user())) |
|
1214 | 1214 | except Exception as e: |
|
1215 | 1215 | raise error.Abort(_(b'Bugzilla error: %s') % e) |
@@ -1,376 +1,378 b'' | |||
|
1 | 1 | # gnuarch.py - GNU Arch support for the convert extension |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2008, 2009 Aleix Conchillo Flaque <aleix@member.fsf.org> |
|
4 | 4 | # and others |
|
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 | from __future__ import absolute_import |
|
9 | 9 | |
|
10 | import email.parser as emailparser | |
|
11 | 10 | import os |
|
12 | 11 | import shutil |
|
13 | 12 | import stat |
|
14 | 13 | import tempfile |
|
15 | 14 | |
|
16 | 15 | from mercurial.i18n import _ |
|
17 | 16 | from mercurial import ( |
|
18 | 17 | encoding, |
|
19 | 18 | error, |
|
19 | mail, | |
|
20 | 20 | pycompat, |
|
21 | 21 | util, |
|
22 | 22 | ) |
|
23 | 23 | from mercurial.utils import ( |
|
24 | 24 | dateutil, |
|
25 | 25 | procutil, |
|
26 | 26 | ) |
|
27 | 27 | from . import common |
|
28 | 28 | |
|
29 | 29 | |
|
30 | 30 | class gnuarch_source(common.converter_source, common.commandline): |
|
31 | 31 | class gnuarch_rev(object): |
|
32 | 32 | def __init__(self, rev): |
|
33 | 33 | self.rev = rev |
|
34 | 34 | self.summary = b'' |
|
35 | 35 | self.date = None |
|
36 | 36 | self.author = b'' |
|
37 | 37 | self.continuationof = None |
|
38 | 38 | self.add_files = [] |
|
39 | 39 | self.mod_files = [] |
|
40 | 40 | self.del_files = [] |
|
41 | 41 | self.ren_files = {} |
|
42 | 42 | self.ren_dirs = {} |
|
43 | 43 | |
|
44 | 44 | def __init__(self, ui, repotype, path, revs=None): |
|
45 | 45 | super(gnuarch_source, self).__init__(ui, repotype, path, revs=revs) |
|
46 | 46 | |
|
47 | 47 | if not os.path.exists(os.path.join(path, b'{arch}')): |
|
48 | 48 | raise common.NoRepo( |
|
49 | 49 | _(b"%s does not look like a GNU Arch repository") % path |
|
50 | 50 | ) |
|
51 | 51 | |
|
52 | 52 | # Could use checktool, but we want to check for baz or tla. |
|
53 | 53 | self.execmd = None |
|
54 | 54 | if procutil.findexe(b'baz'): |
|
55 | 55 | self.execmd = b'baz' |
|
56 | 56 | else: |
|
57 | 57 | if procutil.findexe(b'tla'): |
|
58 | 58 | self.execmd = b'tla' |
|
59 | 59 | else: |
|
60 | 60 | raise error.Abort(_(b'cannot find a GNU Arch tool')) |
|
61 | 61 | |
|
62 | 62 | common.commandline.__init__(self, ui, self.execmd) |
|
63 | 63 | |
|
64 | 64 | self.path = os.path.realpath(path) |
|
65 | 65 | self.tmppath = None |
|
66 | 66 | |
|
67 | 67 | self.treeversion = None |
|
68 | 68 | self.lastrev = None |
|
69 | 69 | self.changes = {} |
|
70 | 70 | self.parents = {} |
|
71 | 71 | self.tags = {} |
|
72 | self.catlogparser = emailparser.Parser() | |
|
73 | 72 | self.encoding = encoding.encoding |
|
74 | 73 | self.archives = [] |
|
75 | 74 | |
|
76 | 75 | def before(self): |
|
77 | 76 | # Get registered archives |
|
78 | 77 | self.archives = [ |
|
79 | 78 | i.rstrip(b'\n') for i in self.runlines0(b'archives', b'-n') |
|
80 | 79 | ] |
|
81 | 80 | |
|
82 | 81 | if self.execmd == b'tla': |
|
83 | 82 | output = self.run0(b'tree-version', self.path) |
|
84 | 83 | else: |
|
85 | 84 | output = self.run0(b'tree-version', b'-d', self.path) |
|
86 | 85 | self.treeversion = output.strip() |
|
87 | 86 | |
|
88 | 87 | # Get name of temporary directory |
|
89 | 88 | version = self.treeversion.split(b'/') |
|
90 | 89 | self.tmppath = os.path.join( |
|
91 | 90 | pycompat.fsencode(tempfile.gettempdir()), b'hg-%s' % version[1] |
|
92 | 91 | ) |
|
93 | 92 | |
|
94 | 93 | # Generate parents dictionary |
|
95 | 94 | self.parents[None] = [] |
|
96 | 95 | treeversion = self.treeversion |
|
97 | 96 | child = None |
|
98 | 97 | while treeversion: |
|
99 | 98 | self.ui.status(_(b'analyzing tree version %s...\n') % treeversion) |
|
100 | 99 | |
|
101 | 100 | archive = treeversion.split(b'/')[0] |
|
102 | 101 | if archive not in self.archives: |
|
103 | 102 | self.ui.status( |
|
104 | 103 | _( |
|
105 | 104 | b'tree analysis stopped because it points to ' |
|
106 | 105 | b'an unregistered archive %s...\n' |
|
107 | 106 | ) |
|
108 | 107 | % archive |
|
109 | 108 | ) |
|
110 | 109 | break |
|
111 | 110 | |
|
112 | 111 | # Get the complete list of revisions for that tree version |
|
113 | 112 | output, status = self.runlines( |
|
114 | 113 | b'revisions', b'-r', b'-f', treeversion |
|
115 | 114 | ) |
|
116 | 115 | self.checkexit( |
|
117 | 116 | status, b'failed retrieving revisions for %s' % treeversion |
|
118 | 117 | ) |
|
119 | 118 | |
|
120 | 119 | # No new iteration unless a revision has a continuation-of header |
|
121 | 120 | treeversion = None |
|
122 | 121 | |
|
123 | 122 | for l in output: |
|
124 | 123 | rev = l.strip() |
|
125 | 124 | self.changes[rev] = self.gnuarch_rev(rev) |
|
126 | 125 | self.parents[rev] = [] |
|
127 | 126 | |
|
128 | 127 | # Read author, date and summary |
|
129 | 128 | catlog, status = self.run(b'cat-log', b'-d', self.path, rev) |
|
130 | 129 | if status: |
|
131 | 130 | catlog = self.run0(b'cat-archive-log', rev) |
|
132 | 131 | self._parsecatlog(catlog, rev) |
|
133 | 132 | |
|
134 | 133 | # Populate the parents map |
|
135 | 134 | self.parents[child].append(rev) |
|
136 | 135 | |
|
137 | 136 | # Keep track of the current revision as the child of the next |
|
138 | 137 | # revision scanned |
|
139 | 138 | child = rev |
|
140 | 139 | |
|
141 | 140 | # Check if we have to follow the usual incremental history |
|
142 | 141 | # or if we have to 'jump' to a different treeversion given |
|
143 | 142 | # by the continuation-of header. |
|
144 | 143 | if self.changes[rev].continuationof: |
|
145 | 144 | treeversion = b'--'.join( |
|
146 | 145 | self.changes[rev].continuationof.split(b'--')[:-1] |
|
147 | 146 | ) |
|
148 | 147 | break |
|
149 | 148 | |
|
150 | 149 | # If we reached a base-0 revision w/o any continuation-of |
|
151 | 150 | # header, it means the tree history ends here. |
|
152 | 151 | if rev[-6:] == b'base-0': |
|
153 | 152 | break |
|
154 | 153 | |
|
155 | 154 | def after(self): |
|
156 | 155 | self.ui.debug(b'cleaning up %s\n' % self.tmppath) |
|
157 | 156 | shutil.rmtree(self.tmppath, ignore_errors=True) |
|
158 | 157 | |
|
159 | 158 | def getheads(self): |
|
160 | 159 | return self.parents[None] |
|
161 | 160 | |
|
162 | 161 | def getfile(self, name, rev): |
|
163 | 162 | if rev != self.lastrev: |
|
164 | 163 | raise error.Abort(_(b'internal calling inconsistency')) |
|
165 | 164 | |
|
166 | 165 | if not os.path.lexists(os.path.join(self.tmppath, name)): |
|
167 | 166 | return None, None |
|
168 | 167 | |
|
169 | 168 | return self._getfile(name, rev) |
|
170 | 169 | |
|
171 | 170 | def getchanges(self, rev, full): |
|
172 | 171 | if full: |
|
173 | 172 | raise error.Abort(_(b"convert from arch does not support --full")) |
|
174 | 173 | self._update(rev) |
|
175 | 174 | changes = [] |
|
176 | 175 | copies = {} |
|
177 | 176 | |
|
178 | 177 | for f in self.changes[rev].add_files: |
|
179 | 178 | changes.append((f, rev)) |
|
180 | 179 | |
|
181 | 180 | for f in self.changes[rev].mod_files: |
|
182 | 181 | changes.append((f, rev)) |
|
183 | 182 | |
|
184 | 183 | for f in self.changes[rev].del_files: |
|
185 | 184 | changes.append((f, rev)) |
|
186 | 185 | |
|
187 | 186 | for src in self.changes[rev].ren_files: |
|
188 | 187 | to = self.changes[rev].ren_files[src] |
|
189 | 188 | changes.append((src, rev)) |
|
190 | 189 | changes.append((to, rev)) |
|
191 | 190 | copies[to] = src |
|
192 | 191 | |
|
193 | 192 | for src in self.changes[rev].ren_dirs: |
|
194 | 193 | to = self.changes[rev].ren_dirs[src] |
|
195 | 194 | chgs, cps = self._rendirchanges(src, to) |
|
196 | 195 | changes += [(f, rev) for f in chgs] |
|
197 | 196 | copies.update(cps) |
|
198 | 197 | |
|
199 | 198 | self.lastrev = rev |
|
200 | 199 | return sorted(set(changes)), copies, set() |
|
201 | 200 | |
|
202 | 201 | def getcommit(self, rev): |
|
203 | 202 | changes = self.changes[rev] |
|
204 | 203 | return common.commit( |
|
205 | 204 | author=changes.author, |
|
206 | 205 | date=changes.date, |
|
207 | 206 | desc=changes.summary, |
|
208 | 207 | parents=self.parents[rev], |
|
209 | 208 | rev=rev, |
|
210 | 209 | ) |
|
211 | 210 | |
|
212 | 211 | def gettags(self): |
|
213 | 212 | return self.tags |
|
214 | 213 | |
|
215 | 214 | def _execute(self, cmd, *args, **kwargs): |
|
216 | 215 | cmdline = [self.execmd, cmd] |
|
217 | 216 | cmdline += args |
|
218 | 217 | cmdline = [procutil.shellquote(arg) for arg in cmdline] |
|
219 | 218 | bdevnull = pycompat.bytestr(os.devnull) |
|
220 | 219 | cmdline += [b'>', bdevnull, b'2>', bdevnull] |
|
221 | 220 | cmdline = procutil.quotecommand(b' '.join(cmdline)) |
|
222 | 221 | self.ui.debug(cmdline, b'\n') |
|
223 | 222 | return os.system(pycompat.rapply(procutil.tonativestr, cmdline)) |
|
224 | 223 | |
|
225 | 224 | def _update(self, rev): |
|
226 | 225 | self.ui.debug(b'applying revision %s...\n' % rev) |
|
227 | 226 | changeset, status = self.runlines(b'replay', b'-d', self.tmppath, rev) |
|
228 | 227 | if status: |
|
229 | 228 | # Something went wrong while merging (baz or tla |
|
230 | 229 | # issue?), get latest revision and try from there |
|
231 | 230 | shutil.rmtree(self.tmppath, ignore_errors=True) |
|
232 | 231 | self._obtainrevision(rev) |
|
233 | 232 | else: |
|
234 | 233 | old_rev = self.parents[rev][0] |
|
235 | 234 | self.ui.debug( |
|
236 | 235 | b'computing changeset between %s and %s...\n' % (old_rev, rev) |
|
237 | 236 | ) |
|
238 | 237 | self._parsechangeset(changeset, rev) |
|
239 | 238 | |
|
240 | 239 | def _getfile(self, name, rev): |
|
241 | 240 | mode = os.lstat(os.path.join(self.tmppath, name)).st_mode |
|
242 | 241 | if stat.S_ISLNK(mode): |
|
243 | 242 | data = util.readlink(os.path.join(self.tmppath, name)) |
|
244 | 243 | if mode: |
|
245 | 244 | mode = b'l' |
|
246 | 245 | else: |
|
247 | 246 | mode = b'' |
|
248 | 247 | else: |
|
249 | 248 | data = util.readfile(os.path.join(self.tmppath, name)) |
|
250 | 249 | mode = (mode & 0o111) and b'x' or b'' |
|
251 | 250 | return data, mode |
|
252 | 251 | |
|
253 | 252 | def _exclude(self, name): |
|
254 | 253 | exclude = [b'{arch}', b'.arch-ids', b'.arch-inventory'] |
|
255 | 254 | for exc in exclude: |
|
256 | 255 | if name.find(exc) != -1: |
|
257 | 256 | return True |
|
258 | 257 | return False |
|
259 | 258 | |
|
260 | 259 | def _readcontents(self, path): |
|
261 | 260 | files = [] |
|
262 | 261 | contents = os.listdir(path) |
|
263 | 262 | while len(contents) > 0: |
|
264 | 263 | c = contents.pop() |
|
265 | 264 | p = os.path.join(path, c) |
|
266 | 265 | # os.walk could be used, but here we avoid internal GNU |
|
267 | 266 | # Arch files and directories, thus saving a lot time. |
|
268 | 267 | if not self._exclude(p): |
|
269 | 268 | if os.path.isdir(p): |
|
270 | 269 | contents += [os.path.join(c, f) for f in os.listdir(p)] |
|
271 | 270 | else: |
|
272 | 271 | files.append(c) |
|
273 | 272 | return files |
|
274 | 273 | |
|
275 | 274 | def _rendirchanges(self, src, dest): |
|
276 | 275 | changes = [] |
|
277 | 276 | copies = {} |
|
278 | 277 | files = self._readcontents(os.path.join(self.tmppath, dest)) |
|
279 | 278 | for f in files: |
|
280 | 279 | s = os.path.join(src, f) |
|
281 | 280 | d = os.path.join(dest, f) |
|
282 | 281 | changes.append(s) |
|
283 | 282 | changes.append(d) |
|
284 | 283 | copies[d] = s |
|
285 | 284 | return changes, copies |
|
286 | 285 | |
|
287 | 286 | def _obtainrevision(self, rev): |
|
288 | 287 | self.ui.debug(b'obtaining revision %s...\n' % rev) |
|
289 | 288 | output = self._execute(b'get', rev, self.tmppath) |
|
290 | 289 | self.checkexit(output) |
|
291 | 290 | self.ui.debug(b'analyzing revision %s...\n' % rev) |
|
292 | 291 | files = self._readcontents(self.tmppath) |
|
293 | 292 | self.changes[rev].add_files += files |
|
294 | 293 | |
|
295 | 294 | def _stripbasepath(self, path): |
|
296 | 295 | if path.startswith(b'./'): |
|
297 | 296 | return path[2:] |
|
298 | 297 | return path |
|
299 | 298 | |
|
300 | 299 | def _parsecatlog(self, data, rev): |
|
301 | 300 | try: |
|
302 |
catlog = |
|
|
301 | catlog = mail.parsebytes(data) | |
|
303 | 302 | |
|
304 | 303 | # Commit date |
|
305 | 304 | self.changes[rev].date = dateutil.datestr( |
|
306 |
dateutil.strdate(catlog[ |
|
|
305 | dateutil.strdate(catlog[r'Standard-date'], b'%Y-%m-%d %H:%M:%S') | |
|
307 | 306 | ) |
|
308 | 307 | |
|
309 | 308 | # Commit author |
|
310 |
self.changes[rev].author = self.recode(catlog[ |
|
|
309 | self.changes[rev].author = self.recode(catlog[r'Creator']) | |
|
311 | 310 | |
|
312 | 311 | # Commit description |
|
313 | 312 | self.changes[rev].summary = b'\n\n'.join( |
|
314 | (catlog[b'Summary'], catlog.get_payload()) | |
|
313 | ( | |
|
314 | self.recode(catlog[r'Summary']), | |
|
315 | self.recode(catlog.get_payload()), | |
|
316 | ) | |
|
315 | 317 | ) |
|
316 | 318 | self.changes[rev].summary = self.recode(self.changes[rev].summary) |
|
317 | 319 | |
|
318 | 320 | # Commit revision origin when dealing with a branch or tag |
|
319 |
if |
|
|
321 | if r'Continuation-of' in catlog: | |
|
320 | 322 | self.changes[rev].continuationof = self.recode( |
|
321 |
catlog[ |
|
|
323 | catlog[r'Continuation-of'] | |
|
322 | 324 | ) |
|
323 | 325 | except Exception: |
|
324 | 326 | raise error.Abort(_(b'could not parse cat-log of %s') % rev) |
|
325 | 327 | |
|
326 | 328 | def _parsechangeset(self, data, rev): |
|
327 | 329 | for l in data: |
|
328 | 330 | l = l.strip() |
|
329 | 331 | # Added file (ignore added directory) |
|
330 | 332 | if l.startswith(b'A') and not l.startswith(b'A/'): |
|
331 | 333 | file = self._stripbasepath(l[1:].strip()) |
|
332 | 334 | if not self._exclude(file): |
|
333 | 335 | self.changes[rev].add_files.append(file) |
|
334 | 336 | # Deleted file (ignore deleted directory) |
|
335 | 337 | elif l.startswith(b'D') and not l.startswith(b'D/'): |
|
336 | 338 | file = self._stripbasepath(l[1:].strip()) |
|
337 | 339 | if not self._exclude(file): |
|
338 | 340 | self.changes[rev].del_files.append(file) |
|
339 | 341 | # Modified binary file |
|
340 | 342 | elif l.startswith(b'Mb'): |
|
341 | 343 | file = self._stripbasepath(l[2:].strip()) |
|
342 | 344 | if not self._exclude(file): |
|
343 | 345 | self.changes[rev].mod_files.append(file) |
|
344 | 346 | # Modified link |
|
345 | 347 | elif l.startswith(b'M->'): |
|
346 | 348 | file = self._stripbasepath(l[3:].strip()) |
|
347 | 349 | if not self._exclude(file): |
|
348 | 350 | self.changes[rev].mod_files.append(file) |
|
349 | 351 | # Modified file |
|
350 | 352 | elif l.startswith(b'M'): |
|
351 | 353 | file = self._stripbasepath(l[1:].strip()) |
|
352 | 354 | if not self._exclude(file): |
|
353 | 355 | self.changes[rev].mod_files.append(file) |
|
354 | 356 | # Renamed file (or link) |
|
355 | 357 | elif l.startswith(b'=>'): |
|
356 | 358 | files = l[2:].strip().split(b' ') |
|
357 | 359 | if len(files) == 1: |
|
358 | 360 | files = l[2:].strip().split(b'\t') |
|
359 | 361 | src = self._stripbasepath(files[0]) |
|
360 | 362 | dst = self._stripbasepath(files[1]) |
|
361 | 363 | if not self._exclude(src) and not self._exclude(dst): |
|
362 | 364 | self.changes[rev].ren_files[src] = dst |
|
363 | 365 | # Conversion from file to link or from link to file (modified) |
|
364 | 366 | elif l.startswith(b'ch'): |
|
365 | 367 | file = self._stripbasepath(l[2:].strip()) |
|
366 | 368 | if not self._exclude(file): |
|
367 | 369 | self.changes[rev].mod_files.append(file) |
|
368 | 370 | # Renamed directory |
|
369 | 371 | elif l.startswith(b'/>'): |
|
370 | 372 | dirs = l[2:].strip().split(b' ') |
|
371 | 373 | if len(dirs) == 1: |
|
372 | 374 | dirs = l[2:].strip().split(b'\t') |
|
373 | 375 | src = self._stripbasepath(dirs[0]) |
|
374 | 376 | dst = self._stripbasepath(dirs[1]) |
|
375 | 377 | if not self._exclude(src) and not self._exclude(dst): |
|
376 | 378 | self.changes[rev].ren_dirs[src] = dst |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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: file was removed | |
This diff has been collapsed as it changes many lines, (4094 lines changed) Show them Hide them |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now