##// END OF EJS Templates
merge with stable
Augie Fackler -
r45251:f365dfed merge default
parent child Browse files
Show More

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

@@ -1,196 +1,197 b''
1 1 35fb62a3a673d5322f6274a44ba6456e5e4b3b37 0 iD8DBQBEYmO2ywK+sNU5EO8RAnaYAKCO7x15xUn5mnhqWNXqk/ehlhRt2QCfRDfY0LrUq2q4oK/KypuJYPHgq1A=
2 2 2be3001847cb18a23c403439d9e7d0ace30804e9 0 iD8DBQBExUbjywK+sNU5EO8RAhzxAKCtyHAQUzcTSZTqlfJ0by6vhREwWQCghaQFHfkfN0l9/40EowNhuMOKnJk=
3 3 36a957364b1b89c150f2d0e60a99befe0ee08bd3 0 iD8DBQBFfL2QywK+sNU5EO8RAjYFAKCoGlaWRTeMsjdmxAjUYx6diZxOBwCfY6IpBYsKvPTwB3oktnPt5Rmrlys=
4 4 27230c29bfec36d5540fbe1c976810aefecfd1d2 0 iD8DBQBFheweywK+sNU5EO8RAt7VAKCrqJQWT2/uo2RWf0ZI4bLp6v82jACgjrMdsaTbxRsypcmEsdPhlG6/8F4=
5 5 fb4b6d5fe100b0886f8bc3d6731ec0e5ed5c4694 0 iD8DBQBGgHicywK+sNU5EO8RAgNxAJ0VG8ixAaeudx4sZbhngI1syu49HQCeNUJQfWBgA8bkJ2pvsFpNxwYaX3I=
6 6 23889160905a1b09fffe1c07378e9fc1827606eb 0 iD8DBQBHGTzoywK+sNU5EO8RAr/UAJ0Y8s4jQtzgS+G9vM8z6CWBThZ8fwCcCT5XDj2XwxKkz/0s6UELwjsO3LU=
7 7 bae2e9c838e90a393bae3973a7850280413e091a 0 iD8DBQBH6DO5ywK+sNU5EO8RAsfrAJ0e4r9c9GF/MJsM7Xjd3NesLRC3+ACffj6+6HXdZf8cswAoFPO+DY00oD0=
8 8 d5cbbe2c49cee22a9fbeb9ea41daa0ac4e26b846 0 iD8DBQBINdwsywK+sNU5EO8RAjIUAKCPmlFJSpsPAAUKF+iNHAwVnwmzeQCdEXrL27CWclXuUKdbQC8De7LICtE=
9 9 d2375bbee6d47e62ba8e415c86e83a465dc4dce9 0 iD8DBQBIo1wpywK+sNU5EO8RAmRNAJ94x3OFt6blbqu/yBoypm/AJ44fuACfUaldXcV5z9tht97hSp22DVTEPGc=
10 10 2a67430f92f15ea5159c26b09ec4839a0c549a26 0 iEYEABECAAYFAkk1hykACgkQywK+sNU5EO85QACeNJNUanjc2tl4wUoPHNuv+lSj0ZMAoIm93wSTc/feyYnO2YCaQ1iyd9Nu
11 11 3773e510d433969e277b1863c317b674cbee2065 0 iEYEABECAAYFAklNbbAACgkQywK+sNU5EO8o+gCfeb2/lfIJZMvyDA1m+G1CsBAxfFsAoIa6iAMG8SBY7hW1Q85Yf/LXEvaE
12 12 11a4eb81fb4f4742451591489e2797dc47903277 0 iEYEABECAAYFAklcAnsACgkQywK+sNU5EO+uXwCbBVHNNsLy1g7BlAyQJwadYVyHOXoAoKvtAVO71+bv7EbVoukwTzT+P4Sx
13 13 11efa41037e280d08cfb07c09ad485df30fb0ea8 0 iEYEABECAAYFAkmvJRQACgkQywK+sNU5EO9XZwCeLMgDgPSMWMm6vgjL4lDs2pEc5+0AnRxfiFbpbBfuEFTqKz9nbzeyoBlx
14 14 02981000012e3adf40c4849bd7b3d5618f9ce82d 0 iEYEABECAAYFAknEH3wACgkQywK+sNU5EO+uXwCeI+LbLMmhjU1lKSfU3UWJHjjUC7oAoIZLvYDGOL/tNZFUuatc3RnZ2eje
15 15 196d40e7c885fa6e95f89134809b3ec7bdbca34b 0 iEYEABECAAYFAkpL2X4ACgkQywK+sNU5EO9FOwCfXJycjyKJXsvQqKkHrglwOQhEKS4An36GfKzptfN8b1qNc3+ya/5c2WOM
16 16 3ef6c14a1e8e83a31226f5881b7fe6095bbfa6f6 0 iEYEABECAAYFAkpopLIACgkQywK+sNU5EO8QSgCfZ0ztsd071rOa2lhmp9Fyue/WoI0AoLTei80/xrhRlB8L/rZEf2KBl8dA
17 17 31ec469f9b556f11819937cf68ee53f2be927ebf 0 iEYEABECAAYFAksBuxAACgkQywK+sNU5EO+mBwCfagB+A0txzWZ6dRpug3LEoK7Z1QsAoKpbk8vsLjv6/oRDicSk/qBu33+m
18 18 439d7ea6fe3aa4ab9ec274a68846779153789de9 0 iEYEABECAAYFAksVw0kACgkQywK+sNU5EO/oZwCfdfBEkgp38xq6wN2F4nj+SzofrJIAnjmxt04vaJSeOOeHylHvk6lzuQsw
19 19 296a0b14a68621f6990c54fdba0083f6f20935bf 0 iEYEABECAAYFAks+jCoACgkQywK+sNU5EO9J8wCeMUGF9E/gS2UBsqIz56WS4HMPRPUAoI5J95mwEIK8Clrl7qFRidNI6APq
20 20 4aa619c4c2c09907034d9824ebb1dd0e878206eb 0 iEYEABECAAYFAktm9IsACgkQywK+sNU5EO9XGgCgk4HclRQhexEtooPE5GcUCdB6M8EAn2ptOhMVbIoO+JncA+tNACPFXh0O
21 21 ff2704a8ded37fbebd8b6eb5ec733731d725da8a 0 iEYEABECAAYFAkuRoSQACgkQywK+sNU5EO//3QCeJDc5r2uFyFCtAlpSA27DEE5rrxAAn2FSwTy9fhrB3QAdDQlwkEZcQzDh
22 22 2b01dab594167bc0dd33331dbaa6dca3dca1b3aa 0 iEYEABECAAYFAku1IwIACgkQywK+sNU5EO9MjgCdHLVwkTZlNHxhcznZKBL1rjN+J7cAoLLWi9LTL6f/TgBaPSKOy1ublbaW
23 23 39f725929f0c48c5fb3b90c071fc3066012456ca 0 iEYEABECAAYFAkvclvsACgkQywK+sNU5EO9FSwCeL9i5x8ALW/LE5+lCX6MFEAe4MhwAn1ev5o6SX6GrNdDfKweiemfO2VBk
24 24 fdcf80f26604f233dc4d8f0a5ef9d7470e317e8a 0 iEYEABECAAYFAkvsKTkACgkQywK+sNU5EO9qEACgiSiRGvTG2vXGJ65tUSOIYihTuFAAnRzRIqEVSw8M8/RGeUXRps0IzaCO
25 25 24fe2629c6fd0c74c90bd066e77387c2b02e8437 0 iEYEABECAAYFAkwFLRsACgkQywK+sNU5EO+pJACgp13tPI+pbwKZV+LeMjcQ4H6tCZYAoJebzhd6a8yYx6qiwpJxA9BXZNXy
26 26 f786fc4b8764cd2a5526d259cf2f94d8a66924d9 0 iEYEABECAAYFAkwsyxcACgkQywK+sNU5EO+crACfUpNAF57PmClkSri9nJcBjb2goN4AniPCNaKvnki7TnUsi1u2oxltpKKL
27 27 bf1774d95bde614af3956d92b20e2a0c68c5fec7 0 iEYEABECAAYFAkxVwccACgkQywK+sNU5EO+oFQCeJzwZ+we1fIIyBGCddHceOUAN++cAnjvT6A8ZWW0zV21NXIFF1qQmjxJd
28 28 c00f03a4982e467fb6b6bd45908767db6df4771d 0 iEYEABECAAYFAkxXDqsACgkQywK+sNU5EO/GJACfT9Rz4hZOxPQEs91JwtmfjevO84gAmwSmtfo5mmWSm8gtTUebCcdTv0Kf
29 29 ff5cec76b1c5b6be9c3bb923aae8c3c6d079d6b9 0 iD8DBQBMdo+qywK+sNU5EO8RAqQpAJ975BL2CCAiWMz9SXthNQ9xG181IwCgp4O+KViHPkufZVFn2aTKMNvcr1A=
30 30 93d8bff78c96fe7e33237b257558ee97290048a4 0 iD8DBQBMpfvdywK+sNU5EO8RAsxVAJ0UaL1XB51C76JUBhafc9GBefuMxwCdEWkTOzwvE0SarJBe9i008jhbqW4=
31 31 333421b9e0f96c7bc788e5667c146a58a9440a55 0 iD8DBQBMz0HOywK+sNU5EO8RAlsEAJ0USh6yOG7OrWkADGunVt9QimBQnwCbBqeMnKgSbwEw8jZwE3Iz1mdrYlo=
32 32 4438875ec01bd0fc32be92b0872eb6daeed4d44f 0 iD8DBQBM4WYUywK+sNU5EO8RAhCVAJ0dJswachwFAHALmk1x0RJehxzqPQCbBNskP9n/X689jB+btNTZTyKU/fw=
33 33 6aff4f144ad356311318b0011df0bb21f2c97429 0 iD8DBQBM9uxXywK+sNU5EO8RAv+4AKCDj4qKP16GdPaq1tP6BUwpM/M1OACfRyzLPp/qiiN8xJTWoWYSe/XjJug=
34 34 e3bf16703e2601de99e563cdb3a5d50b64e6d320 0 iD8DBQBNH8WqywK+sNU5EO8RAiQTAJ9sBO+TeiGro4si77VVaQaA6jcRUgCfSA28dBbjj0oFoQwvPoZjANiZBH8=
35 35 a6c855c32ea081da3c3b8ff628f1847ff271482f 0 iD8DBQBNSJJ+ywK+sNU5EO8RAoJaAKCweDEF70fu+r1Zn7pYDXdlk5RuSgCeO9gK/eit8Lin/1n3pO7aYguFLok=
36 36 2b2155623ee2559caf288fd333f30475966c4525 0 iD8DBQBNSJeBywK+sNU5EO8RAm1KAJ4hW9Cm9nHaaGJguchBaPLlAr+O3wCgqgmMok8bdAS06N6PL60PSTM//Gg=
37 37 2616325766e3504c8ae7c84bd15ee610901fe91d 0 iD8DBQBNbWy9ywK+sNU5EO8RAlWCAJ4mW8HbzjJj9GpK98muX7k+7EvEHwCfaTLbC/DH3QEsZBhEP+M8tzL6RU4=
38 38 aa1f3be38ab127280761889d2dca906ca465b5f4 0 iD8DBQBNeQq7ywK+sNU5EO8RAlEOAJ4tlEDdetE9lKfjGgjbkcR8PrC3egCfXCfF3qNVvU/2YYjpgvRwevjvDy0=
39 39 b032bec2c0a651ca0ddecb65714bfe6770f67d70 0 iD8DBQBNlg5kywK+sNU5EO8RAnGEAJ9gmEx6MfaR4XcG2m/93vwtfyzs3gCgltzx8/YdHPwqDwRX/WbpYgi33is=
40 40 3cb1e95676ad089596bd81d0937cad37d6e3b7fb 0 iD8DBQBNvTy4ywK+sNU5EO8RAmp8AJ9QnxK4jTJ7G722MyeBxf0UXEdGwACgtlM7BKtNQfbEH/fOW5y+45W88VI=
41 41 733af5d9f6b22387913e1d11350fb8cb7c1487dd 0 iD8DBQBN5q/8ywK+sNU5EO8RArRGAKCNGT94GKIYtSuwZ57z1sQbcw6uLACfffpbMV4NAPMl8womAwg+7ZPKnIU=
42 42 de9eb6b1da4fc522b1cab16d86ca166204c24f25 0 iD8DBQBODhfhywK+sNU5EO8RAr2+AJ4ugbAj8ae8/K0bYZzx3sascIAg1QCeK3b+zbbVVqd3b7CDpwFnaX8kTd4=
43 43 4a43e23b8c55b4566b8200bf69fe2158485a2634 0 iD8DBQBONzIMywK+sNU5EO8RAj5SAJ0aPS3+JHnyI6bHB2Fl0LImbDmagwCdGbDLp1S7TFobxXudOH49bX45Iik=
44 44 d629f1e89021103f1753addcef6b310e4435b184 0 iD8DBQBOWAsBywK+sNU5EO8RAht4AJwJl9oNFopuGkj5m8aKuf7bqPkoAQCeNrEm7UhFsZKYT5iUOjnMV7s2LaM=
45 45 351a9292e430e35766c552066ed3e87c557b803b 0 iD8DBQBOh3zUywK+sNU5EO8RApFMAKCD3Y/u3avDFndznwqfG5UeTHMlvACfUivPIVQZyDZnhZMq0UhC6zhCEQg=
46 46 384082750f2c51dc917d85a7145748330fa6ef4d 0 iD8DBQBOmd+OywK+sNU5EO8RAgDgAJ9V/X+G7VLwhTpHrZNiOHabzSyzYQCdE2kKfIevJUYB9QLAWCWP6DPwrwI=
47 47 41453d55b481ddfcc1dacb445179649e24ca861d 0 iD8DBQBOsFhpywK+sNU5EO8RAqM6AKCyfxUae3/zLuiLdQz+JR78690eMACfQ6JTBQib4AbE+rUDdkeFYg9K/+4=
48 48 195dbd1cef0c2f9f8bcf4ea303238105f716bda3 0 iD8DBQBO1/fWywK+sNU5EO8RAmoPAKCR5lpv1D6JLURHD8KVLSV4GRVEBgCgnd0Sy78ligNfqAMafmACRDvj7vo=
49 49 6344043924497cd06d781d9014c66802285072e4 0 iD8DBQBPALgmywK+sNU5EO8RAlfhAJ9nYOdWnhfVDHYtDTJAyJtXBAQS9wCgnefoSQt7QABkbGxM+Q85UYEBuD0=
50 50 db33555eafeaf9df1e18950e29439eaa706d399b 0 iD8DBQBPGdzxywK+sNU5EO8RAppkAJ9jOXhUVE/97CPgiMA0pMGiIYnesQCfengAszcBiSiKGugiI8Okc9ghU+Y=
51 51 2aa5b51f310fb3befd26bed99c02267f5c12c734 0 iD8DBQBPKZ9bywK+sNU5EO8RAt1TAJ45r1eJ0YqSkInzrrayg4TVCh0SnQCgm0GA/Ua74jnnDwVQ60lAwROuz1Q=
52 52 53e2cd303ecf8ca7c7eeebd785c34e5ed6b0f4a4 0 iD8DBQBPT/fvywK+sNU5EO8RAnfYAKCn7d0vwqIb100YfWm1F7nFD5B+FACeM02YHpQLSNsztrBCObtqcnfod7Q=
53 53 b9bd95e61b49c221c4cca24e6da7c946fc02f992 0 iD8DBQBPeLsIywK+sNU5EO8RAvpNAKCtKe2gitz8dYn52IRF0hFOPCR7AQCfRJL/RWCFweu2T1vH/mUOCf8SXXc=
54 54 d9e2f09d5488c395ae9ddbb320ceacd24757e055 0 iD8DBQBPju/dywK+sNU5EO8RArBYAJ9xtifdbk+hCOJO8OZa4JfHX8OYZQCeKPMBaBWiT8N/WHoOm1XU0q+iono=
55 55 00182b3d087909e3c3ae44761efecdde8f319ef3 0 iD8DBQBPoFhIywK+sNU5EO8RAhzhAKCBj1n2jxPTkZNJJ5pSp3soa+XHIgCgsZZpAQxOpXwCp0eCdNGe0+pmxmg=
56 56 5983de86462c5a9f42a3ad0f5e90ce5b1d221d25 0 iD8DBQBPovNWywK+sNU5EO8RAhgiAJ980T91FdPTRMmVONDhpkMsZwVIMACgg3bKvoWSeuCW28llUhAJtUjrMv0=
57 57 85a358df5bbbe404ca25730c9c459b34263441dc 0 iD8DBQBPyZsWywK+sNU5EO8RAnpLAJ48qrGDJRT+pteS0mSQ11haqHstPwCdG4ccGbk+0JHb7aNy8/NRGAOqn9w=
58 58 b013baa3898e117959984fc64c29d8c784d2f28b 0 iD8DBQBP8QOPywK+sNU5EO8RAqimAKCFRSx0lvG6y8vne2IhNG062Hn0dACeMLI5/zhpWpHBIVeAAquYfx2XFeA=
59 59 7f5094bb3f423fc799e471aac2aee81a7ce57a0b 0 iD8DBQBQGiL8ywK+sNU5EO8RAq5oAJ4rMMCPx6O+OuzNXVOexogedWz/QgCeIiIxLd76I4pXO48tdXhr0hQcBuM=
60 60 072209ae4ddb654eb2d5fd35bff358c738414432 0 iD8DBQBQQkq0ywK+sNU5EO8RArDTAJ9nk5CySnNAjAXYvqvx4uWCw9ThZwCgqmFRehH/l+oTwj3f8nw8u8qTCdc=
61 61 b3f0f9a39c4e1d0250048cd803ab03542d6f140a 0 iD8DBQBQamltywK+sNU5EO8RAlsqAJ4qF/m6aFu4mJCOKTiAP5RvZFK02ACfawYShUZO6OXEFfveU0aAxDR0M1k=
62 62 d118a4f4fd16d9b558ec3f3e87bfee772861d2b7 0 iD8DBQBQgPV5ywK+sNU5EO8RArylAJ0abcx5NlDjyv3ZDWpAfRIHyRsJtQCgn4TMuEayqgxzrvadQZHdTEU2g38=
63 63 195ad823b5d58c68903a6153a25e3fb4ed25239d 0 iD8DBQBQkuT9ywK+sNU5EO8RAhB4AKCeerItoK2Jipm2cVf4euGofAa/WACeJj3TVd4pFILpb+ogj7ebweFLJi0=
64 64 0c10cf8191469e7c3c8844922e17e71a176cb7cb 0 iD8DBQBQvQWoywK+sNU5EO8RAnq3AJoCn98u4geFx5YaQaeh99gFhCd7bQCgjoBwBSUyOvGd0yBy60E3Vv3VZhM=
65 65 a4765077b65e6ae29ba42bab7834717b5072d5ba 0 iD8DBQBQ486sywK+sNU5EO8RAhmJAJ90aLfLKZhmcZN7kqphigQJxiFOQACeJ5IUZxjGKH4xzi3MrgIcx9n+dB0=
66 66 f5fbe15ca7449f2c9a3cf817c86d0ae68b307214 0 iD8DBQBQ+yuYywK+sNU5EO8RAm9JAJoD/UciWvpGeKBcpGtZJBFJVcL/HACghDXSgQ+xQDjB+6uGrdgAQsRR1Lg=
67 67 a6088c05e43a8aee0472ca3a4f6f8d7dd914ebbf 0 iD8DBQBRDDROywK+sNU5EO8RAh75AJ9uJCGoCWnP0Lv/+XuYs4hvUl+sAgCcD36QgAnuw8IQXrvv684BAXAnHcA=
68 68 7511d4df752e61fe7ae4f3682e0a0008573b0402 0 iD8DBQBRFYaoywK+sNU5EO8RAuErAJoDyhXn+lptU3+AevVdwAIeNFyR2gCdHzPHyWd+JDeWCUR+pSOBi8O2ppM=
69 69 5b7175377babacce80a6c1e12366d8032a6d4340 0 iD8DBQBRMCYgywK+sNU5EO8RAq1/AKCWKlt9ysibyQgYwoxxIOZv5J8rpwCcDSHQaaf1fFZUTnQsOePwcM2Y/Sg=
70 70 50c922c1b5145dab8baefefb0437d363b6a6c21c 0 iD8DBQBRWnUnywK+sNU5EO8RAuQRAJwM42cJqJPeqJ0jVNdMqKMDqr4dSACeP0cRVGz1gitMuV0x8f3mrZrqc7I=
71 71 8a7bd2dccd44ed571afe7424cd7f95594f27c092 0 iD8DBQBRXfBvywK+sNU5EO8RAn+LAKCsMmflbuXjYRxlzFwId5ptm8TZcwCdGkyLbZcASBOkzQUm/WW1qfknJHU=
72 72 292cd385856d98bacb2c3086f8897bc660c2beea 0 iD8DBQBRcM0BywK+sNU5EO8RAjp4AKCJBykQbvXhKuvLSMxKx3a2TBiXcACfbr/kLg5GlZTF/XDPmY+PyHgI/GM=
73 73 23f785b38af38d2fca6b8f3db56b8007a84cd73a 0 iD8DBQBRgZwNywK+sNU5EO8RAmO4AJ4u2ILGuimRP6MJgE2t65LZ5dAdkACgiENEstIdrlFC80p+sWKD81kKIYI=
74 74 ddc7a6be20212d18f3e27d9d7e6f079a66d96f21 0 iD8DBQBRkswvywK+sNU5EO8RAiYYAJsHTHyHbJeAgmGvBTmDrfcKu4doUgCeLm7eGBjx7yAPUvEtxef8rAkQmXI=
75 75 cceaf7af4c9e9e6fa2dbfdcfe9856c5da69c4ffd 0 iD8DBQBRqnFLywK+sNU5EO8RAsWNAJ9RR6t+y1DLFc2HeH0eN9VfZAKF9gCeJ8ezvhtKq/LMs0/nvcgKQc/d5jk=
76 76 009794acc6e37a650f0fae37872e733382ac1c0c 0 iD8DBQBR0guxywK+sNU5EO8RArNkAKCq9pMihVzP8Os5kCmgbWpe5C37wgCgqzuPZTHvAsXF5wTyaSTMVa9Ccq4=
77 77 f0d7721d7322dcfb5af33599c2543f27335334bb 0 iD8DBQBR8taaywK+sNU5EO8RAqeEAJ4idDhhDuEsgsUjeQgWNj498matHACfT67gSF5w0ylsrBx1Hb52HkGXDm0=
78 78 f37b5a17e6a0ee17afde2cdde5393dd74715fb58 0 iD8DBQBR+ymFywK+sNU5EO8RAuSdAJkBMcd9DAZ3rWE9WGKPm2YZ8LBoXACfXn/wbEsVy7ZgJoUwiWmHSnQaWCI=
79 79 335a558f81dc73afeab4d7be63617392b130117f 0 iQIVAwUAUiZrIyBXgaxoKi1yAQK2iw//cquNqqSkc8Re5/TZT9I6NH+lh6DbOKjJP0Xl1Wqq0K+KSIUgZG4G32ovaEb2l5X0uY+3unRPiZ0ebl0YSw4Fb2ZiPIADXLBTOYRrY2Wwd3tpJeGI6wEgZt3SfcITV/g7NJrCjT3FlYoSOIayrExM80InSdcEM0Q3Rx6HKzY2acyxzgZeAtAW5ohFvHilSvY6p5Gcm4+QptMxvw45GPdreUmjeXZxNXNXZ8P+MjMz/QJbai/N7PjmK8lqnhkBsT48Ng/KhhmOkGntNJ2/ImBWLFGcWngSvJ7sfWwnyhndvGhe0Hq1NcCf7I8TjNDxU5TR+m+uW7xjXdLoDbUjBdX4sKXnh8ZjbYiODKBOrrDq25cf8nA/tnpKyE/qsVy60kOk6loY4XKiYmn1V49Ta0emmDx0hqo3HgxHHsHX0NDnGdWGol7cPRET0RzVobKq1A0jnrhPooWidvLh9bPzLonrWDo+ib+DuySoRkuYUK4pgZJ2mbg6daFOBEZygkSyRB8bo1UQUP7EgQDrWe4khb/5GHEfDkrQz3qu/sXvc0Ir1mOUWBFPHC2DjjCn/oMJuUkG1SwM8l2Bfv7h67ssES6YQ2+RjOix4yid7EXS/Ogl45PzCIPSI5+BbNs10JhE0w5uErBHlF53EDTe/TSLc+GU6DB6PP6dH912Njdr3jpNSUQ=
80 80 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
@@ -1,209 +1,210 b''
1 1 d40cc5aacc31ed673d9b5b24f98bee78c283062c 0.4f
2 2 1c590d34bf61e2ea12c71738e5a746cd74586157 0.4e
3 3 7eca4cfa8aad5fce9a04f7d8acadcd0452e2f34e 0.4d
4 4 b4d0c3786ad3e47beacf8412157326a32b6d25a4 0.4c
5 5 f40273b0ad7b3a6d3012fd37736d0611f41ecf54 0.5
6 6 0a28dfe59f8fab54a5118c5be4f40da34a53cdb7 0.5b
7 7 12e0fdbc57a0be78f0e817fd1d170a3615cd35da 0.6
8 8 4ccf3de52989b14c3d84e1097f59e39a992e00bd 0.6b
9 9 eac9c8efcd9bd8244e72fb6821f769f450457a32 0.6c
10 10 979c049974485125e1f9357f6bbe9c1b548a64c3 0.7
11 11 3a56574f329a368d645853e0f9e09472aee62349 0.8
12 12 6a03cff2b0f5d30281e6addefe96b993582f2eac 0.8.1
13 13 35fb62a3a673d5322f6274a44ba6456e5e4b3b37 0.9
14 14 2be3001847cb18a23c403439d9e7d0ace30804e9 0.9.1
15 15 36a957364b1b89c150f2d0e60a99befe0ee08bd3 0.9.2
16 16 27230c29bfec36d5540fbe1c976810aefecfd1d2 0.9.3
17 17 fb4b6d5fe100b0886f8bc3d6731ec0e5ed5c4694 0.9.4
18 18 23889160905a1b09fffe1c07378e9fc1827606eb 0.9.5
19 19 bae2e9c838e90a393bae3973a7850280413e091a 1.0
20 20 d5cbbe2c49cee22a9fbeb9ea41daa0ac4e26b846 1.0.1
21 21 d2375bbee6d47e62ba8e415c86e83a465dc4dce9 1.0.2
22 22 2a67430f92f15ea5159c26b09ec4839a0c549a26 1.1
23 23 3773e510d433969e277b1863c317b674cbee2065 1.1.1
24 24 11a4eb81fb4f4742451591489e2797dc47903277 1.1.2
25 25 11efa41037e280d08cfb07c09ad485df30fb0ea8 1.2
26 26 02981000012e3adf40c4849bd7b3d5618f9ce82d 1.2.1
27 27 196d40e7c885fa6e95f89134809b3ec7bdbca34b 1.3
28 28 3ef6c14a1e8e83a31226f5881b7fe6095bbfa6f6 1.3.1
29 29 31ec469f9b556f11819937cf68ee53f2be927ebf 1.4
30 30 439d7ea6fe3aa4ab9ec274a68846779153789de9 1.4.1
31 31 296a0b14a68621f6990c54fdba0083f6f20935bf 1.4.2
32 32 4aa619c4c2c09907034d9824ebb1dd0e878206eb 1.4.3
33 33 ff2704a8ded37fbebd8b6eb5ec733731d725da8a 1.5
34 34 2b01dab594167bc0dd33331dbaa6dca3dca1b3aa 1.5.1
35 35 39f725929f0c48c5fb3b90c071fc3066012456ca 1.5.2
36 36 fdcf80f26604f233dc4d8f0a5ef9d7470e317e8a 1.5.3
37 37 24fe2629c6fd0c74c90bd066e77387c2b02e8437 1.5.4
38 38 f786fc4b8764cd2a5526d259cf2f94d8a66924d9 1.6
39 39 bf1774d95bde614af3956d92b20e2a0c68c5fec7 1.6.1
40 40 c00f03a4982e467fb6b6bd45908767db6df4771d 1.6.2
41 41 ff5cec76b1c5b6be9c3bb923aae8c3c6d079d6b9 1.6.3
42 42 93d8bff78c96fe7e33237b257558ee97290048a4 1.6.4
43 43 333421b9e0f96c7bc788e5667c146a58a9440a55 1.7
44 44 4438875ec01bd0fc32be92b0872eb6daeed4d44f 1.7.1
45 45 6aff4f144ad356311318b0011df0bb21f2c97429 1.7.2
46 46 e3bf16703e2601de99e563cdb3a5d50b64e6d320 1.7.3
47 47 a6c855c32ea081da3c3b8ff628f1847ff271482f 1.7.4
48 48 2b2155623ee2559caf288fd333f30475966c4525 1.7.5
49 49 2616325766e3504c8ae7c84bd15ee610901fe91d 1.8
50 50 aa1f3be38ab127280761889d2dca906ca465b5f4 1.8.1
51 51 b032bec2c0a651ca0ddecb65714bfe6770f67d70 1.8.2
52 52 3cb1e95676ad089596bd81d0937cad37d6e3b7fb 1.8.3
53 53 733af5d9f6b22387913e1d11350fb8cb7c1487dd 1.8.4
54 54 de9eb6b1da4fc522b1cab16d86ca166204c24f25 1.9
55 55 4a43e23b8c55b4566b8200bf69fe2158485a2634 1.9.1
56 56 d629f1e89021103f1753addcef6b310e4435b184 1.9.2
57 57 351a9292e430e35766c552066ed3e87c557b803b 1.9.3
58 58 384082750f2c51dc917d85a7145748330fa6ef4d 2.0-rc
59 59 41453d55b481ddfcc1dacb445179649e24ca861d 2.0
60 60 195dbd1cef0c2f9f8bcf4ea303238105f716bda3 2.0.1
61 61 6344043924497cd06d781d9014c66802285072e4 2.0.2
62 62 db33555eafeaf9df1e18950e29439eaa706d399b 2.1-rc
63 63 2aa5b51f310fb3befd26bed99c02267f5c12c734 2.1
64 64 53e2cd303ecf8ca7c7eeebd785c34e5ed6b0f4a4 2.1.1
65 65 b9bd95e61b49c221c4cca24e6da7c946fc02f992 2.1.2
66 66 d9e2f09d5488c395ae9ddbb320ceacd24757e055 2.2-rc
67 67 00182b3d087909e3c3ae44761efecdde8f319ef3 2.2
68 68 5983de86462c5a9f42a3ad0f5e90ce5b1d221d25 2.2.1
69 69 85a358df5bbbe404ca25730c9c459b34263441dc 2.2.2
70 70 b013baa3898e117959984fc64c29d8c784d2f28b 2.2.3
71 71 a06e2681dd1786e2354d84a5fa9c1c88dd4fa3e0 2.3-rc
72 72 7f5094bb3f423fc799e471aac2aee81a7ce57a0b 2.3
73 73 072209ae4ddb654eb2d5fd35bff358c738414432 2.3.1
74 74 b3f0f9a39c4e1d0250048cd803ab03542d6f140a 2.3.2
75 75 d118a4f4fd16d9b558ec3f3e87bfee772861d2b7 2.4-rc
76 76 195ad823b5d58c68903a6153a25e3fb4ed25239d 2.4
77 77 0c10cf8191469e7c3c8844922e17e71a176cb7cb 2.4.1
78 78 a4765077b65e6ae29ba42bab7834717b5072d5ba 2.4.2
79 79 f5fbe15ca7449f2c9a3cf817c86d0ae68b307214 2.5-rc
80 80 a6088c05e43a8aee0472ca3a4f6f8d7dd914ebbf 2.5
81 81 7511d4df752e61fe7ae4f3682e0a0008573b0402 2.5.1
82 82 5b7175377babacce80a6c1e12366d8032a6d4340 2.5.2
83 83 50c922c1b5145dab8baefefb0437d363b6a6c21c 2.5.3
84 84 8a7bd2dccd44ed571afe7424cd7f95594f27c092 2.5.4
85 85 292cd385856d98bacb2c3086f8897bc660c2beea 2.6-rc
86 86 23f785b38af38d2fca6b8f3db56b8007a84cd73a 2.6
87 87 ddc7a6be20212d18f3e27d9d7e6f079a66d96f21 2.6.1
88 88 cceaf7af4c9e9e6fa2dbfdcfe9856c5da69c4ffd 2.6.2
89 89 009794acc6e37a650f0fae37872e733382ac1c0c 2.6.3
90 90 f0d7721d7322dcfb5af33599c2543f27335334bb 2.7-rc
91 91 f37b5a17e6a0ee17afde2cdde5393dd74715fb58 2.7
92 92 335a558f81dc73afeab4d7be63617392b130117f 2.7.1
93 93 e7fa36d2ad3a7944a52dca126458d6f482db3524 2.7.2
94 94 1596f2d8f2421314b1ddead8f7d0c91009358994 2.8-rc
95 95 d825e4025e39d1c39db943cdc89818abd0a87c27 2.8
96 96 209e04a06467e2969c0cc6501335be0406d46ef0 2.8.1
97 97 ca387377df7a3a67dbb90b6336b781cdadc3ef41 2.8.2
98 98 8862469e16f9236208581b20de5f96bd13cc039d 2.9-rc
99 99 3cec5134e9c4bceab6a00c60f52a4f80677a78f2 2.9
100 100 b96cb15ec9e04d8ac5ee08b34fcbbe4200588965 2.9.1
101 101 3f83fc5cfe715d292069ee8417c83804f6c6c1e4 2.9.2
102 102 564f55b251224f16508dd1311452db7780dafe2b 3.0-rc
103 103 2195ac506c6ababe86985b932f4948837c0891b5 3.0
104 104 269c80ee5b3cb3684fa8edc61501b3506d02eb10 3.0.1
105 105 2d8cd3d0e83c7336c0cb45a9f88638363f993848 3.0.2
106 106 6c36dc6cd61a0e1b563f1d51e55bdf4dacf12162 3.1-rc
107 107 3178e49892020336491cdc6945885c4de26ffa8b 3.1
108 108 5dc91146f35369949ea56b40172308158b59063a 3.1.1
109 109 f768c888aaa68d12dd7f509dcc7f01c9584357d0 3.1.2
110 110 7f8d16af8cae246fa5a48e723d48d58b015aed94 3.2-rc
111 111 ced632394371a36953ce4d394f86278ae51a2aae 3.2
112 112 643c58303fb0ec020907af28b9e486be299ba043 3.2.1
113 113 902554884335e5ca3661d63be9978eb4aec3f68a 3.2.2
114 114 6dad422ecc5adb63d9fa649eeb8e05a5f9bc4900 3.2.3
115 115 1265a3a71d75396f5d4cf6935ae7d9ba5407a547 3.2.4
116 116 db8e3f7948b1fdeb9ad12d448fc3525759908b9f 3.3-rc
117 117 fbdd5195528fae4f41feebc1838215c110b25d6a 3.3
118 118 5b4ed033390bf6e2879c8f5c28c84e1ee3b87231 3.3.1
119 119 07a92bbd02e5e3a625e0820389b47786b02b2cea 3.3.2
120 120 2e2e9a0750f91a6fe0ad88e4de34f8efefdcab08 3.3.3
121 121 e89f909edffad558b56f4affa8239e4832f88de0 3.4-rc
122 122 8cc6036bca532e06681c5a8fa37efaa812de67b5 3.4
123 123 ed18f4acf435a2824c6f49fba40f42b9df5da7ad 3.4.1
124 124 540cd0ddac49c1125b2e013aa2ff18ecbd4dd954 3.4.2
125 125 96a38d44ba093bd1d1ecfd34119e94056030278b 3.5-rc
126 126 21aa1c313b05b1a85f8ffa1120d51579ddf6bf24 3.5
127 127 1a45e49a6bed023deb229102a8903234d18054d3 3.5.1
128 128 9a466b9f9792e3ad7ae3fc6c43c3ff2e136b718d 3.5.2
129 129 b66e3ca0b90c3095ea28dfd39aa24247bebf5c20 3.6-rc
130 130 47dd34f2e7272be9e3b2a5a83cd0d20be44293f4 3.6
131 131 1aa5083cbebbe7575c88f3402ab377539b484897 3.6.1
132 132 2d437a0f3355834a9485bbbeb30a52a052c98f19 3.6.2
133 133 ea389970c08449440587712117f178d33bab3f1e 3.6.3
134 134 158bdc8965720ca4061f8f8d806563cfc7cdb62e 3.7-rc
135 135 2408645de650d8a29a6ce9e7dce601d8dd0d1474 3.7
136 136 b698abf971e7377d9b7ec7fc8c52df45255b0329 3.7.1
137 137 d493d64757eb45ada99fcb3693e479a51b7782da 3.7.2
138 138 ae279d4a19e9683214cbd1fe8298cf0b50571432 3.7.3
139 139 740156eedf2c450aee58b1a90b0e826f47c5da64 3.8-rc
140 140 f85de28eae32e7d3064b1a1321309071bbaaa069 3.8
141 141 a56296f55a5e1038ea5016dace2076b693c28a56 3.8.1
142 142 aaabed77791a75968a12b8c43ad263631a23ee81 3.8.2
143 143 a9764ab80e11bcf6a37255db7dd079011f767c6c 3.8.3
144 144 26a5d605b8683a292bb89aea11f37a81b06ac016 3.8.4
145 145 519bb4f9d3a47a6e83c2b414d58811ed38f503c2 3.9-rc
146 146 299546f84e68dbb9bd026f0f3a974ce4bdb93686 3.9
147 147 ccd436f7db6d5d7b9af89715179b911d031d44f1 3.9.1
148 148 149433e68974eb5c63ccb03f794d8b57339a80c4 3.9.2
149 149 438173c415874f6ac653efc1099dec9c9150e90f 4.0-rc
150 150 eab27446995210c334c3d06f1a659e3b9b5da769 4.0
151 151 b3b1ae98f6a0e14c1e1ba806a6c18e193b6dae5c 4.0.1
152 152 e69874dc1f4e142746ff3df91e678a09c6fc208c 4.0.2
153 153 a1dd2c0c479e0550040542e392e87bc91262517e 4.1-rc
154 154 e1526da1e6d84e03146151c9b6e6950fe9a83d7d 4.1
155 155 25703b624d27e3917d978af56d6ad59331e0464a 4.1.1
156 156 ed5b25874d998ababb181a939dd37a16ea644435 4.1.2
157 157 77eaf9539499a1b8be259ffe7ada787d07857f80 4.1.3
158 158 616e788321cc4ae9975b7f0c54c849f36d82182b 4.2-rc
159 159 bb96d4a497432722623ae60d9bc734a1e360179e 4.2
160 160 c850f0ed54c1d42f9aa079ad528f8127e5775217 4.2.1
161 161 26c49ed51a698ec016d2b4c6b44ca3c3f73cc788 4.2.2
162 162 857876ebaed4e315f63157bd157d6ce553c7ab73 4.3-rc
163 163 5544af8622863796a0027566f6b646e10d522c4c 4.3
164 164 943c91326b23954e6e1c6960d0239511f9530258 4.2.3
165 165 3fee7f7d2da04226914c2258cc2884dc27384fd7 4.3.1
166 166 920977f72c7b70acfdaf56ab35360584d7845827 4.3.2
167 167 2f427b57bf9019c6dc3750baa539dc22c1be50f6 4.3.3
168 168 1e2454b60e5936f5e77498cab2648db469504487 4.4-rc
169 169 0ccb43d4cf01d013ae05917ec4f305509f851b2d 4.4
170 170 cabc840ffdee8a72f3689fb77dd74d04fdc2bc04 4.4.1
171 171 a92b9f8e11ba330614cdfd6af0e03b15c1ff3797 4.4.2
172 172 27b6df1b5adbdf647cf5c6675b40575e1b197c60 4.5-rc
173 173 d334afc585e29577f271c5eda03378736a16ca6b 4.5
174 174 369aadf7a3264b03c8b09efce715bc41e6ab4a9b 4.5.1
175 175 8bba684efde7f45add05f737952093bb2aa07155 4.5.2
176 176 7de7bd407251af2bc98e5b809c8598ee95830daf 4.5.3
177 177 ed5448edcbfa747b9154099e18630e49024fd47b 4.6rc0
178 178 1ec874717d8a93b19e0d50628443e0ee5efab3a9 4.6rc1
179 179 6614cac550aea66d19c601e45efd1b7bd08d7c40 4.6
180 180 9c5ced5276d6e7d54f7c3dadf5247b7ee98ec79c 4.6.1
181 181 0b63a6743010dfdbf8a8154186e119949bdaa1cc 4.6.2
182 182 e90130af47ce8dd53a3109aed9d15876b3e7dee8 4.7rc0
183 183 33ac6a72308a215e6086fbced347ec10aa963b0a 4.7
184 184 ede3bf31fe63677fdf5bd8db687977d4e3d792ed 4.7.1
185 185 5405cb1a79010ac50c58cd84e6f50c4556bf2a4c 4.7.2
186 186 956ec6f1320df26f3133ec40f3de866ea0695fd7 4.8rc0
187 187 a91a2837150bdcb27ae76b3646e6c93cd6a15904 4.8
188 188 1c8c54cf97256f4468da2eb4dbee24f7f3888e71 4.8.1
189 189 197f092b2cd9691e2a55d198f717b231af9be6f9 4.8.2
190 190 593718ff5844cad7a27ee3eb5adad89ac8550949 4.9rc0
191 191 83377b4b4ae0e9a6b8e579f7b0a693b8cf5c3b10 4.9
192 192 4ea21df312ec7159c5b3633096b6ecf68750b0dd 4.9.1
193 193 4a8d9ed864754837a185a642170cde24392f9abf 5.0rc0
194 194 07e479ef7c9639be0029f00e6a722b96dcc05fee 5.0
195 195 c3484ddbdb9621256d597ed86b90d229c59c2af9 5.0.1
196 196 97ada9b8d51bef24c5cb4cdca4243f0db694ab6e 5.0.2
197 197 e386b5f4f8360dbb43a576dd9b1368e386fefa5b 5.1rc0
198 198 e91930d712e8507d1bc1b2dffd96c83edc4cbed3 5.1
199 199 a4e32fd539ab41489a51b2aa88bda9a73b839562 5.1.1
200 200 181e52f2b62f4768aa0d988936c929dc7c4a41a0 5.1.2
201 201 59338f9561099de77c684c00f76507f11e46ebe8 5.2rc0
202 202 ca3dca416f8d5863ca6f5a4a6a6bb835dcd5feeb 5.2
203 203 a50fecefa691c9b72a99e49aa6fe9dd13943c2bf 5.2.1
204 204 b4c82b70418022e67cc0e69b1aa3c3aa43aa1d29 5.2.2
205 205 84a0102c05c7852c8215ef6cf21d809927586b69 5.3rc0
206 206 e4344e463c0c888a2f437b78b5982ecdf3f6650a 5.3rc1
207 207 7f5410dfc8a64bb587d19637deb95d378fd1eb5c 5.3
208 208 6d121acbb82e65fe4dd3c2318a1b61981b958492 5.3.1
209 209 8fca7e8449a847e3cf1054f2c07b51237699fad3 5.3.2
210 26ce8e7515036d3431a03aaeb7bc72dd96cb1112 5.4rc0
@@ -1,1266 +1,1278 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 WINDOWS_BASE_IMAGE_NAME = 'Windows_Server-2019-English-Full-Base-2019.11.13'
62 WINDOWS_BASE_IMAGE_NAME = 'Windows_Server-2019-English-Full-Base-*'
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 194 # Avoid long usernames in the temp directory path because the '~' causes extra quoting in ssh output
195 195 [System.Environment]::SetEnvironmentVariable('TMP', 'C:\Temp', [System.EnvironmentVariableTarget]::User)
196 196 [System.Environment]::SetEnvironmentVariable('TEMP', 'C:\Temp', [System.EnvironmentVariableTarget]::User)
197 197
198 198 # Configure and restart the WinRM Service; Enable the required firewall exception
199 199 Stop-Service -Name WinRM
200 200 Set-Service -Name WinRM -StartupType Automatic
201 201 netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new action=allow localip=any remoteip=any
202 202 Start-Service -Name WinRM
203 203
204 204 # Disable firewall on private network interfaces so prompts don't appear.
205 205 Set-NetFirewallProfile -Name private -Enabled false
206 206 </powershell>
207 207 '''.lstrip()
208 208
209 209
210 210 WINDOWS_BOOTSTRAP_POWERSHELL = '''
211 211 Write-Output "installing PowerShell dependencies"
212 212 Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
213 213 Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
214 214 Install-Module -Name OpenSSHUtils -RequiredVersion 0.0.2.0
215 215
216 216 Write-Output "installing OpenSSL server"
217 217 Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
218 218 # Various tools will attempt to use older versions of .NET. So we enable
219 219 # the feature that provides them so it doesn't have to be auto-enabled
220 220 # later.
221 221 Write-Output "enabling .NET Framework feature"
222 222 Install-WindowsFeature -Name Net-Framework-Core
223 223 '''
224 224
225 225
226 226 class AWSConnection:
227 227 """Manages the state of a connection with AWS."""
228 228
229 229 def __init__(self, automation, region: str, ensure_ec2_state: bool = True):
230 230 self.automation = automation
231 231 self.local_state_path = automation.state_path
232 232
233 233 self.prefix = 'hg-'
234 234
235 235 self.session = boto3.session.Session(region_name=region)
236 236 self.ec2client = self.session.client('ec2')
237 237 self.ec2resource = self.session.resource('ec2')
238 238 self.iamclient = self.session.client('iam')
239 239 self.iamresource = self.session.resource('iam')
240 240 self.security_groups = {}
241 241
242 242 if ensure_ec2_state:
243 243 ensure_key_pairs(automation.state_path, self.ec2resource)
244 244 self.security_groups = ensure_security_groups(self.ec2resource)
245 245 ensure_iam_state(self.iamclient, self.iamresource)
246 246
247 247 def key_pair_path_private(self, name):
248 248 """Path to a key pair private key file."""
249 249 return self.local_state_path / 'keys' / ('keypair-%s' % name)
250 250
251 251 def key_pair_path_public(self, name):
252 252 return self.local_state_path / 'keys' / ('keypair-%s.pub' % name)
253 253
254 254
255 255 def rsa_key_fingerprint(p: pathlib.Path):
256 256 """Compute the fingerprint of an RSA private key."""
257 257
258 258 # TODO use rsa package.
259 259 res = subprocess.run(
260 260 [
261 261 'openssl',
262 262 'pkcs8',
263 263 '-in',
264 264 str(p),
265 265 '-nocrypt',
266 266 '-topk8',
267 267 '-outform',
268 268 'DER',
269 269 ],
270 270 capture_output=True,
271 271 check=True,
272 272 )
273 273
274 274 sha1 = hashlib.sha1(res.stdout).hexdigest()
275 275 return ':'.join(a + b for a, b in zip(sha1[::2], sha1[1::2]))
276 276
277 277
278 278 def ensure_key_pairs(state_path: pathlib.Path, ec2resource, prefix='hg-'):
279 279 remote_existing = {}
280 280
281 281 for kpi in ec2resource.key_pairs.all():
282 282 if kpi.name.startswith(prefix):
283 283 remote_existing[kpi.name[len(prefix) :]] = kpi.key_fingerprint
284 284
285 285 # Validate that we have these keys locally.
286 286 key_path = state_path / 'keys'
287 287 key_path.mkdir(exist_ok=True, mode=0o700)
288 288
289 289 def remove_remote(name):
290 290 print('deleting key pair %s' % name)
291 291 key = ec2resource.KeyPair(name)
292 292 key.delete()
293 293
294 294 def remove_local(name):
295 295 pub_full = key_path / ('keypair-%s.pub' % name)
296 296 priv_full = key_path / ('keypair-%s' % name)
297 297
298 298 print('removing %s' % pub_full)
299 299 pub_full.unlink()
300 300 print('removing %s' % priv_full)
301 301 priv_full.unlink()
302 302
303 303 local_existing = {}
304 304
305 305 for f in sorted(os.listdir(key_path)):
306 306 if not f.startswith('keypair-') or not f.endswith('.pub'):
307 307 continue
308 308
309 309 name = f[len('keypair-') : -len('.pub')]
310 310
311 311 pub_full = key_path / f
312 312 priv_full = key_path / ('keypair-%s' % name)
313 313
314 314 with open(pub_full, 'r', encoding='ascii') as fh:
315 315 data = fh.read()
316 316
317 317 if not data.startswith('ssh-rsa '):
318 318 print(
319 319 'unexpected format for key pair file: %s; removing' % pub_full
320 320 )
321 321 pub_full.unlink()
322 322 priv_full.unlink()
323 323 continue
324 324
325 325 local_existing[name] = rsa_key_fingerprint(priv_full)
326 326
327 327 for name in sorted(set(remote_existing) | set(local_existing)):
328 328 if name not in local_existing:
329 329 actual = '%s%s' % (prefix, name)
330 330 print('remote key %s does not exist locally' % name)
331 331 remove_remote(actual)
332 332 del remote_existing[name]
333 333
334 334 elif name not in remote_existing:
335 335 print('local key %s does not exist remotely' % name)
336 336 remove_local(name)
337 337 del local_existing[name]
338 338
339 339 elif remote_existing[name] != local_existing[name]:
340 340 print(
341 341 'key fingerprint mismatch for %s; '
342 342 'removing from local and remote' % name
343 343 )
344 344 remove_local(name)
345 345 remove_remote('%s%s' % (prefix, name))
346 346 del local_existing[name]
347 347 del remote_existing[name]
348 348
349 349 missing = KEY_PAIRS - set(remote_existing)
350 350
351 351 for name in sorted(missing):
352 352 actual = '%s%s' % (prefix, name)
353 353 print('creating key pair %s' % actual)
354 354
355 355 priv_full = key_path / ('keypair-%s' % name)
356 356 pub_full = key_path / ('keypair-%s.pub' % name)
357 357
358 358 kp = ec2resource.create_key_pair(KeyName=actual)
359 359
360 360 with priv_full.open('w', encoding='ascii') as fh:
361 361 fh.write(kp.key_material)
362 362 fh.write('\n')
363 363
364 364 priv_full.chmod(0o0600)
365 365
366 366 # SSH public key can be extracted via `ssh-keygen`.
367 367 with pub_full.open('w', encoding='ascii') as fh:
368 368 subprocess.run(
369 369 ['ssh-keygen', '-y', '-f', str(priv_full)],
370 370 stdout=fh,
371 371 check=True,
372 372 )
373 373
374 374 pub_full.chmod(0o0600)
375 375
376 376
377 377 def delete_instance_profile(profile):
378 378 for role in profile.roles:
379 379 print(
380 380 'removing role %s from instance profile %s'
381 381 % (role.name, profile.name)
382 382 )
383 383 profile.remove_role(RoleName=role.name)
384 384
385 385 print('deleting instance profile %s' % profile.name)
386 386 profile.delete()
387 387
388 388
389 389 def ensure_iam_state(iamclient, iamresource, prefix='hg-'):
390 390 """Ensure IAM state is in sync with our canonical definition."""
391 391
392 392 remote_profiles = {}
393 393
394 394 for profile in iamresource.instance_profiles.all():
395 395 if profile.name.startswith(prefix):
396 396 remote_profiles[profile.name[len(prefix) :]] = profile
397 397
398 398 for name in sorted(set(remote_profiles) - set(IAM_INSTANCE_PROFILES)):
399 399 delete_instance_profile(remote_profiles[name])
400 400 del remote_profiles[name]
401 401
402 402 remote_roles = {}
403 403
404 404 for role in iamresource.roles.all():
405 405 if role.name.startswith(prefix):
406 406 remote_roles[role.name[len(prefix) :]] = role
407 407
408 408 for name in sorted(set(remote_roles) - set(IAM_ROLES)):
409 409 role = remote_roles[name]
410 410
411 411 print('removing role %s' % role.name)
412 412 role.delete()
413 413 del remote_roles[name]
414 414
415 415 # We've purged remote state that doesn't belong. Create missing
416 416 # instance profiles and roles.
417 417 for name in sorted(set(IAM_INSTANCE_PROFILES) - set(remote_profiles)):
418 418 actual = '%s%s' % (prefix, name)
419 419 print('creating IAM instance profile %s' % actual)
420 420
421 421 profile = iamresource.create_instance_profile(
422 422 InstanceProfileName=actual
423 423 )
424 424 remote_profiles[name] = profile
425 425
426 426 waiter = iamclient.get_waiter('instance_profile_exists')
427 427 waiter.wait(InstanceProfileName=actual)
428 428 print('IAM instance profile %s is available' % actual)
429 429
430 430 for name in sorted(set(IAM_ROLES) - set(remote_roles)):
431 431 entry = IAM_ROLES[name]
432 432
433 433 actual = '%s%s' % (prefix, name)
434 434 print('creating IAM role %s' % actual)
435 435
436 436 role = iamresource.create_role(
437 437 RoleName=actual,
438 438 Description=entry['description'],
439 439 AssumeRolePolicyDocument=ASSUME_ROLE_POLICY_DOCUMENT,
440 440 )
441 441
442 442 waiter = iamclient.get_waiter('role_exists')
443 443 waiter.wait(RoleName=actual)
444 444 print('IAM role %s is available' % actual)
445 445
446 446 remote_roles[name] = role
447 447
448 448 for arn in entry['policy_arns']:
449 449 print('attaching policy %s to %s' % (arn, role.name))
450 450 role.attach_policy(PolicyArn=arn)
451 451
452 452 # Now reconcile state of profiles.
453 453 for name, meta in sorted(IAM_INSTANCE_PROFILES.items()):
454 454 profile = remote_profiles[name]
455 455 wanted = {'%s%s' % (prefix, role) for role in meta['roles']}
456 456 have = {role.name for role in profile.roles}
457 457
458 458 for role in sorted(have - wanted):
459 459 print('removing role %s from %s' % (role, profile.name))
460 460 profile.remove_role(RoleName=role)
461 461
462 462 for role in sorted(wanted - have):
463 463 print('adding role %s to %s' % (role, profile.name))
464 464 profile.add_role(RoleName=role)
465 465
466 466
467 def find_image(ec2resource, owner_id, name):
467 def find_image(ec2resource, owner_id, name, reverse_sort_field=None):
468 468 """Find an AMI by its owner ID and name."""
469 469
470 470 images = ec2resource.images.filter(
471 471 Filters=[
472 472 {'Name': 'owner-id', 'Values': [owner_id],},
473 473 {'Name': 'state', 'Values': ['available'],},
474 474 {'Name': 'image-type', 'Values': ['machine'],},
475 475 {'Name': 'name', 'Values': [name],},
476 476 ]
477 477 )
478 478
479 if reverse_sort_field:
480 images = sorted(
481 images,
482 key=lambda image: getattr(image, reverse_sort_field),
483 reverse=True,
484 )
485
479 486 for image in images:
480 487 return image
481 488
482 489 raise Exception('unable to find image for %s' % name)
483 490
484 491
485 492 def ensure_security_groups(ec2resource, prefix='hg-'):
486 493 """Ensure all necessary Mercurial security groups are present.
487 494
488 495 All security groups are prefixed with ``hg-`` by default. Any security
489 496 groups having this prefix but aren't in our list are deleted.
490 497 """
491 498 existing = {}
492 499
493 500 for group in ec2resource.security_groups.all():
494 501 if group.group_name.startswith(prefix):
495 502 existing[group.group_name[len(prefix) :]] = group
496 503
497 504 purge = set(existing) - set(SECURITY_GROUPS)
498 505
499 506 for name in sorted(purge):
500 507 group = existing[name]
501 508 print('removing legacy security group: %s' % group.group_name)
502 509 group.delete()
503 510
504 511 security_groups = {}
505 512
506 513 for name, group in sorted(SECURITY_GROUPS.items()):
507 514 if name in existing:
508 515 security_groups[name] = existing[name]
509 516 continue
510 517
511 518 actual = '%s%s' % (prefix, name)
512 519 print('adding security group %s' % actual)
513 520
514 521 group_res = ec2resource.create_security_group(
515 522 Description=group['description'], GroupName=actual,
516 523 )
517 524
518 525 group_res.authorize_ingress(IpPermissions=group['ingress'],)
519 526
520 527 security_groups[name] = group_res
521 528
522 529 return security_groups
523 530
524 531
525 532 def terminate_ec2_instances(ec2resource, prefix='hg-'):
526 533 """Terminate all EC2 instances managed by us."""
527 534 waiting = []
528 535
529 536 for instance in ec2resource.instances.all():
530 537 if instance.state['Name'] == 'terminated':
531 538 continue
532 539
533 540 for tag in instance.tags or []:
534 541 if tag['Key'] == 'Name' and tag['Value'].startswith(prefix):
535 542 print('terminating %s' % instance.id)
536 543 instance.terminate()
537 544 waiting.append(instance)
538 545
539 546 for instance in waiting:
540 547 instance.wait_until_terminated()
541 548
542 549
543 550 def remove_resources(c, prefix='hg-'):
544 551 """Purge all of our resources in this EC2 region."""
545 552 ec2resource = c.ec2resource
546 553 iamresource = c.iamresource
547 554
548 555 terminate_ec2_instances(ec2resource, prefix=prefix)
549 556
550 557 for image in ec2resource.images.filter(Owners=['self']):
551 558 if image.name.startswith(prefix):
552 559 remove_ami(ec2resource, image)
553 560
554 561 for group in ec2resource.security_groups.all():
555 562 if group.group_name.startswith(prefix):
556 563 print('removing security group %s' % group.group_name)
557 564 group.delete()
558 565
559 566 for profile in iamresource.instance_profiles.all():
560 567 if profile.name.startswith(prefix):
561 568 delete_instance_profile(profile)
562 569
563 570 for role in iamresource.roles.all():
564 571 if role.name.startswith(prefix):
565 572 for p in role.attached_policies.all():
566 573 print('detaching policy %s from %s' % (p.arn, role.name))
567 574 role.detach_policy(PolicyArn=p.arn)
568 575
569 576 print('removing role %s' % role.name)
570 577 role.delete()
571 578
572 579
573 580 def wait_for_ip_addresses(instances):
574 581 """Wait for the public IP addresses of an iterable of instances."""
575 582 for instance in instances:
576 583 while True:
577 584 if not instance.public_ip_address:
578 585 time.sleep(2)
579 586 instance.reload()
580 587 continue
581 588
582 589 print(
583 590 'public IP address for %s: %s'
584 591 % (instance.id, instance.public_ip_address)
585 592 )
586 593 break
587 594
588 595
589 596 def remove_ami(ec2resource, image):
590 597 """Remove an AMI and its underlying snapshots."""
591 598 snapshots = []
592 599
593 600 for device in image.block_device_mappings:
594 601 if 'Ebs' in device:
595 602 snapshots.append(ec2resource.Snapshot(device['Ebs']['SnapshotId']))
596 603
597 604 print('deregistering %s' % image.id)
598 605 image.deregister()
599 606
600 607 for snapshot in snapshots:
601 608 print('deleting snapshot %s' % snapshot.id)
602 609 snapshot.delete()
603 610
604 611
605 612 def wait_for_ssm(ssmclient, instances):
606 613 """Wait for SSM to come online for an iterable of instance IDs."""
607 614 while True:
608 615 res = ssmclient.describe_instance_information(
609 616 Filters=[
610 617 {'Key': 'InstanceIds', 'Values': [i.id for i in instances],},
611 618 ],
612 619 )
613 620
614 621 available = len(res['InstanceInformationList'])
615 622 wanted = len(instances)
616 623
617 624 print('%d/%d instances available in SSM' % (available, wanted))
618 625
619 626 if available == wanted:
620 627 return
621 628
622 629 time.sleep(2)
623 630
624 631
625 632 def run_ssm_command(ssmclient, instances, document_name, parameters):
626 633 """Run a PowerShell script on an EC2 instance."""
627 634
628 635 res = ssmclient.send_command(
629 636 InstanceIds=[i.id for i in instances],
630 637 DocumentName=document_name,
631 638 Parameters=parameters,
632 639 CloudWatchOutputConfig={'CloudWatchOutputEnabled': True,},
633 640 )
634 641
635 642 command_id = res['Command']['CommandId']
636 643
637 644 for instance in instances:
638 645 while True:
639 646 try:
640 647 res = ssmclient.get_command_invocation(
641 648 CommandId=command_id, InstanceId=instance.id,
642 649 )
643 650 except botocore.exceptions.ClientError as e:
644 651 if e.response['Error']['Code'] == 'InvocationDoesNotExist':
645 652 print('could not find SSM command invocation; waiting')
646 653 time.sleep(1)
647 654 continue
648 655 else:
649 656 raise
650 657
651 658 if res['Status'] == 'Success':
652 659 break
653 660 elif res['Status'] in ('Pending', 'InProgress', 'Delayed'):
654 661 time.sleep(2)
655 662 else:
656 663 raise Exception(
657 664 'command failed on %s: %s' % (instance.id, res['Status'])
658 665 )
659 666
660 667
661 668 @contextlib.contextmanager
662 669 def temporary_ec2_instances(ec2resource, config):
663 670 """Create temporary EC2 instances.
664 671
665 672 This is a proxy to ``ec2client.run_instances(**config)`` that takes care of
666 673 managing the lifecycle of the instances.
667 674
668 675 When the context manager exits, the instances are terminated.
669 676
670 677 The context manager evaluates to the list of data structures
671 678 describing each created instance. The instances may not be available
672 679 for work immediately: it is up to the caller to wait for the instance
673 680 to start responding.
674 681 """
675 682
676 683 ids = None
677 684
678 685 try:
679 686 res = ec2resource.create_instances(**config)
680 687
681 688 ids = [i.id for i in res]
682 689 print('started instances: %s' % ' '.join(ids))
683 690
684 691 yield res
685 692 finally:
686 693 if ids:
687 694 print('terminating instances: %s' % ' '.join(ids))
688 695 for instance in res:
689 696 instance.terminate()
690 697 print('terminated %d instances' % len(ids))
691 698
692 699
693 700 @contextlib.contextmanager
694 701 def create_temp_windows_ec2_instances(
695 702 c: AWSConnection, config, bootstrap: bool = False
696 703 ):
697 704 """Create temporary Windows EC2 instances.
698 705
699 706 This is a higher-level wrapper around ``create_temp_ec2_instances()`` that
700 707 configures the Windows instance for Windows Remote Management. The emitted
701 708 instances will have a ``winrm_client`` attribute containing a
702 709 ``pypsrp.client.Client`` instance bound to the instance.
703 710 """
704 711 if 'IamInstanceProfile' in config:
705 712 raise ValueError('IamInstanceProfile cannot be provided in config')
706 713 if 'UserData' in config:
707 714 raise ValueError('UserData cannot be provided in config')
708 715
709 716 password = c.automation.default_password()
710 717
711 718 config = copy.deepcopy(config)
712 719 config['IamInstanceProfile'] = {
713 720 'Name': 'hg-ephemeral-ec2-1',
714 721 }
715 722 config.setdefault('TagSpecifications', []).append(
716 723 {
717 724 'ResourceType': 'instance',
718 725 'Tags': [{'Key': 'Name', 'Value': 'hg-temp-windows'}],
719 726 }
720 727 )
721 728
722 729 if bootstrap:
723 730 config['UserData'] = WINDOWS_USER_DATA % password
724 731
725 732 with temporary_ec2_instances(c.ec2resource, config) as instances:
726 733 wait_for_ip_addresses(instances)
727 734
728 735 print('waiting for Windows Remote Management service...')
729 736
730 737 for instance in instances:
731 738 client = wait_for_winrm(
732 739 instance.public_ip_address, 'Administrator', password
733 740 )
734 741 print('established WinRM connection to %s' % instance.id)
735 742 instance.winrm_client = client
736 743
737 744 yield instances
738 745
739 746
740 747 def resolve_fingerprint(fingerprint):
741 748 fingerprint = json.dumps(fingerprint, sort_keys=True)
742 749 return hashlib.sha256(fingerprint.encode('utf-8')).hexdigest()
743 750
744 751
745 752 def find_and_reconcile_image(ec2resource, name, fingerprint):
746 753 """Attempt to find an existing EC2 AMI with a name and fingerprint.
747 754
748 755 If an image with the specified fingerprint is found, it is returned.
749 756 Otherwise None is returned.
750 757
751 758 Existing images for the specified name that don't have the specified
752 759 fingerprint or are missing required metadata or deleted.
753 760 """
754 761 # Find existing AMIs with this name and delete the ones that are invalid.
755 762 # Store a reference to a good image so it can be returned one the
756 763 # image state is reconciled.
757 764 images = ec2resource.images.filter(
758 765 Filters=[{'Name': 'name', 'Values': [name]}]
759 766 )
760 767
761 768 existing_image = None
762 769
763 770 for image in images:
764 771 if image.tags is None:
765 772 print(
766 773 'image %s for %s lacks required tags; removing'
767 774 % (image.id, image.name)
768 775 )
769 776 remove_ami(ec2resource, image)
770 777 else:
771 778 tags = {t['Key']: t['Value'] for t in image.tags}
772 779
773 780 if tags.get('HGIMAGEFINGERPRINT') == fingerprint:
774 781 existing_image = image
775 782 else:
776 783 print(
777 784 'image %s for %s has wrong fingerprint; removing'
778 785 % (image.id, image.name)
779 786 )
780 787 remove_ami(ec2resource, image)
781 788
782 789 return existing_image
783 790
784 791
785 792 def create_ami_from_instance(
786 793 ec2client, instance, name, description, fingerprint
787 794 ):
788 795 """Create an AMI from a running instance.
789 796
790 797 Returns the ``ec2resource.Image`` representing the created AMI.
791 798 """
792 799 instance.stop()
793 800
794 801 ec2client.get_waiter('instance_stopped').wait(
795 802 InstanceIds=[instance.id], WaiterConfig={'Delay': 5,}
796 803 )
797 804 print('%s is stopped' % instance.id)
798 805
799 806 image = instance.create_image(Name=name, Description=description,)
800 807
801 808 image.create_tags(
802 809 Tags=[{'Key': 'HGIMAGEFINGERPRINT', 'Value': fingerprint,},]
803 810 )
804 811
805 812 print('waiting for image %s' % image.id)
806 813
807 814 ec2client.get_waiter('image_available').wait(ImageIds=[image.id],)
808 815
809 816 print('image %s available as %s' % (image.id, image.name))
810 817
811 818 return image
812 819
813 820
814 821 def ensure_linux_dev_ami(c: AWSConnection, distro='debian10', prefix='hg-'):
815 822 """Ensures a Linux development AMI is available and up-to-date.
816 823
817 824 Returns an ``ec2.Image`` of either an existing AMI or a newly-built one.
818 825 """
819 826 ec2client = c.ec2client
820 827 ec2resource = c.ec2resource
821 828
822 829 name = '%s%s-%s' % (prefix, 'linux-dev', distro)
823 830
824 831 if distro == 'debian9':
825 832 image = find_image(
826 833 ec2resource,
827 834 DEBIAN_ACCOUNT_ID,
828 835 'debian-stretch-hvm-x86_64-gp2-2019-09-08-17994',
829 836 )
830 837 ssh_username = 'admin'
831 838 elif distro == 'debian10':
832 839 image = find_image(
833 840 ec2resource, DEBIAN_ACCOUNT_ID_2, 'debian-10-amd64-20190909-10',
834 841 )
835 842 ssh_username = 'admin'
836 843 elif distro == 'ubuntu18.04':
837 844 image = find_image(
838 845 ec2resource,
839 846 UBUNTU_ACCOUNT_ID,
840 847 'ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-20190918',
841 848 )
842 849 ssh_username = 'ubuntu'
843 850 elif distro == 'ubuntu19.04':
844 851 image = find_image(
845 852 ec2resource,
846 853 UBUNTU_ACCOUNT_ID,
847 854 'ubuntu/images/hvm-ssd/ubuntu-disco-19.04-amd64-server-20190918',
848 855 )
849 856 ssh_username = 'ubuntu'
850 857 else:
851 858 raise ValueError('unsupported Linux distro: %s' % distro)
852 859
853 860 config = {
854 861 'BlockDeviceMappings': [
855 862 {
856 863 'DeviceName': image.block_device_mappings[0]['DeviceName'],
857 864 'Ebs': {
858 865 'DeleteOnTermination': True,
859 866 'VolumeSize': 10,
860 867 'VolumeType': 'gp2',
861 868 },
862 869 },
863 870 ],
864 871 'EbsOptimized': True,
865 872 'ImageId': image.id,
866 873 'InstanceInitiatedShutdownBehavior': 'stop',
867 874 # 8 VCPUs for compiling Python.
868 875 'InstanceType': 't3.2xlarge',
869 876 'KeyName': '%sautomation' % prefix,
870 877 'MaxCount': 1,
871 878 'MinCount': 1,
872 879 'SecurityGroupIds': [c.security_groups['linux-dev-1'].id],
873 880 }
874 881
875 882 requirements2_path = (
876 883 pathlib.Path(__file__).parent.parent / 'linux-requirements-py2.txt'
877 884 )
878 885 requirements3_path = (
879 886 pathlib.Path(__file__).parent.parent / 'linux-requirements-py3.txt'
880 887 )
881 888 with requirements2_path.open('r', encoding='utf-8') as fh:
882 889 requirements2 = fh.read()
883 890 with requirements3_path.open('r', encoding='utf-8') as fh:
884 891 requirements3 = fh.read()
885 892
886 893 # Compute a deterministic fingerprint to determine whether image needs to
887 894 # be regenerated.
888 895 fingerprint = resolve_fingerprint(
889 896 {
890 897 'instance_config': config,
891 898 'bootstrap_script': BOOTSTRAP_DEBIAN,
892 899 'requirements_py2': requirements2,
893 900 'requirements_py3': requirements3,
894 901 }
895 902 )
896 903
897 904 existing_image = find_and_reconcile_image(ec2resource, name, fingerprint)
898 905
899 906 if existing_image:
900 907 return existing_image
901 908
902 909 print('no suitable %s image found; creating one...' % name)
903 910
904 911 with temporary_ec2_instances(ec2resource, config) as instances:
905 912 wait_for_ip_addresses(instances)
906 913
907 914 instance = instances[0]
908 915
909 916 client = wait_for_ssh(
910 917 instance.public_ip_address,
911 918 22,
912 919 username=ssh_username,
913 920 key_filename=str(c.key_pair_path_private('automation')),
914 921 )
915 922
916 923 home = '/home/%s' % ssh_username
917 924
918 925 with client:
919 926 print('connecting to SSH server')
920 927 sftp = client.open_sftp()
921 928
922 929 print('uploading bootstrap files')
923 930 with sftp.open('%s/bootstrap' % home, 'wb') as fh:
924 931 fh.write(BOOTSTRAP_DEBIAN)
925 932 fh.chmod(0o0700)
926 933
927 934 with sftp.open('%s/requirements-py2.txt' % home, 'wb') as fh:
928 935 fh.write(requirements2)
929 936 fh.chmod(0o0700)
930 937
931 938 with sftp.open('%s/requirements-py3.txt' % home, 'wb') as fh:
932 939 fh.write(requirements3)
933 940 fh.chmod(0o0700)
934 941
935 942 print('executing bootstrap')
936 943 chan, stdin, stdout = ssh_exec_command(
937 944 client, '%s/bootstrap' % home
938 945 )
939 946 stdin.close()
940 947
941 948 for line in stdout:
942 949 print(line, end='')
943 950
944 951 res = chan.recv_exit_status()
945 952 if res:
946 953 raise Exception('non-0 exit from bootstrap: %d' % res)
947 954
948 955 print(
949 956 'bootstrap completed; stopping %s to create %s'
950 957 % (instance.id, name)
951 958 )
952 959
953 960 return create_ami_from_instance(
954 961 ec2client,
955 962 instance,
956 963 name,
957 964 'Mercurial Linux development environment',
958 965 fingerprint,
959 966 )
960 967
961 968
962 969 @contextlib.contextmanager
963 970 def temporary_linux_dev_instances(
964 971 c: AWSConnection,
965 972 image,
966 973 instance_type,
967 974 prefix='hg-',
968 975 ensure_extra_volume=False,
969 976 ):
970 977 """Create temporary Linux development EC2 instances.
971 978
972 979 Context manager resolves to a list of ``ec2.Instance`` that were created
973 980 and are running.
974 981
975 982 ``ensure_extra_volume`` can be set to ``True`` to require that instances
976 983 have a 2nd storage volume available other than the primary AMI volume.
977 984 For instance types with instance storage, this does nothing special.
978 985 But for instance types without instance storage, an additional EBS volume
979 986 will be added to the instance.
980 987
981 988 Instances have an ``ssh_client`` attribute containing a paramiko SSHClient
982 989 instance bound to the instance.
983 990
984 991 Instances have an ``ssh_private_key_path`` attributing containing the
985 992 str path to the SSH private key to connect to the instance.
986 993 """
987 994
988 995 block_device_mappings = [
989 996 {
990 997 'DeviceName': image.block_device_mappings[0]['DeviceName'],
991 998 'Ebs': {
992 999 'DeleteOnTermination': True,
993 1000 'VolumeSize': 12,
994 1001 'VolumeType': 'gp2',
995 1002 },
996 1003 }
997 1004 ]
998 1005
999 1006 # This is not an exhaustive list of instance types having instance storage.
1000 1007 # But
1001 1008 if ensure_extra_volume and not instance_type.startswith(
1002 1009 tuple(INSTANCE_TYPES_WITH_STORAGE)
1003 1010 ):
1004 1011 main_device = block_device_mappings[0]['DeviceName']
1005 1012
1006 1013 if main_device == 'xvda':
1007 1014 second_device = 'xvdb'
1008 1015 elif main_device == '/dev/sda1':
1009 1016 second_device = '/dev/sdb'
1010 1017 else:
1011 1018 raise ValueError(
1012 1019 'unhandled primary EBS device name: %s' % main_device
1013 1020 )
1014 1021
1015 1022 block_device_mappings.append(
1016 1023 {
1017 1024 'DeviceName': second_device,
1018 1025 'Ebs': {
1019 1026 'DeleteOnTermination': True,
1020 1027 'VolumeSize': 8,
1021 1028 'VolumeType': 'gp2',
1022 1029 },
1023 1030 }
1024 1031 )
1025 1032
1026 1033 config = {
1027 1034 'BlockDeviceMappings': block_device_mappings,
1028 1035 'EbsOptimized': True,
1029 1036 'ImageId': image.id,
1030 1037 'InstanceInitiatedShutdownBehavior': 'terminate',
1031 1038 'InstanceType': instance_type,
1032 1039 'KeyName': '%sautomation' % prefix,
1033 1040 'MaxCount': 1,
1034 1041 'MinCount': 1,
1035 1042 'SecurityGroupIds': [c.security_groups['linux-dev-1'].id],
1036 1043 }
1037 1044
1038 1045 with temporary_ec2_instances(c.ec2resource, config) as instances:
1039 1046 wait_for_ip_addresses(instances)
1040 1047
1041 1048 ssh_private_key_path = str(c.key_pair_path_private('automation'))
1042 1049
1043 1050 for instance in instances:
1044 1051 client = wait_for_ssh(
1045 1052 instance.public_ip_address,
1046 1053 22,
1047 1054 username='hg',
1048 1055 key_filename=ssh_private_key_path,
1049 1056 )
1050 1057
1051 1058 instance.ssh_client = client
1052 1059 instance.ssh_private_key_path = ssh_private_key_path
1053 1060
1054 1061 try:
1055 1062 yield instances
1056 1063 finally:
1057 1064 for instance in instances:
1058 1065 instance.ssh_client.close()
1059 1066
1060 1067
1061 1068 def ensure_windows_dev_ami(
1062 c: AWSConnection, prefix='hg-', base_image_name=WINDOWS_BASE_IMAGE_NAME
1069 c: AWSConnection, prefix='hg-', base_image_name=WINDOWS_BASE_IMAGE_NAME,
1063 1070 ):
1064 1071 """Ensure Windows Development AMI is available and up-to-date.
1065 1072
1066 1073 If necessary, a modern AMI will be built by starting a temporary EC2
1067 1074 instance and bootstrapping it.
1068 1075
1069 1076 Obsolete AMIs will be deleted so there is only a single AMI having the
1070 1077 desired name.
1071 1078
1072 1079 Returns an ``ec2.Image`` of either an existing AMI or a newly-built
1073 1080 one.
1074 1081 """
1075 1082 ec2client = c.ec2client
1076 1083 ec2resource = c.ec2resource
1077 1084 ssmclient = c.session.client('ssm')
1078 1085
1079 1086 name = '%s%s' % (prefix, 'windows-dev')
1080 1087
1081 image = find_image(ec2resource, AMAZON_ACCOUNT_ID, base_image_name)
1088 image = find_image(
1089 ec2resource,
1090 AMAZON_ACCOUNT_ID,
1091 base_image_name,
1092 reverse_sort_field="name",
1093 )
1082 1094
1083 1095 config = {
1084 1096 'BlockDeviceMappings': [
1085 1097 {
1086 1098 'DeviceName': '/dev/sda1',
1087 1099 'Ebs': {
1088 1100 'DeleteOnTermination': True,
1089 1101 'VolumeSize': 32,
1090 1102 'VolumeType': 'gp2',
1091 1103 },
1092 1104 }
1093 1105 ],
1094 1106 'ImageId': image.id,
1095 1107 'InstanceInitiatedShutdownBehavior': 'stop',
1096 1108 'InstanceType': 't3.medium',
1097 1109 'KeyName': '%sautomation' % prefix,
1098 1110 'MaxCount': 1,
1099 1111 'MinCount': 1,
1100 1112 'SecurityGroupIds': [c.security_groups['windows-dev-1'].id],
1101 1113 }
1102 1114
1103 1115 commands = [
1104 1116 # Need to start the service so sshd_config is generated.
1105 1117 'Start-Service sshd',
1106 1118 'Write-Output "modifying sshd_config"',
1107 1119 r'$content = Get-Content C:\ProgramData\ssh\sshd_config',
1108 1120 '$content = $content -replace "Match Group administrators","" -replace "AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys",""',
1109 1121 r'$content | Set-Content C:\ProgramData\ssh\sshd_config',
1110 1122 'Import-Module OpenSSHUtils',
1111 1123 r'Repair-SshdConfigPermission C:\ProgramData\ssh\sshd_config -Confirm:$false',
1112 1124 'Restart-Service sshd',
1113 1125 'Write-Output "installing OpenSSL client"',
1114 1126 'Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0',
1115 1127 'Set-Service -Name sshd -StartupType "Automatic"',
1116 1128 'Write-Output "OpenSSH server running"',
1117 1129 ]
1118 1130
1119 1131 with INSTALL_WINDOWS_DEPENDENCIES.open('r', encoding='utf-8') as fh:
1120 1132 commands.extend(l.rstrip() for l in fh)
1121 1133
1122 1134 # Schedule run of EC2Launch on next boot. This ensures that UserData
1123 1135 # is executed.
1124 1136 # We disable setComputerName because it forces a reboot.
1125 1137 # We set an explicit admin password because this causes UserData to run
1126 1138 # as Administrator instead of System.
1127 1139 commands.extend(
1128 1140 [
1129 1141 r'''Set-Content -Path C:\ProgramData\Amazon\EC2-Windows\Launch\Config\LaunchConfig.json '''
1130 1142 r'''-Value '{"setComputerName": false, "setWallpaper": true, "addDnsSuffixList": true, '''
1131 1143 r'''"extendBootVolumeSize": true, "handleUserData": true, '''
1132 1144 r'''"adminPasswordType": "Specify", "adminPassword": "%s"}' '''
1133 1145 % c.automation.default_password(),
1134 1146 r'C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 '
1135 1147 r'–Schedule',
1136 1148 ]
1137 1149 )
1138 1150
1139 1151 # Disable Windows Defender when bootstrapping because it just slows
1140 1152 # things down.
1141 1153 commands.insert(0, 'Set-MpPreference -DisableRealtimeMonitoring $true')
1142 1154 commands.append('Set-MpPreference -DisableRealtimeMonitoring $false')
1143 1155
1144 1156 # Compute a deterministic fingerprint to determine whether image needs
1145 1157 # to be regenerated.
1146 1158 fingerprint = resolve_fingerprint(
1147 1159 {
1148 1160 'instance_config': config,
1149 1161 'user_data': WINDOWS_USER_DATA,
1150 1162 'initial_bootstrap': WINDOWS_BOOTSTRAP_POWERSHELL,
1151 1163 'bootstrap_commands': commands,
1152 1164 'base_image_name': base_image_name,
1153 1165 }
1154 1166 )
1155 1167
1156 1168 existing_image = find_and_reconcile_image(ec2resource, name, fingerprint)
1157 1169
1158 1170 if existing_image:
1159 1171 return existing_image
1160 1172
1161 1173 print('no suitable Windows development image found; creating one...')
1162 1174
1163 1175 with create_temp_windows_ec2_instances(
1164 1176 c, config, bootstrap=True
1165 1177 ) as instances:
1166 1178 assert len(instances) == 1
1167 1179 instance = instances[0]
1168 1180
1169 1181 wait_for_ssm(ssmclient, [instance])
1170 1182
1171 1183 # On first boot, install various Windows updates.
1172 1184 # We would ideally use PowerShell Remoting for this. However, there are
1173 1185 # trust issues that make it difficult to invoke Windows Update
1174 1186 # remotely. So we use SSM, which has a mechanism for running Windows
1175 1187 # Update.
1176 1188 print('installing Windows features...')
1177 1189 run_ssm_command(
1178 1190 ssmclient,
1179 1191 [instance],
1180 1192 'AWS-RunPowerShellScript',
1181 1193 {'commands': WINDOWS_BOOTSTRAP_POWERSHELL.split('\n'),},
1182 1194 )
1183 1195
1184 1196 # Reboot so all updates are fully applied.
1185 1197 #
1186 1198 # We don't use instance.reboot() here because it is asynchronous and
1187 1199 # we don't know when exactly the instance has rebooted. It could take
1188 1200 # a while to stop and we may start trying to interact with the instance
1189 1201 # before it has rebooted.
1190 1202 print('rebooting instance %s' % instance.id)
1191 1203 instance.stop()
1192 1204 ec2client.get_waiter('instance_stopped').wait(
1193 1205 InstanceIds=[instance.id], WaiterConfig={'Delay': 5,}
1194 1206 )
1195 1207
1196 1208 instance.start()
1197 1209 wait_for_ip_addresses([instance])
1198 1210
1199 1211 # There is a race condition here between the User Data PS script running
1200 1212 # and us connecting to WinRM. This can manifest as
1201 1213 # "AuthorizationManager check failed" failures during run_powershell().
1202 1214 # TODO figure out a workaround.
1203 1215
1204 1216 print('waiting for Windows Remote Management to come back...')
1205 1217 client = wait_for_winrm(
1206 1218 instance.public_ip_address,
1207 1219 'Administrator',
1208 1220 c.automation.default_password(),
1209 1221 )
1210 1222 print('established WinRM connection to %s' % instance.id)
1211 1223 instance.winrm_client = client
1212 1224
1213 1225 print('bootstrapping instance...')
1214 1226 run_powershell(instance.winrm_client, '\n'.join(commands))
1215 1227
1216 1228 print('bootstrap completed; stopping %s to create image' % instance.id)
1217 1229 return create_ami_from_instance(
1218 1230 ec2client,
1219 1231 instance,
1220 1232 name,
1221 1233 'Mercurial Windows development environment',
1222 1234 fingerprint,
1223 1235 )
1224 1236
1225 1237
1226 1238 @contextlib.contextmanager
1227 1239 def temporary_windows_dev_instances(
1228 1240 c: AWSConnection,
1229 1241 image,
1230 1242 instance_type,
1231 1243 prefix='hg-',
1232 1244 disable_antivirus=False,
1233 1245 ):
1234 1246 """Create a temporary Windows development EC2 instance.
1235 1247
1236 1248 Context manager resolves to the list of ``EC2.Instance`` that were created.
1237 1249 """
1238 1250 config = {
1239 1251 'BlockDeviceMappings': [
1240 1252 {
1241 1253 'DeviceName': '/dev/sda1',
1242 1254 'Ebs': {
1243 1255 'DeleteOnTermination': True,
1244 1256 'VolumeSize': 32,
1245 1257 'VolumeType': 'gp2',
1246 1258 },
1247 1259 }
1248 1260 ],
1249 1261 'ImageId': image.id,
1250 1262 'InstanceInitiatedShutdownBehavior': 'stop',
1251 1263 'InstanceType': instance_type,
1252 1264 'KeyName': '%sautomation' % prefix,
1253 1265 'MaxCount': 1,
1254 1266 'MinCount': 1,
1255 1267 'SecurityGroupIds': [c.security_groups['windows-dev-1'].id],
1256 1268 }
1257 1269
1258 1270 with create_temp_windows_ec2_instances(c, config) as instances:
1259 1271 if disable_antivirus:
1260 1272 for instance in instances:
1261 1273 run_powershell(
1262 1274 instance.winrm_client,
1263 1275 'Set-MpPreference -DisableRealtimeMonitoring $true',
1264 1276 )
1265 1277
1266 1278 yield instances
@@ -1,595 +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 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"
29 PYENV3_VERSIONS="3.5.9 3.6.10 3.7.7 3.8.2 pypy3.5-7.0.0 pypy3.6-7.3.0"
30 30
31 31 git clone https://github.com/pyenv/pyenv.git /hgdev/pyenv
32 32 pushd /hgdev/pyenv
33 git checkout 0e7cfc3b3d4eca46ad83d632e1505f5932cd179b
33 git checkout 3005c4664372ae13fbe376be699313eb428c8bdd
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 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
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 269 python3-venv \
270 270 rsync \
271 271 sqlite3 \
272 272 subversion \
273 273 tcl-dev \
274 274 tk-dev \
275 275 tla \
276 276 unzip \
277 277 uuid-dev \
278 278 vim \
279 279 virtualenv \
280 280 wget \
281 281 xfsprogs \
282 282 zip \
283 283 zlib1g-dev"
284 284
285 285 if [ "LSB_RELEASE" = "stretch" ]; then
286 286 PACKAGES="$PACKAGES linux-perf"
287 287 elif [ "$DISTRO" = "Ubuntu" ]; then
288 288 PACKAGES="$PACKAGES linux-tools-common"
289 289 fi
290 290
291 291 # Monotone only available in older releases.
292 292 if [ "$LSB_RELEASE" = "stretch" -o "$LSB_RELEASE" = "xenial" ]; then
293 293 PACKAGES="$PACKAGES monotone"
294 294 fi
295 295
296 296 sudo DEBIAN_FRONTEND=noninteractive apt-get -yq install --no-install-recommends $PACKAGES
297 297
298 298 # Create clang-format symlink so test harness finds it.
299 299 sudo update-alternatives --install /usr/bin/clang-format clang-format \
300 300 /usr/bin/clang-format-6.0 1000
301 301
302 302 sudo mkdir /hgdev
303 303 # Will be normalized to hg:hg later.
304 304 sudo chown `whoami` /hgdev
305 305
306 306 {install_rust}
307 307
308 308 cp requirements-py2.txt /hgdev/requirements-py2.txt
309 309 cp requirements-py3.txt /hgdev/requirements-py3.txt
310 310
311 311 # Disable the pip version check because it uses the network and can
312 312 # be annoying.
313 313 cat << EOF | sudo tee -a /etc/pip.conf
314 314 [global]
315 315 disable-pip-version-check = True
316 316 EOF
317 317
318 318 {install_pythons}
319 319 {bootstrap_virtualenv}
320 320
321 321 /hgdev/venv-bootstrap/bin/hg clone https://www.mercurial-scm.org/repo/hg /hgdev/src
322 322
323 323 # Mark the repo as non-publishing.
324 324 cat >> /hgdev/src/.hg/hgrc << EOF
325 325 [phases]
326 326 publish = false
327 327 EOF
328 328
329 329 sudo chown -R hg:hg /hgdev
330 330 '''.lstrip()
331 331 .format(
332 332 install_rust=INSTALL_RUST,
333 333 install_pythons=INSTALL_PYTHONS,
334 334 bootstrap_virtualenv=BOOTSTRAP_VIRTUALENV,
335 335 )
336 336 .replace('\r\n', '\n')
337 337 )
338 338
339 339
340 340 # Prepares /hgdev for operations.
341 341 PREPARE_HGDEV = '''
342 342 #!/bin/bash
343 343
344 344 set -e
345 345
346 346 FS=$1
347 347
348 348 ensure_device() {
349 349 if [ -z "${DEVICE}" ]; then
350 350 echo "could not find block device to format"
351 351 exit 1
352 352 fi
353 353 }
354 354
355 355 # Determine device to partition for extra filesystem.
356 356 # If only 1 volume is present, it will be the root volume and
357 357 # should be /dev/nvme0. If multiple volumes are present, the
358 358 # root volume could be nvme0 or nvme1. Use whichever one doesn't have
359 359 # a partition.
360 360 if [ -e /dev/nvme1n1 ]; then
361 361 if [ -e /dev/nvme0n1p1 ]; then
362 362 DEVICE=/dev/nvme1n1
363 363 else
364 364 DEVICE=/dev/nvme0n1
365 365 fi
366 366 else
367 367 DEVICE=
368 368 fi
369 369
370 370 sudo mkdir /hgwork
371 371
372 372 if [ "${FS}" != "default" -a "${FS}" != "tmpfs" ]; then
373 373 ensure_device
374 374 echo "creating ${FS} filesystem on ${DEVICE}"
375 375 fi
376 376
377 377 if [ "${FS}" = "default" ]; then
378 378 :
379 379
380 380 elif [ "${FS}" = "btrfs" ]; then
381 381 sudo mkfs.btrfs ${DEVICE}
382 382 sudo mount ${DEVICE} /hgwork
383 383
384 384 elif [ "${FS}" = "ext3" ]; then
385 385 # lazy_journal_init speeds up filesystem creation at the expense of
386 386 # integrity if things crash. We are an ephemeral instance, so we don't
387 387 # care about integrity.
388 388 sudo mkfs.ext3 -E lazy_journal_init=1 ${DEVICE}
389 389 sudo mount ${DEVICE} /hgwork
390 390
391 391 elif [ "${FS}" = "ext4" ]; then
392 392 sudo mkfs.ext4 -E lazy_journal_init=1 ${DEVICE}
393 393 sudo mount ${DEVICE} /hgwork
394 394
395 395 elif [ "${FS}" = "jfs" ]; then
396 396 sudo mkfs.jfs ${DEVICE}
397 397 sudo mount ${DEVICE} /hgwork
398 398
399 399 elif [ "${FS}" = "tmpfs" ]; then
400 400 echo "creating tmpfs volume in /hgwork"
401 401 sudo mount -t tmpfs -o size=1024M tmpfs /hgwork
402 402
403 403 elif [ "${FS}" = "xfs" ]; then
404 404 sudo mkfs.xfs ${DEVICE}
405 405 sudo mount ${DEVICE} /hgwork
406 406
407 407 else
408 408 echo "unsupported filesystem: ${FS}"
409 409 exit 1
410 410 fi
411 411
412 412 echo "/hgwork ready"
413 413
414 414 sudo chown hg:hg /hgwork
415 415 mkdir /hgwork/tmp
416 416 chown hg:hg /hgwork/tmp
417 417
418 418 rsync -a /hgdev/src /hgwork/
419 419 '''.lstrip().replace(
420 420 '\r\n', '\n'
421 421 )
422 422
423 423
424 424 HG_UPDATE_CLEAN = '''
425 425 set -ex
426 426
427 427 HG=/hgdev/venv-bootstrap/bin/hg
428 428
429 429 cd /hgwork/src
430 430 ${HG} --config extensions.purge= purge --all
431 431 ${HG} update -C $1
432 432 ${HG} log -r .
433 433 '''.lstrip().replace(
434 434 '\r\n', '\n'
435 435 )
436 436
437 437
438 438 def prepare_exec_environment(ssh_client, filesystem='default'):
439 439 """Prepare an EC2 instance to execute things.
440 440
441 441 The AMI has an ``/hgdev`` bootstrapped with various Python installs
442 442 and a clone of the Mercurial repo.
443 443
444 444 In EC2, EBS volumes launched from snapshots have wonky performance behavior.
445 445 Notably, blocks have to be copied on first access, which makes volume
446 446 I/O extremely slow on fresh volumes.
447 447
448 448 Furthermore, we may want to run operations, tests, etc on alternative
449 449 filesystems so we examine behavior on different filesystems.
450 450
451 451 This function is used to facilitate executing operations on alternate
452 452 volumes.
453 453 """
454 454 sftp = ssh_client.open_sftp()
455 455
456 456 with sftp.open('/hgdev/prepare-hgdev', 'wb') as fh:
457 457 fh.write(PREPARE_HGDEV)
458 458 fh.chmod(0o0777)
459 459
460 460 command = 'sudo /hgdev/prepare-hgdev %s' % filesystem
461 461 chan, stdin, stdout = exec_command(ssh_client, command)
462 462 stdin.close()
463 463
464 464 for line in stdout:
465 465 print(line, end='')
466 466
467 467 res = chan.recv_exit_status()
468 468
469 469 if res:
470 470 raise Exception('non-0 exit code updating working directory; %d' % res)
471 471
472 472
473 473 def synchronize_hg(
474 474 source_path: pathlib.Path, ec2_instance, revision: str = None
475 475 ):
476 476 """Synchronize a local Mercurial source path to remote EC2 instance."""
477 477
478 478 with tempfile.TemporaryDirectory() as temp_dir:
479 479 temp_dir = pathlib.Path(temp_dir)
480 480
481 481 ssh_dir = temp_dir / '.ssh'
482 482 ssh_dir.mkdir()
483 483 ssh_dir.chmod(0o0700)
484 484
485 485 public_ip = ec2_instance.public_ip_address
486 486
487 487 ssh_config = ssh_dir / 'config'
488 488
489 489 with ssh_config.open('w', encoding='utf-8') as fh:
490 490 fh.write('Host %s\n' % public_ip)
491 491 fh.write(' User hg\n')
492 492 fh.write(' StrictHostKeyChecking no\n')
493 493 fh.write(' UserKnownHostsFile %s\n' % (ssh_dir / 'known_hosts'))
494 494 fh.write(' IdentityFile %s\n' % ec2_instance.ssh_private_key_path)
495 495
496 496 if not (source_path / '.hg').is_dir():
497 497 raise Exception(
498 498 '%s is not a Mercurial repository; synchronization '
499 499 'not yet supported' % source_path
500 500 )
501 501
502 502 env = dict(os.environ)
503 503 env['HGPLAIN'] = '1'
504 504 env['HGENCODING'] = 'utf-8'
505 505
506 506 hg_bin = source_path / 'hg'
507 507
508 508 res = subprocess.run(
509 509 ['python2.7', str(hg_bin), 'log', '-r', revision, '-T', '{node}'],
510 510 cwd=str(source_path),
511 511 env=env,
512 512 check=True,
513 513 capture_output=True,
514 514 )
515 515
516 516 full_revision = res.stdout.decode('ascii')
517 517
518 518 args = [
519 519 'python2.7',
520 520 str(hg_bin),
521 521 '--config',
522 522 'ui.ssh=ssh -F %s' % ssh_config,
523 523 '--config',
524 524 'ui.remotecmd=/hgdev/venv-bootstrap/bin/hg',
525 525 # Also ensure .hgtags changes are present so auto version
526 526 # calculation works.
527 527 'push',
528 528 '-f',
529 529 '-r',
530 530 full_revision,
531 531 '-r',
532 532 'file(.hgtags)',
533 533 'ssh://%s//hgwork/src' % public_ip,
534 534 ]
535 535
536 536 res = subprocess.run(args, cwd=str(source_path), env=env)
537 537
538 538 # Allow 1 (no-op) to not trigger error.
539 539 if res.returncode not in (0, 1):
540 540 res.check_returncode()
541 541
542 542 # TODO support synchronizing dirty working directory.
543 543
544 544 sftp = ec2_instance.ssh_client.open_sftp()
545 545
546 546 with sftp.open('/hgdev/hgup', 'wb') as fh:
547 547 fh.write(HG_UPDATE_CLEAN)
548 548 fh.chmod(0o0700)
549 549
550 550 chan, stdin, stdout = exec_command(
551 551 ec2_instance.ssh_client, '/hgdev/hgup %s' % full_revision
552 552 )
553 553 stdin.close()
554 554
555 555 for line in stdout:
556 556 print(line, end='')
557 557
558 558 res = chan.recv_exit_status()
559 559
560 560 if res:
561 561 raise Exception(
562 562 'non-0 exit code updating working directory; %d' % res
563 563 )
564 564
565 565
566 566 def run_tests(ssh_client, python_version, test_flags=None):
567 567 """Run tests on a remote Linux machine via an SSH client."""
568 568 test_flags = test_flags or []
569 569
570 570 print('running tests')
571 571
572 572 if python_version == 'system2':
573 573 python = '/usr/bin/python2'
574 574 elif python_version == 'system3':
575 575 python = '/usr/bin/python3'
576 576 elif python_version.startswith('pypy'):
577 577 python = '/hgdev/pyenv/shims/%s' % python_version
578 578 else:
579 579 python = '/hgdev/pyenv/shims/python%s' % python_version
580 580
581 581 test_flags = ' '.join(shlex.quote(a) for a in test_flags)
582 582
583 583 command = (
584 584 '/bin/sh -c "export TMPDIR=/hgwork/tmp; '
585 585 'cd /hgwork/src/tests && %s run-tests.py %s"' % (python, test_flags)
586 586 )
587 587
588 588 chan, stdin, stdout = exec_command(ssh_client, command)
589 589
590 590 stdin.close()
591 591
592 592 for line in stdout:
593 593 print(line, end='')
594 594
595 595 return chan.recv_exit_status()
@@ -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.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"
25 $PYTHON27_x64_URL = "https://www.python.org/ftp/python/2.7.18/python-2.7.18.amd64.msi"
26 $PYTHON27_x64_SHA256 = "b74a3afa1e0bf2a6fc566a7b70d15c9bfabba3756fb077797d16fffa27800c05"
27 $PYTHON27_X86_URL = "https://www.python.org/ftp/python/2.7.18/python-2.7.18.msi"
28 $PYTHON27_X86_SHA256 = "d901802e90026e9bad76b8a81f8dd7e43c7d7e8269d9281c9e9df7a9c40480a9"
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.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"
40 $PYTHON37_x86_URL = "https://www.python.org/ftp/python/3.7.7/python-3.7.7.exe"
41 $PYTHON37_x86_SHA256 = "27fbffcd342d5055acc64050db4c35d0025661521e642b59c381dcba2e162c6a"
42 $PYTHON37_X64_URL = "https://www.python.org/ftp/python/3.7.7/python-3.7.7-amd64.exe"
43 $PYTHON37_x64_SHA256 = "1a0368663ceff999d865de955992b6ea3cb0c8cb15a1a296a8eb7df19cc59e69"
44 44
45 $PYTHON38_x86_URL = "https://www.python.org/ftp/python/3.8.0/python-3.8.0.exe"
46 $PYTHON38_x86_SHA256 = "b471908de5e10d8fb5c3351a5affb1172da7790c533e0c9ffbaeec9c11611b15"
47 $PYTHON38_x64_URL = "https://www.python.org/ftp/python/3.8.0/python-3.8.0-amd64.exe"
48 $PYTHON38_x64_SHA256 = "a9bbc6088a3e4c7112826e21bfee6277f7b6d93259f7c57176139231bb7071e4"
45 $PYTHON38_x86_URL = "https://www.python.org/ftp/python/3.8.2/python-3.8.2.exe"
46 $PYTHON38_x86_SHA256 = "03ac5754a69c9c11c08d1f4d694c14625a4d27348ad4dd2d1253e2547819db2c"
47 $PYTHON38_x64_URL = "https://www.python.org/ftp/python/3.8.2/python-3.8.2-amd64.exe"
48 $PYTHON38_x64_SHA256 = "8e400e3f32cdcb746e62e0db4d3ae4cba1f927141ebc4d0d5a4006b0daee8921"
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,39 +1,49 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/packaging/requirements.txt contrib/packaging/requirements.txt.in
6 6 #
7 jinja2==2.10.3 \
8 --hash=sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f \
9 --hash=sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de
7 docutils==0.16 \
8 --hash=sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af \
9 --hash=sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc \
10 # via -r contrib/packaging/requirements.txt.in
11 jinja2==2.11.2 \
12 --hash=sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0 \
13 --hash=sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035 \
14 # via -r contrib/packaging/requirements.txt.in
10 15 markupsafe==1.1.1 \
11 16 --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \
12 17 --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \
13 18 --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \
14 19 --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \
20 --hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \
15 21 --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \
16 22 --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b \
17 23 --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \
18 24 --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \
19 25 --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \
20 26 --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \
27 --hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \
21 28 --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \
29 --hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \
22 30 --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \
23 31 --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \
24 32 --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \
25 33 --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \
26 34 --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \
27 35 --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \
28 36 --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \
29 37 --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \
30 38 --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \
31 39 --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \
32 40 --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \
33 41 --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \
34 42 --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \
35 43 --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \
36 44 --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \
37 45 --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \
46 --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \
38 47 --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \
48 --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \
39 49 # via jinja2
@@ -1,1 +1,2 b''
1 docutils
1 2 jinja2
@@ -1,598 +1,602 b''
1 1 # discovery.py - protocol changeset discovery functions
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import functools
11 11
12 12 from .i18n import _
13 13 from .node import (
14 14 hex,
15 15 nullid,
16 16 short,
17 17 )
18 18
19 19 from . import (
20 20 bookmarks,
21 21 branchmap,
22 22 error,
23 23 phases,
24 24 pycompat,
25 25 scmutil,
26 26 setdiscovery,
27 27 treediscovery,
28 28 util,
29 29 )
30 30
31 31
32 32 def findcommonincoming(repo, remote, heads=None, force=False, ancestorsof=None):
33 33 """Return a tuple (common, anyincoming, heads) used to identify the common
34 34 subset of nodes between repo and remote.
35 35
36 36 "common" is a list of (at least) the heads of the common subset.
37 37 "anyincoming" is testable as a boolean indicating if any nodes are missing
38 38 locally. If remote does not support getbundle, this actually is a list of
39 39 roots of the nodes that would be incoming, to be supplied to
40 40 changegroupsubset. No code except for pull should be relying on this fact
41 41 any longer.
42 42 "heads" is either the supplied heads, or else the remote's heads.
43 43 "ancestorsof" if not None, restrict the discovery to a subset defined by
44 44 these nodes. Changeset outside of this set won't be considered (and
45 45 won't appears in "common")
46 46
47 47 If you pass heads and they are all known locally, the response lists just
48 48 these heads in "common" and in "heads".
49 49
50 50 Please use findcommonoutgoing to compute the set of outgoing nodes to give
51 51 extensions a good hook into outgoing.
52 52 """
53 53
54 54 if not remote.capable(b'getbundle'):
55 55 return treediscovery.findcommonincoming(repo, remote, heads, force)
56 56
57 57 if heads:
58 58 knownnode = repo.changelog.hasnode # no nodemap until it is filtered
59 59 if all(knownnode(h) for h in heads):
60 60 return (heads, False, heads)
61 61
62 62 res = setdiscovery.findcommonheads(
63 63 repo.ui,
64 64 repo,
65 65 remote,
66 66 abortwhenunrelated=not force,
67 67 ancestorsof=ancestorsof,
68 68 )
69 69 common, anyinc, srvheads = res
70 if heads and not anyinc:
71 # server could be lying on the advertised heads
72 has_node = repo.changelog.hasnode
73 anyinc = any(not has_node(n) for n in heads)
70 74 return (list(common), anyinc, heads or list(srvheads))
71 75
72 76
73 77 class outgoing(object):
74 78 '''Represents the set of nodes present in a local repo but not in a
75 79 (possibly) remote one.
76 80
77 81 Members:
78 82
79 83 missing is a list of all nodes present in local but not in remote.
80 84 common is a list of all nodes shared between the two repos.
81 85 excluded is the list of missing changeset that shouldn't be sent remotely.
82 86 missingheads is the list of heads of missing.
83 87 commonheads is the list of heads of common.
84 88
85 89 The sets are computed on demand from the heads, unless provided upfront
86 90 by discovery.'''
87 91
88 92 def __init__(
89 93 self, repo, commonheads=None, missingheads=None, missingroots=None
90 94 ):
91 95 # at least one of them must not be set
92 96 assert None in (commonheads, missingroots)
93 97 cl = repo.changelog
94 98 if missingheads is None:
95 99 missingheads = cl.heads()
96 100 if missingroots:
97 101 discbases = []
98 102 for n in missingroots:
99 103 discbases.extend([p for p in cl.parents(n) if p != nullid])
100 104 # TODO remove call to nodesbetween.
101 105 # TODO populate attributes on outgoing instance instead of setting
102 106 # discbases.
103 107 csets, roots, heads = cl.nodesbetween(missingroots, missingheads)
104 108 included = set(csets)
105 109 missingheads = heads
106 110 commonheads = [n for n in discbases if n not in included]
107 111 elif not commonheads:
108 112 commonheads = [nullid]
109 113 self.commonheads = commonheads
110 114 self.missingheads = missingheads
111 115 self._revlog = cl
112 116 self._common = None
113 117 self._missing = None
114 118 self.excluded = []
115 119
116 120 def _computecommonmissing(self):
117 121 sets = self._revlog.findcommonmissing(
118 122 self.commonheads, self.missingheads
119 123 )
120 124 self._common, self._missing = sets
121 125
122 126 @util.propertycache
123 127 def common(self):
124 128 if self._common is None:
125 129 self._computecommonmissing()
126 130 return self._common
127 131
128 132 @util.propertycache
129 133 def missing(self):
130 134 if self._missing is None:
131 135 self._computecommonmissing()
132 136 return self._missing
133 137
134 138
135 139 def findcommonoutgoing(
136 140 repo, other, onlyheads=None, force=False, commoninc=None, portable=False
137 141 ):
138 142 '''Return an outgoing instance to identify the nodes present in repo but
139 143 not in other.
140 144
141 145 If onlyheads is given, only nodes ancestral to nodes in onlyheads
142 146 (inclusive) are included. If you already know the local repo's heads,
143 147 passing them in onlyheads is faster than letting them be recomputed here.
144 148
145 149 If commoninc is given, it must be the result of a prior call to
146 150 findcommonincoming(repo, other, force) to avoid recomputing it here.
147 151
148 152 If portable is given, compute more conservative common and missingheads,
149 153 to make bundles created from the instance more portable.'''
150 154 # declare an empty outgoing object to be filled later
151 155 og = outgoing(repo, None, None)
152 156
153 157 # get common set if not provided
154 158 if commoninc is None:
155 159 commoninc = findcommonincoming(
156 160 repo, other, force=force, ancestorsof=onlyheads
157 161 )
158 162 og.commonheads, _any, _hds = commoninc
159 163
160 164 # compute outgoing
161 165 mayexclude = repo._phasecache.phaseroots[phases.secret] or repo.obsstore
162 166 if not mayexclude:
163 167 og.missingheads = onlyheads or repo.heads()
164 168 elif onlyheads is None:
165 169 # use visible heads as it should be cached
166 170 og.missingheads = repo.filtered(b"served").heads()
167 171 og.excluded = [ctx.node() for ctx in repo.set(b'secret() or extinct()')]
168 172 else:
169 173 # compute common, missing and exclude secret stuff
170 174 sets = repo.changelog.findcommonmissing(og.commonheads, onlyheads)
171 175 og._common, allmissing = sets
172 176 og._missing = missing = []
173 177 og.excluded = excluded = []
174 178 for node in allmissing:
175 179 ctx = repo[node]
176 180 if ctx.phase() >= phases.secret or ctx.extinct():
177 181 excluded.append(node)
178 182 else:
179 183 missing.append(node)
180 184 if len(missing) == len(allmissing):
181 185 missingheads = onlyheads
182 186 else: # update missing heads
183 187 missingheads = phases.newheads(repo, onlyheads, excluded)
184 188 og.missingheads = missingheads
185 189 if portable:
186 190 # recompute common and missingheads as if -r<rev> had been given for
187 191 # each head of missing, and --base <rev> for each head of the proper
188 192 # ancestors of missing
189 193 og._computecommonmissing()
190 194 cl = repo.changelog
191 195 missingrevs = {cl.rev(n) for n in og._missing}
192 196 og._common = set(cl.ancestors(missingrevs)) - missingrevs
193 197 commonheads = set(og.commonheads)
194 198 og.missingheads = [h for h in og.missingheads if h not in commonheads]
195 199
196 200 return og
197 201
198 202
199 203 def _headssummary(pushop):
200 204 """compute a summary of branch and heads status before and after push
201 205
202 206 return {'branch': ([remoteheads], [newheads],
203 207 [unsyncedheads], [discardedheads])} mapping
204 208
205 209 - branch: the branch name,
206 210 - remoteheads: the list of remote heads known locally
207 211 None if the branch is new,
208 212 - newheads: the new remote heads (known locally) with outgoing pushed,
209 213 - unsyncedheads: the list of remote heads unknown locally,
210 214 - discardedheads: the list of heads made obsolete by the push.
211 215 """
212 216 repo = pushop.repo.unfiltered()
213 217 remote = pushop.remote
214 218 outgoing = pushop.outgoing
215 219 cl = repo.changelog
216 220 headssum = {}
217 221 missingctx = set()
218 222 # A. Create set of branches involved in the push.
219 223 branches = set()
220 224 for n in outgoing.missing:
221 225 ctx = repo[n]
222 226 missingctx.add(ctx)
223 227 branches.add(ctx.branch())
224 228
225 229 with remote.commandexecutor() as e:
226 230 remotemap = e.callcommand(b'branchmap', {}).result()
227 231
228 232 knownnode = cl.hasnode # do not use nodemap until it is filtered
229 233 # A. register remote heads of branches which are in outgoing set
230 234 for branch, heads in pycompat.iteritems(remotemap):
231 235 # don't add head info about branches which we don't have locally
232 236 if branch not in branches:
233 237 continue
234 238 known = []
235 239 unsynced = []
236 240 for h in heads:
237 241 if knownnode(h):
238 242 known.append(h)
239 243 else:
240 244 unsynced.append(h)
241 245 headssum[branch] = (known, list(known), unsynced)
242 246
243 247 # B. add new branch data
244 248 for branch in branches:
245 249 if branch not in headssum:
246 250 headssum[branch] = (None, [], [])
247 251
248 252 # C. Update newmap with outgoing changes.
249 253 # This will possibly add new heads and remove existing ones.
250 254 newmap = branchmap.remotebranchcache(
251 255 (branch, heads[1])
252 256 for branch, heads in pycompat.iteritems(headssum)
253 257 if heads[0] is not None
254 258 )
255 259 newmap.update(repo, (ctx.rev() for ctx in missingctx))
256 260 for branch, newheads in pycompat.iteritems(newmap):
257 261 headssum[branch][1][:] = newheads
258 262 for branch, items in pycompat.iteritems(headssum):
259 263 for l in items:
260 264 if l is not None:
261 265 l.sort()
262 266 headssum[branch] = items + ([],)
263 267
264 268 # If there are no obsstore, no post processing are needed.
265 269 if repo.obsstore:
266 270 torev = repo.changelog.rev
267 271 futureheads = {torev(h) for h in outgoing.missingheads}
268 272 futureheads |= {torev(h) for h in outgoing.commonheads}
269 273 allfuturecommon = repo.changelog.ancestors(futureheads, inclusive=True)
270 274 for branch, heads in sorted(pycompat.iteritems(headssum)):
271 275 remoteheads, newheads, unsyncedheads, placeholder = heads
272 276 result = _postprocessobsolete(pushop, allfuturecommon, newheads)
273 277 headssum[branch] = (
274 278 remoteheads,
275 279 sorted(result[0]),
276 280 unsyncedheads,
277 281 sorted(result[1]),
278 282 )
279 283 return headssum
280 284
281 285
282 286 def _oldheadssummary(repo, remoteheads, outgoing, inc=False):
283 287 """Compute branchmapsummary for repo without branchmap support"""
284 288
285 289 # 1-4b. old servers: Check for new topological heads.
286 290 # Construct {old,new}map with branch = None (topological branch).
287 291 # (code based on update)
288 292 knownnode = repo.changelog.hasnode # no nodemap until it is filtered
289 293 oldheads = sorted(h for h in remoteheads if knownnode(h))
290 294 # all nodes in outgoing.missing are children of either:
291 295 # - an element of oldheads
292 296 # - another element of outgoing.missing
293 297 # - nullrev
294 298 # This explains why the new head are very simple to compute.
295 299 r = repo.set(b'heads(%ln + %ln)', oldheads, outgoing.missing)
296 300 newheads = sorted(c.node() for c in r)
297 301 # set some unsynced head to issue the "unsynced changes" warning
298 302 if inc:
299 303 unsynced = [None]
300 304 else:
301 305 unsynced = []
302 306 return {None: (oldheads, newheads, unsynced, [])}
303 307
304 308
305 309 def _nowarnheads(pushop):
306 310 # Compute newly pushed bookmarks. We don't warn about bookmarked heads.
307 311 repo = pushop.repo.unfiltered()
308 312 remote = pushop.remote
309 313 localbookmarks = repo._bookmarks
310 314
311 315 with remote.commandexecutor() as e:
312 316 remotebookmarks = e.callcommand(
313 317 b'listkeys', {b'namespace': b'bookmarks',}
314 318 ).result()
315 319
316 320 bookmarkedheads = set()
317 321
318 322 # internal config: bookmarks.pushing
319 323 newbookmarks = [
320 324 localbookmarks.expandname(b)
321 325 for b in pushop.ui.configlist(b'bookmarks', b'pushing')
322 326 ]
323 327
324 328 for bm in localbookmarks:
325 329 rnode = remotebookmarks.get(bm)
326 330 if rnode and rnode in repo:
327 331 lctx, rctx = repo[localbookmarks[bm]], repo[rnode]
328 332 if bookmarks.validdest(repo, rctx, lctx):
329 333 bookmarkedheads.add(lctx.node())
330 334 else:
331 335 if bm in newbookmarks and bm not in remotebookmarks:
332 336 bookmarkedheads.add(localbookmarks[bm])
333 337
334 338 return bookmarkedheads
335 339
336 340
337 341 def checkheads(pushop):
338 342 """Check that a push won't add any outgoing head
339 343
340 344 raise Abort error and display ui message as needed.
341 345 """
342 346
343 347 repo = pushop.repo.unfiltered()
344 348 remote = pushop.remote
345 349 outgoing = pushop.outgoing
346 350 remoteheads = pushop.remoteheads
347 351 newbranch = pushop.newbranch
348 352 inc = bool(pushop.incoming)
349 353
350 354 # Check for each named branch if we're creating new remote heads.
351 355 # To be a remote head after push, node must be either:
352 356 # - unknown locally
353 357 # - a local outgoing head descended from update
354 358 # - a remote head that's known locally and not
355 359 # ancestral to an outgoing head
356 360 if remoteheads == [nullid]:
357 361 # remote is empty, nothing to check.
358 362 return
359 363
360 364 if remote.capable(b'branchmap'):
361 365 headssum = _headssummary(pushop)
362 366 else:
363 367 headssum = _oldheadssummary(repo, remoteheads, outgoing, inc)
364 368 pushop.pushbranchmap = headssum
365 369 newbranches = [
366 370 branch
367 371 for branch, heads in pycompat.iteritems(headssum)
368 372 if heads[0] is None
369 373 ]
370 374 # 1. Check for new branches on the remote.
371 375 if newbranches and not newbranch: # new branch requires --new-branch
372 376 branchnames = b', '.join(sorted(newbranches))
373 377 # Calculate how many of the new branches are closed branches
374 378 closedbranches = set()
375 379 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
376 380 if isclosed:
377 381 closedbranches.add(tag)
378 382 closedbranches = closedbranches & set(newbranches)
379 383 if closedbranches:
380 384 errmsg = _(b"push creates new remote branches: %s (%d closed)!") % (
381 385 branchnames,
382 386 len(closedbranches),
383 387 )
384 388 else:
385 389 errmsg = _(b"push creates new remote branches: %s!") % branchnames
386 390 hint = _(b"use 'hg push --new-branch' to create new remote branches")
387 391 raise error.Abort(errmsg, hint=hint)
388 392
389 393 # 2. Find heads that we need not warn about
390 394 nowarnheads = _nowarnheads(pushop)
391 395
392 396 # 3. Check for new heads.
393 397 # If there are more heads after the push than before, a suitable
394 398 # error message, depending on unsynced status, is displayed.
395 399 errormsg = None
396 400 for branch, heads in sorted(pycompat.iteritems(headssum)):
397 401 remoteheads, newheads, unsyncedheads, discardedheads = heads
398 402 # add unsynced data
399 403 if remoteheads is None:
400 404 oldhs = set()
401 405 else:
402 406 oldhs = set(remoteheads)
403 407 oldhs.update(unsyncedheads)
404 408 dhs = None # delta heads, the new heads on branch
405 409 newhs = set(newheads)
406 410 newhs.update(unsyncedheads)
407 411 if unsyncedheads:
408 412 if None in unsyncedheads:
409 413 # old remote, no heads data
410 414 heads = None
411 415 else:
412 416 heads = scmutil.nodesummaries(repo, unsyncedheads)
413 417 if heads is None:
414 418 repo.ui.status(
415 419 _(b"remote has heads that are not known locally\n")
416 420 )
417 421 elif branch is None:
418 422 repo.ui.status(
419 423 _(b"remote has heads that are not known locally: %s\n")
420 424 % heads
421 425 )
422 426 else:
423 427 repo.ui.status(
424 428 _(
425 429 b"remote has heads on branch '%s' that are "
426 430 b"not known locally: %s\n"
427 431 )
428 432 % (branch, heads)
429 433 )
430 434 if remoteheads is None:
431 435 if len(newhs) > 1:
432 436 dhs = list(newhs)
433 437 if errormsg is None:
434 438 errormsg = (
435 439 _(b"push creates new branch '%s' with multiple heads")
436 440 % branch
437 441 )
438 442 hint = _(
439 443 b"merge or"
440 444 b" see 'hg help push' for details about"
441 445 b" pushing new heads"
442 446 )
443 447 elif len(newhs) > len(oldhs):
444 448 # remove bookmarked or existing remote heads from the new heads list
445 449 dhs = sorted(newhs - nowarnheads - oldhs)
446 450 if dhs:
447 451 if errormsg is None:
448 452 if branch not in (b'default', None):
449 453 errormsg = _(
450 454 b"push creates new remote head %s on branch '%s'!"
451 455 ) % (short(dhs[0]), branch,)
452 456 elif repo[dhs[0]].bookmarks():
453 457 errormsg = _(
454 458 b"push creates new remote head %s "
455 459 b"with bookmark '%s'!"
456 460 ) % (short(dhs[0]), repo[dhs[0]].bookmarks()[0])
457 461 else:
458 462 errormsg = _(b"push creates new remote head %s!") % short(
459 463 dhs[0]
460 464 )
461 465 if unsyncedheads:
462 466 hint = _(
463 467 b"pull and merge or"
464 468 b" see 'hg help push' for details about"
465 469 b" pushing new heads"
466 470 )
467 471 else:
468 472 hint = _(
469 473 b"merge or"
470 474 b" see 'hg help push' for details about"
471 475 b" pushing new heads"
472 476 )
473 477 if branch is None:
474 478 repo.ui.note(_(b"new remote heads:\n"))
475 479 else:
476 480 repo.ui.note(_(b"new remote heads on branch '%s':\n") % branch)
477 481 for h in dhs:
478 482 repo.ui.note(b" %s\n" % short(h))
479 483 if errormsg:
480 484 raise error.Abort(errormsg, hint=hint)
481 485
482 486
483 487 def _postprocessobsolete(pushop, futurecommon, candidate_newhs):
484 488 """post process the list of new heads with obsolescence information
485 489
486 490 Exists as a sub-function to contain the complexity and allow extensions to
487 491 experiment with smarter logic.
488 492
489 493 Returns (newheads, discarded_heads) tuple
490 494 """
491 495 # known issue
492 496 #
493 497 # * We "silently" skip processing on all changeset unknown locally
494 498 #
495 499 # * if <nh> is public on the remote, it won't be affected by obsolete
496 500 # marker and a new is created
497 501
498 502 # define various utilities and containers
499 503 repo = pushop.repo
500 504 unfi = repo.unfiltered()
501 505 torev = unfi.changelog.index.get_rev
502 506 public = phases.public
503 507 getphase = unfi._phasecache.phase
504 508 ispublic = lambda r: getphase(unfi, r) == public
505 509 ispushed = lambda n: torev(n) in futurecommon
506 510 hasoutmarker = functools.partial(pushingmarkerfor, unfi.obsstore, ispushed)
507 511 successorsmarkers = unfi.obsstore.successors
508 512 newhs = set() # final set of new heads
509 513 discarded = set() # new head of fully replaced branch
510 514
511 515 localcandidate = set() # candidate heads known locally
512 516 unknownheads = set() # candidate heads unknown locally
513 517 for h in candidate_newhs:
514 518 if h in unfi:
515 519 localcandidate.add(h)
516 520 else:
517 521 if successorsmarkers.get(h) is not None:
518 522 msg = (
519 523 b'checkheads: remote head unknown locally has'
520 524 b' local marker: %s\n'
521 525 )
522 526 repo.ui.debug(msg % hex(h))
523 527 unknownheads.add(h)
524 528
525 529 # fast path the simple case
526 530 if len(localcandidate) == 1:
527 531 return unknownheads | set(candidate_newhs), set()
528 532
529 533 # actually process branch replacement
530 534 while localcandidate:
531 535 nh = localcandidate.pop()
532 536 current_branch = unfi[nh].branch()
533 537 # run this check early to skip the evaluation of the whole branch
534 538 if torev(nh) in futurecommon or ispublic(torev(nh)):
535 539 newhs.add(nh)
536 540 continue
537 541
538 542 # Get all revs/nodes on the branch exclusive to this head
539 543 # (already filtered heads are "ignored"))
540 544 branchrevs = unfi.revs(
541 545 b'only(%n, (%ln+%ln))', nh, localcandidate, newhs
542 546 )
543 547
544 548 branchnodes = []
545 549 for r in branchrevs:
546 550 c = unfi[r]
547 551 if c.branch() == current_branch:
548 552 branchnodes.append(c.node())
549 553
550 554 # The branch won't be hidden on the remote if
551 555 # * any part of it is public,
552 556 # * any part of it is considered part of the result by previous logic,
553 557 # * if we have no markers to push to obsolete it.
554 558 if (
555 559 any(ispublic(r) for r in branchrevs)
556 560 or any(torev(n) in futurecommon for n in branchnodes)
557 561 or any(not hasoutmarker(n) for n in branchnodes)
558 562 ):
559 563 newhs.add(nh)
560 564 else:
561 565 # note: there is a corner case if there is a merge in the branch.
562 566 # we might end up with -more- heads. However, these heads are not
563 567 # "added" by the push, but more by the "removal" on the remote so I
564 568 # think is a okay to ignore them,
565 569 discarded.add(nh)
566 570 newhs |= unknownheads
567 571 return newhs, discarded
568 572
569 573
570 574 def pushingmarkerfor(obsstore, ispushed, node):
571 575 """true if some markers are to be pushed for node
572 576
573 577 We cannot just look in to the pushed obsmarkers from the pushop because
574 578 discovery might have filtered relevant markers. In addition listing all
575 579 markers relevant to all changesets in the pushed set would be too expensive
576 580 (O(len(repo)))
577 581
578 582 (note: There are cache opportunity in this function. but it would requires
579 583 a two dimensional stack.)
580 584 """
581 585 successorsmarkers = obsstore.successors
582 586 stack = [node]
583 587 seen = set(stack)
584 588 while stack:
585 589 current = stack.pop()
586 590 if ispushed(current):
587 591 return True
588 592 markers = successorsmarkers.get(current, ())
589 593 # markers fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
590 594 for m in markers:
591 595 nexts = m[1] # successors
592 596 if not nexts: # this is a prune marker
593 597 nexts = m[5] or () # parents
594 598 for n in nexts:
595 599 if n not in seen:
596 600 seen.add(n)
597 601 stack.append(n)
598 602 return False
@@ -1,628 +1,633 b''
1 1 # nodemap.py - nodemap related code and utilities
2 2 #
3 3 # Copyright 2019 Pierre-Yves David <pierre-yves.david@octobus.net>
4 4 # Copyright 2019 George Racinet <georges.racinet@octobus.net>
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 from __future__ import absolute_import
10 10
11 11 import errno
12 12 import os
13 13 import re
14 14 import struct
15 15
16 16 from .. import (
17 17 error,
18 18 node as nodemod,
19 19 util,
20 20 )
21 21
22 22
23 23 class NodeMap(dict):
24 24 def __missing__(self, x):
25 25 raise error.RevlogError(b'unknown node: %s' % x)
26 26
27 27
28 28 def persisted_data(revlog):
29 29 """read the nodemap for a revlog from disk"""
30 30 if revlog.nodemap_file is None:
31 31 return None
32 32 pdata = revlog.opener.tryread(revlog.nodemap_file)
33 33 if not pdata:
34 34 return None
35 35 offset = 0
36 36 (version,) = S_VERSION.unpack(pdata[offset : offset + S_VERSION.size])
37 37 if version != ONDISK_VERSION:
38 38 return None
39 39 offset += S_VERSION.size
40 40 headers = S_HEADER.unpack(pdata[offset : offset + S_HEADER.size])
41 41 uid_size, tip_rev, data_length, data_unused, tip_node_size = headers
42 42 offset += S_HEADER.size
43 43 docket = NodeMapDocket(pdata[offset : offset + uid_size])
44 44 offset += uid_size
45 45 docket.tip_rev = tip_rev
46 46 docket.tip_node = pdata[offset : offset + tip_node_size]
47 47 docket.data_length = data_length
48 48 docket.data_unused = data_unused
49 49
50 50 filename = _rawdata_filepath(revlog, docket)
51 51 use_mmap = revlog.opener.options.get(b"exp-persistent-nodemap.mmap")
52 52 try:
53 53 with revlog.opener(filename) as fd:
54 54 if use_mmap:
55 55 data = util.buffer(util.mmapread(fd, data_length))
56 56 else:
57 57 data = fd.read(data_length)
58 58 except OSError as e:
59 59 if e.errno != errno.ENOENT:
60 60 raise
61 61 if len(data) < data_length:
62 62 return None
63 63 return docket, data
64 64
65 65
66 66 def setup_persistent_nodemap(tr, revlog):
67 67 """Install whatever is needed transaction side to persist a nodemap on disk
68 68
69 69 (only actually persist the nodemap if this is relevant for this revlog)
70 70 """
71 71 if revlog._inline:
72 72 return # inlined revlog are too small for this to be relevant
73 73 if revlog.nodemap_file is None:
74 74 return # we do not use persistent_nodemap on this revlog
75 75
76 76 # we need to happen after the changelog finalization, in that use "cl-"
77 77 callback_id = b"nm-revlog-persistent-nodemap-%s" % revlog.nodemap_file
78 78 if tr.hasfinalize(callback_id):
79 79 return # no need to register again
80 80 tr.addpending(
81 81 callback_id, lambda tr: _persist_nodemap(tr, revlog, pending=True)
82 82 )
83 83 tr.addfinalize(callback_id, lambda tr: _persist_nodemap(tr, revlog))
84 84
85 85
86 86 class _NoTransaction(object):
87 87 """transaction like object to update the nodemap outside a transaction
88 88 """
89 89
90 90 def __init__(self):
91 91 self._postclose = {}
92 92
93 93 def addpostclose(self, callback_id, callback_func):
94 94 self._postclose[callback_id] = callback_func
95 95
96 96 def registertmp(self, *args, **kwargs):
97 97 pass
98 98
99 99 def addbackup(self, *args, **kwargs):
100 100 pass
101 101
102 102 def add(self, *args, **kwargs):
103 103 pass
104 104
105 105 def addabort(self, *args, **kwargs):
106 106 pass
107 107
108 108
109 109 def update_persistent_nodemap(revlog):
110 110 """update the persistent nodemap right now
111 111
112 112 To be used for updating the nodemap on disk outside of a normal transaction
113 113 setup (eg, `debugupdatecache`).
114 114 """
115 if revlog._inline:
116 return # inlined revlog are too small for this to be relevant
117 if revlog.nodemap_file is None:
118 return # we do not use persistent_nodemap on this revlog
119
115 120 notr = _NoTransaction()
116 121 _persist_nodemap(notr, revlog)
117 122 for k in sorted(notr._postclose):
118 123 notr._postclose[k](None)
119 124
120 125
121 126 def _persist_nodemap(tr, revlog, pending=False):
122 127 """Write nodemap data on disk for a given revlog
123 128 """
124 129 if getattr(revlog, 'filteredrevs', ()):
125 130 raise error.ProgrammingError(
126 131 "cannot persist nodemap of a filtered changelog"
127 132 )
128 133 if revlog.nodemap_file is None:
129 134 msg = "calling persist nodemap on a revlog without the feature enableb"
130 135 raise error.ProgrammingError(msg)
131 136
132 137 can_incremental = util.safehasattr(revlog.index, "nodemap_data_incremental")
133 138 ondisk_docket = revlog._nodemap_docket
134 139 feed_data = util.safehasattr(revlog.index, "update_nodemap_data")
135 140 use_mmap = revlog.opener.options.get(b"exp-persistent-nodemap.mmap")
136 141
137 142 data = None
138 143 # first attemp an incremental update of the data
139 144 if can_incremental and ondisk_docket is not None:
140 145 target_docket = revlog._nodemap_docket.copy()
141 146 (
142 147 src_docket,
143 148 data_changed_count,
144 149 data,
145 150 ) = revlog.index.nodemap_data_incremental()
146 151 new_length = target_docket.data_length + len(data)
147 152 new_unused = target_docket.data_unused + data_changed_count
148 153 if src_docket != target_docket:
149 154 data = None
150 155 elif new_length <= (new_unused * 10): # under 10% of unused data
151 156 data = None
152 157 else:
153 158 datafile = _rawdata_filepath(revlog, target_docket)
154 159 # EXP-TODO: if this is a cache, this should use a cache vfs, not a
155 160 # store vfs
156 161 tr.add(datafile, target_docket.data_length)
157 162 with revlog.opener(datafile, b'r+') as fd:
158 163 fd.seek(target_docket.data_length)
159 164 fd.write(data)
160 165 if feed_data:
161 166 if use_mmap:
162 167 fd.seek(0)
163 168 new_data = fd.read(new_length)
164 169 else:
165 170 fd.flush()
166 171 new_data = util.buffer(util.mmapread(fd, new_length))
167 172 target_docket.data_length = new_length
168 173 target_docket.data_unused = new_unused
169 174
170 175 if data is None:
171 176 # otherwise fallback to a full new export
172 177 target_docket = NodeMapDocket()
173 178 datafile = _rawdata_filepath(revlog, target_docket)
174 179 if util.safehasattr(revlog.index, "nodemap_data_all"):
175 180 data = revlog.index.nodemap_data_all()
176 181 else:
177 182 data = persistent_data(revlog.index)
178 183 # EXP-TODO: if this is a cache, this should use a cache vfs, not a
179 184 # store vfs
180 185
181 186 tryunlink = revlog.opener.tryunlink
182 187
183 188 def abortck(tr):
184 189 tryunlink(datafile)
185 190
186 191 callback_id = b"delete-%s" % datafile
187 192
188 193 # some flavor of the transaction abort does not cleanup new file, it
189 194 # simply empty them.
190 195 tr.addabort(callback_id, abortck)
191 196 with revlog.opener(datafile, b'w+') as fd:
192 197 fd.write(data)
193 198 if feed_data:
194 199 if use_mmap:
195 200 new_data = data
196 201 else:
197 202 fd.flush()
198 203 new_data = util.buffer(util.mmapread(fd, len(data)))
199 204 target_docket.data_length = len(data)
200 205 target_docket.tip_rev = revlog.tiprev()
201 206 target_docket.tip_node = revlog.node(target_docket.tip_rev)
202 207 # EXP-TODO: if this is a cache, this should use a cache vfs, not a
203 208 # store vfs
204 209 file_path = revlog.nodemap_file
205 210 if pending:
206 211 file_path += b'.a'
207 212 tr.registertmp(file_path)
208 213 else:
209 214 tr.addbackup(file_path)
210 215
211 216 with revlog.opener(file_path, b'w', atomictemp=True) as fp:
212 217 fp.write(target_docket.serialize())
213 218 revlog._nodemap_docket = target_docket
214 219 if feed_data:
215 220 revlog.index.update_nodemap_data(target_docket, new_data)
216 221
217 222 # search for old index file in all cases, some older process might have
218 223 # left one behind.
219 224 olds = _other_rawdata_filepath(revlog, target_docket)
220 225 if olds:
221 226 realvfs = getattr(revlog, '_realopener', revlog.opener)
222 227
223 228 def cleanup(tr):
224 229 for oldfile in olds:
225 230 realvfs.tryunlink(oldfile)
226 231
227 232 callback_id = b"revlog-cleanup-nodemap-%s" % revlog.nodemap_file
228 233 tr.addpostclose(callback_id, cleanup)
229 234
230 235
231 236 ### Nodemap docket file
232 237 #
233 238 # The nodemap data are stored on disk using 2 files:
234 239 #
235 240 # * a raw data files containing a persistent nodemap
236 241 # (see `Nodemap Trie` section)
237 242 #
238 243 # * a small "docket" file containing medatadata
239 244 #
240 245 # While the nodemap data can be multiple tens of megabytes, the "docket" is
241 246 # small, it is easy to update it automatically or to duplicated its content
242 247 # during a transaction.
243 248 #
244 249 # Multiple raw data can exist at the same time (The currently valid one and a
245 250 # new one beind used by an in progress transaction). To accomodate this, the
246 251 # filename hosting the raw data has a variable parts. The exact filename is
247 252 # specified inside the "docket" file.
248 253 #
249 254 # The docket file contains information to find, qualify and validate the raw
250 255 # data. Its content is currently very light, but it will expand as the on disk
251 256 # nodemap gains the necessary features to be used in production.
252 257
253 258 # version 0 is experimental, no BC garantee, do no use outside of tests.
254 259 ONDISK_VERSION = 0
255 260 S_VERSION = struct.Struct(">B")
256 261 S_HEADER = struct.Struct(">BQQQQ")
257 262
258 263 ID_SIZE = 8
259 264
260 265
261 266 def _make_uid():
262 267 """return a new unique identifier.
263 268
264 269 The identifier is random and composed of ascii characters."""
265 270 return nodemod.hex(os.urandom(ID_SIZE))
266 271
267 272
268 273 class NodeMapDocket(object):
269 274 """metadata associated with persistent nodemap data
270 275
271 276 The persistent data may come from disk or be on their way to disk.
272 277 """
273 278
274 279 def __init__(self, uid=None):
275 280 if uid is None:
276 281 uid = _make_uid()
277 282 # a unique identifier for the data file:
278 283 # - When new data are appended, it is preserved.
279 284 # - When a new data file is created, a new identifier is generated.
280 285 self.uid = uid
281 286 # the tipmost revision stored in the data file. This revision and all
282 287 # revision before it are expected to be encoded in the data file.
283 288 self.tip_rev = None
284 289 # the node of that tipmost revision, if it mismatch the current index
285 290 # data the docket is not valid for the current index and should be
286 291 # discarded.
287 292 #
288 293 # note: this method is not perfect as some destructive operation could
289 294 # preserve the same tip_rev + tip_node while altering lower revision.
290 295 # However this multiple other caches have the same vulnerability (eg:
291 296 # brancmap cache).
292 297 self.tip_node = None
293 298 # the size (in bytes) of the persisted data to encode the nodemap valid
294 299 # for `tip_rev`.
295 300 # - data file shorter than this are corrupted,
296 301 # - any extra data should be ignored.
297 302 self.data_length = None
298 303 # the amount (in bytes) of "dead" data, still in the data file but no
299 304 # longer used for the nodemap.
300 305 self.data_unused = 0
301 306
302 307 def copy(self):
303 308 new = NodeMapDocket(uid=self.uid)
304 309 new.tip_rev = self.tip_rev
305 310 new.tip_node = self.tip_node
306 311 new.data_length = self.data_length
307 312 new.data_unused = self.data_unused
308 313 return new
309 314
310 315 def __cmp__(self, other):
311 316 if self.uid < other.uid:
312 317 return -1
313 318 if self.uid > other.uid:
314 319 return 1
315 320 elif self.data_length < other.data_length:
316 321 return -1
317 322 elif self.data_length > other.data_length:
318 323 return 1
319 324 return 0
320 325
321 326 def __eq__(self, other):
322 327 return self.uid == other.uid and self.data_length == other.data_length
323 328
324 329 def serialize(self):
325 330 """return serialized bytes for a docket using the passed uid"""
326 331 data = []
327 332 data.append(S_VERSION.pack(ONDISK_VERSION))
328 333 headers = (
329 334 len(self.uid),
330 335 self.tip_rev,
331 336 self.data_length,
332 337 self.data_unused,
333 338 len(self.tip_node),
334 339 )
335 340 data.append(S_HEADER.pack(*headers))
336 341 data.append(self.uid)
337 342 data.append(self.tip_node)
338 343 return b''.join(data)
339 344
340 345
341 346 def _rawdata_filepath(revlog, docket):
342 347 """The (vfs relative) nodemap's rawdata file for a given uid"""
343 348 if revlog.nodemap_file.endswith(b'.n.a'):
344 349 prefix = revlog.nodemap_file[:-4]
345 350 else:
346 351 prefix = revlog.nodemap_file[:-2]
347 352 return b"%s-%s.nd" % (prefix, docket.uid)
348 353
349 354
350 355 def _other_rawdata_filepath(revlog, docket):
351 356 prefix = revlog.nodemap_file[:-2]
352 357 pattern = re.compile(br"(^|/)%s-[0-9a-f]+\.nd$" % prefix)
353 358 new_file_path = _rawdata_filepath(revlog, docket)
354 359 new_file_name = revlog.opener.basename(new_file_path)
355 360 dirpath = revlog.opener.dirname(new_file_path)
356 361 others = []
357 362 for f in revlog.opener.listdir(dirpath):
358 363 if pattern.match(f) and f != new_file_name:
359 364 others.append(f)
360 365 return others
361 366
362 367
363 368 ### Nodemap Trie
364 369 #
365 370 # This is a simple reference implementation to compute and persist a nodemap
366 371 # trie. This reference implementation is write only. The python version of this
367 372 # is not expected to be actually used, since it wont provide performance
368 373 # improvement over existing non-persistent C implementation.
369 374 #
370 375 # The nodemap is persisted as Trie using 4bits-address/16-entries block. each
371 376 # revision can be adressed using its node shortest prefix.
372 377 #
373 378 # The trie is stored as a sequence of block. Each block contains 16 entries
374 379 # (signed 64bit integer, big endian). Each entry can be one of the following:
375 380 #
376 381 # * value >= 0 -> index of sub-block
377 382 # * value == -1 -> no value
378 383 # * value < -1 -> a revision value: rev = -(value+10)
379 384 #
380 385 # The implementation focus on simplicity, not on performance. A Rust
381 386 # implementation should provide a efficient version of the same binary
382 387 # persistence. This reference python implementation is never meant to be
383 388 # extensively use in production.
384 389
385 390
386 391 def persistent_data(index):
387 392 """return the persistent binary form for a nodemap for a given index
388 393 """
389 394 trie = _build_trie(index)
390 395 return _persist_trie(trie)
391 396
392 397
393 398 def update_persistent_data(index, root, max_idx, last_rev):
394 399 """return the incremental update for persistent nodemap from a given index
395 400 """
396 401 changed_block, trie = _update_trie(index, root, last_rev)
397 402 return (
398 403 changed_block * S_BLOCK.size,
399 404 _persist_trie(trie, existing_idx=max_idx),
400 405 )
401 406
402 407
403 408 S_BLOCK = struct.Struct(">" + ("l" * 16))
404 409
405 410 NO_ENTRY = -1
406 411 # rev 0 need to be -2 because 0 is used by block, -1 is a special value.
407 412 REV_OFFSET = 2
408 413
409 414
410 415 def _transform_rev(rev):
411 416 """Return the number used to represent the rev in the tree.
412 417
413 418 (or retrieve a rev number from such representation)
414 419
415 420 Note that this is an involution, a function equal to its inverse (i.e.
416 421 which gives the identity when applied to itself).
417 422 """
418 423 return -(rev + REV_OFFSET)
419 424
420 425
421 426 def _to_int(hex_digit):
422 427 """turn an hexadecimal digit into a proper integer"""
423 428 return int(hex_digit, 16)
424 429
425 430
426 431 class Block(dict):
427 432 """represent a block of the Trie
428 433
429 434 contains up to 16 entry indexed from 0 to 15"""
430 435
431 436 def __init__(self):
432 437 super(Block, self).__init__()
433 438 # If this block exist on disk, here is its ID
434 439 self.ondisk_id = None
435 440
436 441 def __iter__(self):
437 442 return iter(self.get(i) for i in range(16))
438 443
439 444
440 445 def _build_trie(index):
441 446 """build a nodemap trie
442 447
443 448 The nodemap stores revision number for each unique prefix.
444 449
445 450 Each block is a dictionary with keys in `[0, 15]`. Values are either
446 451 another block or a revision number.
447 452 """
448 453 root = Block()
449 454 for rev in range(len(index)):
450 455 hex = nodemod.hex(index[rev][7])
451 456 _insert_into_block(index, 0, root, rev, hex)
452 457 return root
453 458
454 459
455 460 def _update_trie(index, root, last_rev):
456 461 """consume"""
457 462 changed = 0
458 463 for rev in range(last_rev + 1, len(index)):
459 464 hex = nodemod.hex(index[rev][7])
460 465 changed += _insert_into_block(index, 0, root, rev, hex)
461 466 return changed, root
462 467
463 468
464 469 def _insert_into_block(index, level, block, current_rev, current_hex):
465 470 """insert a new revision in a block
466 471
467 472 index: the index we are adding revision for
468 473 level: the depth of the current block in the trie
469 474 block: the block currently being considered
470 475 current_rev: the revision number we are adding
471 476 current_hex: the hexadecimal representation of the of that revision
472 477 """
473 478 changed = 1
474 479 if block.ondisk_id is not None:
475 480 block.ondisk_id = None
476 481 hex_digit = _to_int(current_hex[level : level + 1])
477 482 entry = block.get(hex_digit)
478 483 if entry is None:
479 484 # no entry, simply store the revision number
480 485 block[hex_digit] = current_rev
481 486 elif isinstance(entry, dict):
482 487 # need to recurse to an underlying block
483 488 changed += _insert_into_block(
484 489 index, level + 1, entry, current_rev, current_hex
485 490 )
486 491 else:
487 492 # collision with a previously unique prefix, inserting new
488 493 # vertices to fit both entry.
489 494 other_hex = nodemod.hex(index[entry][7])
490 495 other_rev = entry
491 496 new = Block()
492 497 block[hex_digit] = new
493 498 _insert_into_block(index, level + 1, new, other_rev, other_hex)
494 499 _insert_into_block(index, level + 1, new, current_rev, current_hex)
495 500 return changed
496 501
497 502
498 503 def _persist_trie(root, existing_idx=None):
499 504 """turn a nodemap trie into persistent binary data
500 505
501 506 See `_build_trie` for nodemap trie structure"""
502 507 block_map = {}
503 508 if existing_idx is not None:
504 509 base_idx = existing_idx + 1
505 510 else:
506 511 base_idx = 0
507 512 chunks = []
508 513 for tn in _walk_trie(root):
509 514 if tn.ondisk_id is not None:
510 515 block_map[id(tn)] = tn.ondisk_id
511 516 else:
512 517 block_map[id(tn)] = len(chunks) + base_idx
513 518 chunks.append(_persist_block(tn, block_map))
514 519 return b''.join(chunks)
515 520
516 521
517 522 def _walk_trie(block):
518 523 """yield all the block in a trie
519 524
520 525 Children blocks are always yield before their parent block.
521 526 """
522 527 for (__, item) in sorted(block.items()):
523 528 if isinstance(item, dict):
524 529 for sub_block in _walk_trie(item):
525 530 yield sub_block
526 531 yield block
527 532
528 533
529 534 def _persist_block(block_node, block_map):
530 535 """produce persistent binary data for a single block
531 536
532 537 Children block are assumed to be already persisted and present in
533 538 block_map.
534 539 """
535 540 data = tuple(_to_value(v, block_map) for v in block_node)
536 541 return S_BLOCK.pack(*data)
537 542
538 543
539 544 def _to_value(item, block_map):
540 545 """persist any value as an integer"""
541 546 if item is None:
542 547 return NO_ENTRY
543 548 elif isinstance(item, dict):
544 549 return block_map[id(item)]
545 550 else:
546 551 return _transform_rev(item)
547 552
548 553
549 554 def parse_data(data):
550 555 """parse parse nodemap data into a nodemap Trie"""
551 556 if (len(data) % S_BLOCK.size) != 0:
552 557 msg = "nodemap data size is not a multiple of block size (%d): %d"
553 558 raise error.Abort(msg % (S_BLOCK.size, len(data)))
554 559 if not data:
555 560 return Block(), None
556 561 block_map = {}
557 562 new_blocks = []
558 563 for i in range(0, len(data), S_BLOCK.size):
559 564 block = Block()
560 565 block.ondisk_id = len(block_map)
561 566 block_map[block.ondisk_id] = block
562 567 block_data = data[i : i + S_BLOCK.size]
563 568 values = S_BLOCK.unpack(block_data)
564 569 new_blocks.append((block, values))
565 570 for b, values in new_blocks:
566 571 for idx, v in enumerate(values):
567 572 if v == NO_ENTRY:
568 573 continue
569 574 elif v >= 0:
570 575 b[idx] = block_map[v]
571 576 else:
572 577 b[idx] = _transform_rev(v)
573 578 return block, i // S_BLOCK.size
574 579
575 580
576 581 # debug utility
577 582
578 583
579 584 def check_data(ui, index, data):
580 585 """verify that the provided nodemap data are valid for the given idex"""
581 586 ret = 0
582 587 ui.status((b"revision in index: %d\n") % len(index))
583 588 root, __ = parse_data(data)
584 589 all_revs = set(_all_revisions(root))
585 590 ui.status((b"revision in nodemap: %d\n") % len(all_revs))
586 591 for r in range(len(index)):
587 592 if r not in all_revs:
588 593 msg = b" revision missing from nodemap: %d\n" % r
589 594 ui.write_err(msg)
590 595 ret = 1
591 596 else:
592 597 all_revs.remove(r)
593 598 nm_rev = _find_node(root, nodemod.hex(index[r][7]))
594 599 if nm_rev is None:
595 600 msg = b" revision node does not match any entries: %d\n" % r
596 601 ui.write_err(msg)
597 602 ret = 1
598 603 elif nm_rev != r:
599 604 msg = (
600 605 b" revision node does not match the expected revision: "
601 606 b"%d != %d\n" % (r, nm_rev)
602 607 )
603 608 ui.write_err(msg)
604 609 ret = 1
605 610
606 611 if all_revs:
607 612 for r in sorted(all_revs):
608 613 msg = b" extra revision in nodemap: %d\n" % r
609 614 ui.write_err(msg)
610 615 ret = 1
611 616 return ret
612 617
613 618
614 619 def _all_revisions(root):
615 620 """return all revisions stored in a Trie"""
616 621 for block in _walk_trie(root):
617 622 for v in block:
618 623 if v is None or isinstance(v, Block):
619 624 continue
620 625 yield v
621 626
622 627
623 628 def _find_node(block, node):
624 629 """find the revision associated with a given node"""
625 630 entry = block.get(_to_int(node[0:1]))
626 631 if isinstance(entry, dict):
627 632 return _find_node(entry, node[1:])
628 633 return entry
@@ -1,1400 +1,1398 b''
1 1 # upgrade.py - functions for in place upgrade of Mercurial repository
2 2 #
3 3 # Copyright (c) 2016-present, Gregory Szorc
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 from __future__ import absolute_import
9 9
10 10 import stat
11 11
12 12 from .i18n import _
13 13 from .pycompat import getattr
14 14 from . import (
15 15 changelog,
16 16 copies,
17 17 error,
18 18 filelog,
19 19 hg,
20 20 localrepo,
21 21 manifest,
22 22 pycompat,
23 23 revlog,
24 24 scmutil,
25 25 util,
26 26 vfs as vfsmod,
27 27 )
28 28
29 29 from .utils import compression
30 30
31 31 # list of requirements that request a clone of all revlog if added/removed
32 32 RECLONES_REQUIREMENTS = {
33 33 b'generaldelta',
34 34 localrepo.SPARSEREVLOG_REQUIREMENT,
35 35 }
36 36
37 37
38 38 def requiredsourcerequirements(repo):
39 39 """Obtain requirements required to be present to upgrade a repo.
40 40
41 41 An upgrade will not be allowed if the repository doesn't have the
42 42 requirements returned by this function.
43 43 """
44 44 return {
45 45 # Introduced in Mercurial 0.9.2.
46 46 b'revlogv1',
47 47 # Introduced in Mercurial 0.9.2.
48 48 b'store',
49 49 }
50 50
51 51
52 52 def blocksourcerequirements(repo):
53 53 """Obtain requirements that will prevent an upgrade from occurring.
54 54
55 55 An upgrade cannot be performed if the source repository contains a
56 56 requirements in the returned set.
57 57 """
58 58 return {
59 59 # The upgrade code does not yet support these experimental features.
60 60 # This is an artificial limitation.
61 61 b'treemanifest',
62 62 # This was a precursor to generaldelta and was never enabled by default.
63 63 # It should (hopefully) not exist in the wild.
64 64 b'parentdelta',
65 65 # Upgrade should operate on the actual store, not the shared link.
66 66 b'shared',
67 67 }
68 68
69 69
70 70 def supportremovedrequirements(repo):
71 71 """Obtain requirements that can be removed during an upgrade.
72 72
73 73 If an upgrade were to create a repository that dropped a requirement,
74 74 the dropped requirement must appear in the returned set for the upgrade
75 75 to be allowed.
76 76 """
77 77 supported = {
78 78 localrepo.SPARSEREVLOG_REQUIREMENT,
79 79 localrepo.SIDEDATA_REQUIREMENT,
80 80 localrepo.COPIESSDC_REQUIREMENT,
81 81 }
82 82 for name in compression.compengines:
83 83 engine = compression.compengines[name]
84 84 if engine.available() and engine.revlogheader():
85 85 supported.add(b'exp-compression-%s' % name)
86 86 if engine.name() == b'zstd':
87 87 supported.add(b'revlog-compression-zstd')
88 88 return supported
89 89
90 90
91 91 def supporteddestrequirements(repo):
92 92 """Obtain requirements that upgrade supports in the destination.
93 93
94 94 If the result of the upgrade would create requirements not in this set,
95 95 the upgrade is disallowed.
96 96
97 97 Extensions should monkeypatch this to add their custom requirements.
98 98 """
99 99 supported = {
100 100 b'dotencode',
101 101 b'fncache',
102 102 b'generaldelta',
103 103 b'revlogv1',
104 104 b'store',
105 105 localrepo.SPARSEREVLOG_REQUIREMENT,
106 106 localrepo.SIDEDATA_REQUIREMENT,
107 107 localrepo.COPIESSDC_REQUIREMENT,
108 108 }
109 109 for name in compression.compengines:
110 110 engine = compression.compengines[name]
111 111 if engine.available() and engine.revlogheader():
112 112 supported.add(b'exp-compression-%s' % name)
113 113 if engine.name() == b'zstd':
114 114 supported.add(b'revlog-compression-zstd')
115 115 return supported
116 116
117 117
118 118 def allowednewrequirements(repo):
119 119 """Obtain requirements that can be added to a repository during upgrade.
120 120
121 121 This is used to disallow proposed requirements from being added when
122 122 they weren't present before.
123 123
124 124 We use a list of allowed requirement additions instead of a list of known
125 125 bad additions because the whitelist approach is safer and will prevent
126 126 future, unknown requirements from accidentally being added.
127 127 """
128 128 supported = {
129 129 b'dotencode',
130 130 b'fncache',
131 131 b'generaldelta',
132 132 localrepo.SPARSEREVLOG_REQUIREMENT,
133 133 localrepo.SIDEDATA_REQUIREMENT,
134 134 localrepo.COPIESSDC_REQUIREMENT,
135 135 }
136 136 for name in compression.compengines:
137 137 engine = compression.compengines[name]
138 138 if engine.available() and engine.revlogheader():
139 139 supported.add(b'exp-compression-%s' % name)
140 140 if engine.name() == b'zstd':
141 141 supported.add(b'revlog-compression-zstd')
142 142 return supported
143 143
144 144
145 145 def preservedrequirements(repo):
146 146 return set()
147 147
148 148
149 149 deficiency = b'deficiency'
150 150 optimisation = b'optimization'
151 151
152 152
153 153 class improvement(object):
154 154 """Represents an improvement that can be made as part of an upgrade.
155 155
156 156 The following attributes are defined on each instance:
157 157
158 158 name
159 159 Machine-readable string uniquely identifying this improvement. It
160 160 will be mapped to an action later in the upgrade process.
161 161
162 162 type
163 163 Either ``deficiency`` or ``optimisation``. A deficiency is an obvious
164 164 problem. An optimization is an action (sometimes optional) that
165 165 can be taken to further improve the state of the repository.
166 166
167 167 description
168 168 Message intended for humans explaining the improvement in more detail,
169 169 including the implications of it. For ``deficiency`` types, should be
170 170 worded in the present tense. For ``optimisation`` types, should be
171 171 worded in the future tense.
172 172
173 173 upgrademessage
174 174 Message intended for humans explaining what an upgrade addressing this
175 175 issue will do. Should be worded in the future tense.
176 176 """
177 177
178 178 def __init__(self, name, type, description, upgrademessage):
179 179 self.name = name
180 180 self.type = type
181 181 self.description = description
182 182 self.upgrademessage = upgrademessage
183 183
184 184 def __eq__(self, other):
185 185 if not isinstance(other, improvement):
186 186 # This is what python tell use to do
187 187 return NotImplemented
188 188 return self.name == other.name
189 189
190 190 def __ne__(self, other):
191 191 return not (self == other)
192 192
193 193 def __hash__(self):
194 194 return hash(self.name)
195 195
196 196
197 197 allformatvariant = []
198 198
199 199
200 200 def registerformatvariant(cls):
201 201 allformatvariant.append(cls)
202 202 return cls
203 203
204 204
205 205 class formatvariant(improvement):
206 206 """an improvement subclass dedicated to repository format"""
207 207
208 208 type = deficiency
209 209 ### The following attributes should be defined for each class:
210 210
211 211 # machine-readable string uniquely identifying this improvement. it will be
212 212 # mapped to an action later in the upgrade process.
213 213 name = None
214 214
215 215 # message intended for humans explaining the improvement in more detail,
216 216 # including the implications of it ``deficiency`` types, should be worded
217 217 # in the present tense.
218 218 description = None
219 219
220 220 # message intended for humans explaining what an upgrade addressing this
221 221 # issue will do. should be worded in the future tense.
222 222 upgrademessage = None
223 223
224 224 # value of current Mercurial default for new repository
225 225 default = None
226 226
227 227 def __init__(self):
228 228 raise NotImplementedError()
229 229
230 230 @staticmethod
231 231 def fromrepo(repo):
232 232 """current value of the variant in the repository"""
233 233 raise NotImplementedError()
234 234
235 235 @staticmethod
236 236 def fromconfig(repo):
237 237 """current value of the variant in the configuration"""
238 238 raise NotImplementedError()
239 239
240 240
241 241 class requirementformatvariant(formatvariant):
242 242 """formatvariant based on a 'requirement' name.
243 243
244 244 Many format variant are controlled by a 'requirement'. We define a small
245 245 subclass to factor the code.
246 246 """
247 247
248 248 # the requirement that control this format variant
249 249 _requirement = None
250 250
251 251 @staticmethod
252 252 def _newreporequirements(ui):
253 253 return localrepo.newreporequirements(
254 254 ui, localrepo.defaultcreateopts(ui)
255 255 )
256 256
257 257 @classmethod
258 258 def fromrepo(cls, repo):
259 259 assert cls._requirement is not None
260 260 return cls._requirement in repo.requirements
261 261
262 262 @classmethod
263 263 def fromconfig(cls, repo):
264 264 assert cls._requirement is not None
265 265 return cls._requirement in cls._newreporequirements(repo.ui)
266 266
267 267
268 268 @registerformatvariant
269 269 class fncache(requirementformatvariant):
270 270 name = b'fncache'
271 271
272 272 _requirement = b'fncache'
273 273
274 274 default = True
275 275
276 276 description = _(
277 277 b'long and reserved filenames may not work correctly; '
278 278 b'repository performance is sub-optimal'
279 279 )
280 280
281 281 upgrademessage = _(
282 282 b'repository will be more resilient to storing '
283 283 b'certain paths and performance of certain '
284 284 b'operations should be improved'
285 285 )
286 286
287 287
288 288 @registerformatvariant
289 289 class dotencode(requirementformatvariant):
290 290 name = b'dotencode'
291 291
292 292 _requirement = b'dotencode'
293 293
294 294 default = True
295 295
296 296 description = _(
297 297 b'storage of filenames beginning with a period or '
298 298 b'space may not work correctly'
299 299 )
300 300
301 301 upgrademessage = _(
302 302 b'repository will be better able to store files '
303 303 b'beginning with a space or period'
304 304 )
305 305
306 306
307 307 @registerformatvariant
308 308 class generaldelta(requirementformatvariant):
309 309 name = b'generaldelta'
310 310
311 311 _requirement = b'generaldelta'
312 312
313 313 default = True
314 314
315 315 description = _(
316 316 b'deltas within internal storage are unable to '
317 317 b'choose optimal revisions; repository is larger and '
318 318 b'slower than it could be; interaction with other '
319 319 b'repositories may require extra network and CPU '
320 320 b'resources, making "hg push" and "hg pull" slower'
321 321 )
322 322
323 323 upgrademessage = _(
324 324 b'repository storage will be able to create '
325 325 b'optimal deltas; new repository data will be '
326 326 b'smaller and read times should decrease; '
327 327 b'interacting with other repositories using this '
328 328 b'storage model should require less network and '
329 329 b'CPU resources, making "hg push" and "hg pull" '
330 330 b'faster'
331 331 )
332 332
333 333
334 334 @registerformatvariant
335 335 class sparserevlog(requirementformatvariant):
336 336 name = b'sparserevlog'
337 337
338 338 _requirement = localrepo.SPARSEREVLOG_REQUIREMENT
339 339
340 340 default = True
341 341
342 342 description = _(
343 343 b'in order to limit disk reading and memory usage on older '
344 344 b'version, the span of a delta chain from its root to its '
345 345 b'end is limited, whatever the relevant data in this span. '
346 346 b'This can severly limit Mercurial ability to build good '
347 347 b'chain of delta resulting is much more storage space being '
348 348 b'taken and limit reusability of on disk delta during '
349 349 b'exchange.'
350 350 )
351 351
352 352 upgrademessage = _(
353 353 b'Revlog supports delta chain with more unused data '
354 354 b'between payload. These gaps will be skipped at read '
355 355 b'time. This allows for better delta chains, making a '
356 356 b'better compression and faster exchange with server.'
357 357 )
358 358
359 359
360 360 @registerformatvariant
361 361 class sidedata(requirementformatvariant):
362 362 name = b'sidedata'
363 363
364 364 _requirement = localrepo.SIDEDATA_REQUIREMENT
365 365
366 366 default = False
367 367
368 368 description = _(
369 369 b'Allows storage of extra data alongside a revision, '
370 370 b'unlocking various caching options.'
371 371 )
372 372
373 373 upgrademessage = _(b'Allows storage of extra data alongside a revision.')
374 374
375 375
376 376 @registerformatvariant
377 377 class copiessdc(requirementformatvariant):
378 378 name = b'copies-sdc'
379 379
380 380 _requirement = localrepo.COPIESSDC_REQUIREMENT
381 381
382 382 default = False
383 383
384 384 description = _(b'Stores copies information alongside changesets.')
385 385
386 386 upgrademessage = _(
387 387 b'Allows to use more efficient algorithm to deal with ' b'copy tracing.'
388 388 )
389 389
390 390
391 391 @registerformatvariant
392 392 class removecldeltachain(formatvariant):
393 393 name = b'plain-cl-delta'
394 394
395 395 default = True
396 396
397 397 description = _(
398 398 b'changelog storage is using deltas instead of '
399 399 b'raw entries; changelog reading and any '
400 400 b'operation relying on changelog data are slower '
401 401 b'than they could be'
402 402 )
403 403
404 404 upgrademessage = _(
405 405 b'changelog storage will be reformated to '
406 406 b'store raw entries; changelog reading will be '
407 407 b'faster; changelog size may be reduced'
408 408 )
409 409
410 410 @staticmethod
411 411 def fromrepo(repo):
412 412 # Mercurial 4.0 changed changelogs to not use delta chains. Search for
413 413 # changelogs with deltas.
414 414 cl = repo.changelog
415 415 chainbase = cl.chainbase
416 416 return all(rev == chainbase(rev) for rev in cl)
417 417
418 418 @staticmethod
419 419 def fromconfig(repo):
420 420 return True
421 421
422 422
423 423 @registerformatvariant
424 424 class compressionengine(formatvariant):
425 425 name = b'compression'
426 426 default = b'zlib'
427 427
428 428 description = _(
429 429 b'Compresion algorithm used to compress data. '
430 430 b'Some engine are faster than other'
431 431 )
432 432
433 433 upgrademessage = _(
434 434 b'revlog content will be recompressed with the new algorithm.'
435 435 )
436 436
437 437 @classmethod
438 438 def fromrepo(cls, repo):
439 439 # we allow multiple compression engine requirement to co-exist because
440 440 # strickly speaking, revlog seems to support mixed compression style.
441 441 #
442 442 # The compression used for new entries will be "the last one"
443 443 compression = b'zlib'
444 444 for req in repo.requirements:
445 445 prefix = req.startswith
446 446 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
447 447 compression = req.split(b'-', 2)[2]
448 448 return compression
449 449
450 450 @classmethod
451 451 def fromconfig(cls, repo):
452 452 compengines = repo.ui.configlist(b'format', b'revlog-compression')
453 453 # return the first valid value as the selection code would do
454 454 for comp in compengines:
455 455 if comp in util.compengines:
456 456 return comp
457 457
458 458 # no valide compression found lets display it all for clarity
459 459 return b','.join(compengines)
460 460
461 461
462 462 @registerformatvariant
463 463 class compressionlevel(formatvariant):
464 464 name = b'compression-level'
465 465 default = b'default'
466 466
467 467 description = _(b'compression level')
468 468
469 469 upgrademessage = _(b'revlog content will be recompressed')
470 470
471 471 @classmethod
472 472 def fromrepo(cls, repo):
473 473 comp = compressionengine.fromrepo(repo)
474 474 level = None
475 475 if comp == b'zlib':
476 476 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
477 477 elif comp == b'zstd':
478 478 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
479 479 if level is None:
480 480 return b'default'
481 481 return bytes(level)
482 482
483 483 @classmethod
484 484 def fromconfig(cls, repo):
485 485 comp = compressionengine.fromconfig(repo)
486 486 level = None
487 487 if comp == b'zlib':
488 488 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
489 489 elif comp == b'zstd':
490 490 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
491 491 if level is None:
492 492 return b'default'
493 493 return bytes(level)
494 494
495 495
496 496 def finddeficiencies(repo):
497 497 """returns a list of deficiencies that the repo suffer from"""
498 498 deficiencies = []
499 499
500 500 # We could detect lack of revlogv1 and store here, but they were added
501 501 # in 0.9.2 and we don't support upgrading repos without these
502 502 # requirements, so let's not bother.
503 503
504 504 for fv in allformatvariant:
505 505 if not fv.fromrepo(repo):
506 506 deficiencies.append(fv)
507 507
508 508 return deficiencies
509 509
510 510
511 511 # search without '-' to support older form on newer client.
512 512 #
513 513 # We don't enforce backward compatibility for debug command so this
514 514 # might eventually be dropped. However, having to use two different
515 515 # forms in script when comparing result is anoying enough to add
516 516 # backward compatibility for a while.
517 517 legacy_opts_map = {
518 518 b'redeltaparent': b're-delta-parent',
519 519 b'redeltamultibase': b're-delta-multibase',
520 520 b'redeltaall': b're-delta-all',
521 521 b'redeltafulladd': b're-delta-fulladd',
522 522 }
523 523
524 524
525 525 def findoptimizations(repo):
526 526 """Determine optimisation that could be used during upgrade"""
527 527 # These are unconditionally added. There is logic later that figures out
528 528 # which ones to apply.
529 529 optimizations = []
530 530
531 531 optimizations.append(
532 532 improvement(
533 533 name=b're-delta-parent',
534 534 type=optimisation,
535 535 description=_(
536 536 b'deltas within internal storage will be recalculated to '
537 537 b'choose an optimal base revision where this was not '
538 538 b'already done; the size of the repository may shrink and '
539 539 b'various operations may become faster; the first time '
540 540 b'this optimization is performed could slow down upgrade '
541 541 b'execution considerably; subsequent invocations should '
542 542 b'not run noticeably slower'
543 543 ),
544 544 upgrademessage=_(
545 545 b'deltas within internal storage will choose a new '
546 546 b'base revision if needed'
547 547 ),
548 548 )
549 549 )
550 550
551 551 optimizations.append(
552 552 improvement(
553 553 name=b're-delta-multibase',
554 554 type=optimisation,
555 555 description=_(
556 556 b'deltas within internal storage will be recalculated '
557 557 b'against multiple base revision and the smallest '
558 558 b'difference will be used; the size of the repository may '
559 559 b'shrink significantly when there are many merges; this '
560 560 b'optimization will slow down execution in proportion to '
561 561 b'the number of merges in the repository and the amount '
562 562 b'of files in the repository; this slow down should not '
563 563 b'be significant unless there are tens of thousands of '
564 564 b'files and thousands of merges'
565 565 ),
566 566 upgrademessage=_(
567 567 b'deltas within internal storage will choose an '
568 568 b'optimal delta by computing deltas against multiple '
569 569 b'parents; may slow down execution time '
570 570 b'significantly'
571 571 ),
572 572 )
573 573 )
574 574
575 575 optimizations.append(
576 576 improvement(
577 577 name=b're-delta-all',
578 578 type=optimisation,
579 579 description=_(
580 580 b'deltas within internal storage will always be '
581 581 b'recalculated without reusing prior deltas; this will '
582 582 b'likely make execution run several times slower; this '
583 583 b'optimization is typically not needed'
584 584 ),
585 585 upgrademessage=_(
586 586 b'deltas within internal storage will be fully '
587 587 b'recomputed; this will likely drastically slow down '
588 588 b'execution time'
589 589 ),
590 590 )
591 591 )
592 592
593 593 optimizations.append(
594 594 improvement(
595 595 name=b're-delta-fulladd',
596 596 type=optimisation,
597 597 description=_(
598 598 b'every revision will be re-added as if it was new '
599 599 b'content. It will go through the full storage '
600 600 b'mechanism giving extensions a chance to process it '
601 601 b'(eg. lfs). This is similar to "re-delta-all" but even '
602 602 b'slower since more logic is involved.'
603 603 ),
604 604 upgrademessage=_(
605 605 b'each revision will be added as new content to the '
606 606 b'internal storage; this will likely drastically slow '
607 607 b'down execution time, but some extensions might need '
608 608 b'it'
609 609 ),
610 610 )
611 611 )
612 612
613 613 return optimizations
614 614
615 615
616 616 def determineactions(repo, deficiencies, sourcereqs, destreqs):
617 617 """Determine upgrade actions that will be performed.
618 618
619 619 Given a list of improvements as returned by ``finddeficiencies`` and
620 620 ``findoptimizations``, determine the list of upgrade actions that
621 621 will be performed.
622 622
623 623 The role of this function is to filter improvements if needed, apply
624 624 recommended optimizations from the improvements list that make sense,
625 625 etc.
626 626
627 627 Returns a list of action names.
628 628 """
629 629 newactions = []
630 630
631 knownreqs = supporteddestrequirements(repo)
632
633 631 for d in deficiencies:
634 name = d.name
632 name = d._requirement
635 633
636 634 # If the action is a requirement that doesn't show up in the
637 635 # destination requirements, prune the action.
638 if name in knownreqs and name not in destreqs:
636 if name is not None and name not in destreqs:
639 637 continue
640 638
641 639 newactions.append(d)
642 640
643 641 # FUTURE consider adding some optimizations here for certain transitions.
644 642 # e.g. adding generaldelta could schedule parent redeltas.
645 643
646 644 return newactions
647 645
648 646
649 647 def _revlogfrompath(repo, path):
650 648 """Obtain a revlog from a repo path.
651 649
652 650 An instance of the appropriate class is returned.
653 651 """
654 652 if path == b'00changelog.i':
655 653 return changelog.changelog(repo.svfs)
656 654 elif path.endswith(b'00manifest.i'):
657 655 mandir = path[: -len(b'00manifest.i')]
658 656 return manifest.manifestrevlog(repo.svfs, tree=mandir)
659 657 else:
660 658 # reverse of "/".join(("data", path + ".i"))
661 659 return filelog.filelog(repo.svfs, path[5:-2])
662 660
663 661
664 662 def _copyrevlog(tr, destrepo, oldrl, unencodedname):
665 663 """copy all relevant files for `oldrl` into `destrepo` store
666 664
667 665 Files are copied "as is" without any transformation. The copy is performed
668 666 without extra checks. Callers are responsible for making sure the copied
669 667 content is compatible with format of the destination repository.
670 668 """
671 669 oldrl = getattr(oldrl, '_revlog', oldrl)
672 670 newrl = _revlogfrompath(destrepo, unencodedname)
673 671 newrl = getattr(newrl, '_revlog', newrl)
674 672
675 673 oldvfs = oldrl.opener
676 674 newvfs = newrl.opener
677 675 oldindex = oldvfs.join(oldrl.indexfile)
678 676 newindex = newvfs.join(newrl.indexfile)
679 677 olddata = oldvfs.join(oldrl.datafile)
680 678 newdata = newvfs.join(newrl.datafile)
681 679
682 680 with newvfs(newrl.indexfile, b'w'):
683 681 pass # create all the directories
684 682
685 683 util.copyfile(oldindex, newindex)
686 684 copydata = oldrl.opener.exists(oldrl.datafile)
687 685 if copydata:
688 686 util.copyfile(olddata, newdata)
689 687
690 688 if not (
691 689 unencodedname.endswith(b'00changelog.i')
692 690 or unencodedname.endswith(b'00manifest.i')
693 691 ):
694 692 destrepo.svfs.fncache.add(unencodedname)
695 693 if copydata:
696 694 destrepo.svfs.fncache.add(unencodedname[:-2] + b'.d')
697 695
698 696
699 697 UPGRADE_CHANGELOG = object()
700 698 UPGRADE_MANIFEST = object()
701 699 UPGRADE_FILELOG = object()
702 700
703 701 UPGRADE_ALL_REVLOGS = frozenset(
704 702 [UPGRADE_CHANGELOG, UPGRADE_MANIFEST, UPGRADE_FILELOG]
705 703 )
706 704
707 705
708 706 def getsidedatacompanion(srcrepo, dstrepo):
709 707 sidedatacompanion = None
710 708 removedreqs = srcrepo.requirements - dstrepo.requirements
711 709 addedreqs = dstrepo.requirements - srcrepo.requirements
712 710 if localrepo.SIDEDATA_REQUIREMENT in removedreqs:
713 711
714 712 def sidedatacompanion(rl, rev):
715 713 rl = getattr(rl, '_revlog', rl)
716 714 if rl.flags(rev) & revlog.REVIDX_SIDEDATA:
717 715 return True, (), {}
718 716 return False, (), {}
719 717
720 718 elif localrepo.COPIESSDC_REQUIREMENT in addedreqs:
721 719 sidedatacompanion = copies.getsidedataadder(srcrepo, dstrepo)
722 720 elif localrepo.COPIESSDC_REQUIREMENT in removedreqs:
723 721 sidedatacompanion = copies.getsidedataremover(srcrepo, dstrepo)
724 722 return sidedatacompanion
725 723
726 724
727 725 def matchrevlog(revlogfilter, entry):
728 726 """check is a revlog is selected for cloning
729 727
730 728 The store entry is checked against the passed filter"""
731 729 if entry.endswith(b'00changelog.i'):
732 730 return UPGRADE_CHANGELOG in revlogfilter
733 731 elif entry.endswith(b'00manifest.i'):
734 732 return UPGRADE_MANIFEST in revlogfilter
735 733 return UPGRADE_FILELOG in revlogfilter
736 734
737 735
738 736 def _clonerevlogs(
739 737 ui,
740 738 srcrepo,
741 739 dstrepo,
742 740 tr,
743 741 deltareuse,
744 742 forcedeltabothparents,
745 743 revlogs=UPGRADE_ALL_REVLOGS,
746 744 ):
747 745 """Copy revlogs between 2 repos."""
748 746 revcount = 0
749 747 srcsize = 0
750 748 srcrawsize = 0
751 749 dstsize = 0
752 750 fcount = 0
753 751 frevcount = 0
754 752 fsrcsize = 0
755 753 frawsize = 0
756 754 fdstsize = 0
757 755 mcount = 0
758 756 mrevcount = 0
759 757 msrcsize = 0
760 758 mrawsize = 0
761 759 mdstsize = 0
762 760 crevcount = 0
763 761 csrcsize = 0
764 762 crawsize = 0
765 763 cdstsize = 0
766 764
767 765 alldatafiles = list(srcrepo.store.walk())
768 766
769 767 # Perform a pass to collect metadata. This validates we can open all
770 768 # source files and allows a unified progress bar to be displayed.
771 769 for unencoded, encoded, size in alldatafiles:
772 770 if unencoded.endswith(b'.d'):
773 771 continue
774 772
775 773 rl = _revlogfrompath(srcrepo, unencoded)
776 774
777 775 info = rl.storageinfo(
778 776 exclusivefiles=True,
779 777 revisionscount=True,
780 778 trackedsize=True,
781 779 storedsize=True,
782 780 )
783 781
784 782 revcount += info[b'revisionscount'] or 0
785 783 datasize = info[b'storedsize'] or 0
786 784 rawsize = info[b'trackedsize'] or 0
787 785
788 786 srcsize += datasize
789 787 srcrawsize += rawsize
790 788
791 789 # This is for the separate progress bars.
792 790 if isinstance(rl, changelog.changelog):
793 791 crevcount += len(rl)
794 792 csrcsize += datasize
795 793 crawsize += rawsize
796 794 elif isinstance(rl, manifest.manifestrevlog):
797 795 mcount += 1
798 796 mrevcount += len(rl)
799 797 msrcsize += datasize
800 798 mrawsize += rawsize
801 799 elif isinstance(rl, filelog.filelog):
802 800 fcount += 1
803 801 frevcount += len(rl)
804 802 fsrcsize += datasize
805 803 frawsize += rawsize
806 804 else:
807 805 error.ProgrammingError(b'unknown revlog type')
808 806
809 807 if not revcount:
810 808 return
811 809
812 810 ui.write(
813 811 _(
814 812 b'migrating %d total revisions (%d in filelogs, %d in manifests, '
815 813 b'%d in changelog)\n'
816 814 )
817 815 % (revcount, frevcount, mrevcount, crevcount)
818 816 )
819 817 ui.write(
820 818 _(b'migrating %s in store; %s tracked data\n')
821 819 % ((util.bytecount(srcsize), util.bytecount(srcrawsize)))
822 820 )
823 821
824 822 # Used to keep track of progress.
825 823 progress = None
826 824
827 825 def oncopiedrevision(rl, rev, node):
828 826 progress.increment()
829 827
830 828 sidedatacompanion = getsidedatacompanion(srcrepo, dstrepo)
831 829
832 830 # Do the actual copying.
833 831 # FUTURE this operation can be farmed off to worker processes.
834 832 seen = set()
835 833 for unencoded, encoded, size in alldatafiles:
836 834 if unencoded.endswith(b'.d'):
837 835 continue
838 836
839 837 oldrl = _revlogfrompath(srcrepo, unencoded)
840 838
841 839 if isinstance(oldrl, changelog.changelog) and b'c' not in seen:
842 840 ui.write(
843 841 _(
844 842 b'finished migrating %d manifest revisions across %d '
845 843 b'manifests; change in size: %s\n'
846 844 )
847 845 % (mrevcount, mcount, util.bytecount(mdstsize - msrcsize))
848 846 )
849 847
850 848 ui.write(
851 849 _(
852 850 b'migrating changelog containing %d revisions '
853 851 b'(%s in store; %s tracked data)\n'
854 852 )
855 853 % (
856 854 crevcount,
857 855 util.bytecount(csrcsize),
858 856 util.bytecount(crawsize),
859 857 )
860 858 )
861 859 seen.add(b'c')
862 860 progress = srcrepo.ui.makeprogress(
863 861 _(b'changelog revisions'), total=crevcount
864 862 )
865 863 elif isinstance(oldrl, manifest.manifestrevlog) and b'm' not in seen:
866 864 ui.write(
867 865 _(
868 866 b'finished migrating %d filelog revisions across %d '
869 867 b'filelogs; change in size: %s\n'
870 868 )
871 869 % (frevcount, fcount, util.bytecount(fdstsize - fsrcsize))
872 870 )
873 871
874 872 ui.write(
875 873 _(
876 874 b'migrating %d manifests containing %d revisions '
877 875 b'(%s in store; %s tracked data)\n'
878 876 )
879 877 % (
880 878 mcount,
881 879 mrevcount,
882 880 util.bytecount(msrcsize),
883 881 util.bytecount(mrawsize),
884 882 )
885 883 )
886 884 seen.add(b'm')
887 885 if progress:
888 886 progress.complete()
889 887 progress = srcrepo.ui.makeprogress(
890 888 _(b'manifest revisions'), total=mrevcount
891 889 )
892 890 elif b'f' not in seen:
893 891 ui.write(
894 892 _(
895 893 b'migrating %d filelogs containing %d revisions '
896 894 b'(%s in store; %s tracked data)\n'
897 895 )
898 896 % (
899 897 fcount,
900 898 frevcount,
901 899 util.bytecount(fsrcsize),
902 900 util.bytecount(frawsize),
903 901 )
904 902 )
905 903 seen.add(b'f')
906 904 if progress:
907 905 progress.complete()
908 906 progress = srcrepo.ui.makeprogress(
909 907 _(b'file revisions'), total=frevcount
910 908 )
911 909
912 910 if matchrevlog(revlogs, unencoded):
913 911 ui.note(
914 912 _(b'cloning %d revisions from %s\n') % (len(oldrl), unencoded)
915 913 )
916 914 newrl = _revlogfrompath(dstrepo, unencoded)
917 915 oldrl.clone(
918 916 tr,
919 917 newrl,
920 918 addrevisioncb=oncopiedrevision,
921 919 deltareuse=deltareuse,
922 920 forcedeltabothparents=forcedeltabothparents,
923 921 sidedatacompanion=sidedatacompanion,
924 922 )
925 923 else:
926 924 msg = _(b'blindly copying %s containing %i revisions\n')
927 925 ui.note(msg % (unencoded, len(oldrl)))
928 926 _copyrevlog(tr, dstrepo, oldrl, unencoded)
929 927
930 928 newrl = _revlogfrompath(dstrepo, unencoded)
931 929
932 930 info = newrl.storageinfo(storedsize=True)
933 931 datasize = info[b'storedsize'] or 0
934 932
935 933 dstsize += datasize
936 934
937 935 if isinstance(newrl, changelog.changelog):
938 936 cdstsize += datasize
939 937 elif isinstance(newrl, manifest.manifestrevlog):
940 938 mdstsize += datasize
941 939 else:
942 940 fdstsize += datasize
943 941
944 942 progress.complete()
945 943
946 944 ui.write(
947 945 _(
948 946 b'finished migrating %d changelog revisions; change in size: '
949 947 b'%s\n'
950 948 )
951 949 % (crevcount, util.bytecount(cdstsize - csrcsize))
952 950 )
953 951
954 952 ui.write(
955 953 _(
956 954 b'finished migrating %d total revisions; total change in store '
957 955 b'size: %s\n'
958 956 )
959 957 % (revcount, util.bytecount(dstsize - srcsize))
960 958 )
961 959
962 960
963 961 def _filterstorefile(srcrepo, dstrepo, requirements, path, mode, st):
964 962 """Determine whether to copy a store file during upgrade.
965 963
966 964 This function is called when migrating store files from ``srcrepo`` to
967 965 ``dstrepo`` as part of upgrading a repository.
968 966
969 967 Args:
970 968 srcrepo: repo we are copying from
971 969 dstrepo: repo we are copying to
972 970 requirements: set of requirements for ``dstrepo``
973 971 path: store file being examined
974 972 mode: the ``ST_MODE`` file type of ``path``
975 973 st: ``stat`` data structure for ``path``
976 974
977 975 Function should return ``True`` if the file is to be copied.
978 976 """
979 977 # Skip revlogs.
980 978 if path.endswith((b'.i', b'.d')):
981 979 return False
982 980 # Skip transaction related files.
983 981 if path.startswith(b'undo'):
984 982 return False
985 983 # Only copy regular files.
986 984 if mode != stat.S_IFREG:
987 985 return False
988 986 # Skip other skipped files.
989 987 if path in (b'lock', b'fncache'):
990 988 return False
991 989
992 990 return True
993 991
994 992
995 993 def _finishdatamigration(ui, srcrepo, dstrepo, requirements):
996 994 """Hook point for extensions to perform additional actions during upgrade.
997 995
998 996 This function is called after revlogs and store files have been copied but
999 997 before the new store is swapped into the original location.
1000 998 """
1001 999
1002 1000
1003 1001 def _upgraderepo(
1004 1002 ui, srcrepo, dstrepo, requirements, actions, revlogs=UPGRADE_ALL_REVLOGS
1005 1003 ):
1006 1004 """Do the low-level work of upgrading a repository.
1007 1005
1008 1006 The upgrade is effectively performed as a copy between a source
1009 1007 repository and a temporary destination repository.
1010 1008
1011 1009 The source repository is unmodified for as long as possible so the
1012 1010 upgrade can abort at any time without causing loss of service for
1013 1011 readers and without corrupting the source repository.
1014 1012 """
1015 1013 assert srcrepo.currentwlock()
1016 1014 assert dstrepo.currentwlock()
1017 1015
1018 1016 ui.write(
1019 1017 _(
1020 1018 b'(it is safe to interrupt this process any time before '
1021 1019 b'data migration completes)\n'
1022 1020 )
1023 1021 )
1024 1022
1025 1023 if b're-delta-all' in actions:
1026 1024 deltareuse = revlog.revlog.DELTAREUSENEVER
1027 1025 elif b're-delta-parent' in actions:
1028 1026 deltareuse = revlog.revlog.DELTAREUSESAMEREVS
1029 1027 elif b're-delta-multibase' in actions:
1030 1028 deltareuse = revlog.revlog.DELTAREUSESAMEREVS
1031 1029 elif b're-delta-fulladd' in actions:
1032 1030 deltareuse = revlog.revlog.DELTAREUSEFULLADD
1033 1031 else:
1034 1032 deltareuse = revlog.revlog.DELTAREUSEALWAYS
1035 1033
1036 1034 with dstrepo.transaction(b'upgrade') as tr:
1037 1035 _clonerevlogs(
1038 1036 ui,
1039 1037 srcrepo,
1040 1038 dstrepo,
1041 1039 tr,
1042 1040 deltareuse,
1043 1041 b're-delta-multibase' in actions,
1044 1042 revlogs=revlogs,
1045 1043 )
1046 1044
1047 1045 # Now copy other files in the store directory.
1048 1046 # The sorted() makes execution deterministic.
1049 1047 for p, kind, st in sorted(srcrepo.store.vfs.readdir(b'', stat=True)):
1050 1048 if not _filterstorefile(srcrepo, dstrepo, requirements, p, kind, st):
1051 1049 continue
1052 1050
1053 1051 srcrepo.ui.write(_(b'copying %s\n') % p)
1054 1052 src = srcrepo.store.rawvfs.join(p)
1055 1053 dst = dstrepo.store.rawvfs.join(p)
1056 1054 util.copyfile(src, dst, copystat=True)
1057 1055
1058 1056 _finishdatamigration(ui, srcrepo, dstrepo, requirements)
1059 1057
1060 1058 ui.write(_(b'data fully migrated to temporary repository\n'))
1061 1059
1062 1060 backuppath = pycompat.mkdtemp(prefix=b'upgradebackup.', dir=srcrepo.path)
1063 1061 backupvfs = vfsmod.vfs(backuppath)
1064 1062
1065 1063 # Make a backup of requires file first, as it is the first to be modified.
1066 1064 util.copyfile(srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires'))
1067 1065
1068 1066 # We install an arbitrary requirement that clients must not support
1069 1067 # as a mechanism to lock out new clients during the data swap. This is
1070 1068 # better than allowing a client to continue while the repository is in
1071 1069 # an inconsistent state.
1072 1070 ui.write(
1073 1071 _(
1074 1072 b'marking source repository as being upgraded; clients will be '
1075 1073 b'unable to read from repository\n'
1076 1074 )
1077 1075 )
1078 1076 scmutil.writerequires(
1079 1077 srcrepo.vfs, srcrepo.requirements | {b'upgradeinprogress'}
1080 1078 )
1081 1079
1082 1080 ui.write(_(b'starting in-place swap of repository data\n'))
1083 1081 ui.write(_(b'replaced files will be backed up at %s\n') % backuppath)
1084 1082
1085 1083 # Now swap in the new store directory. Doing it as a rename should make
1086 1084 # the operation nearly instantaneous and atomic (at least in well-behaved
1087 1085 # environments).
1088 1086 ui.write(_(b'replacing store...\n'))
1089 1087 tstart = util.timer()
1090 1088 util.rename(srcrepo.spath, backupvfs.join(b'store'))
1091 1089 util.rename(dstrepo.spath, srcrepo.spath)
1092 1090 elapsed = util.timer() - tstart
1093 1091 ui.write(
1094 1092 _(
1095 1093 b'store replacement complete; repository was inconsistent for '
1096 1094 b'%0.1fs\n'
1097 1095 )
1098 1096 % elapsed
1099 1097 )
1100 1098
1101 1099 # We first write the requirements file. Any new requirements will lock
1102 1100 # out legacy clients.
1103 1101 ui.write(
1104 1102 _(
1105 1103 b'finalizing requirements file and making repository readable '
1106 1104 b'again\n'
1107 1105 )
1108 1106 )
1109 1107 scmutil.writerequires(srcrepo.vfs, requirements)
1110 1108
1111 1109 # The lock file from the old store won't be removed because nothing has a
1112 1110 # reference to its new location. So clean it up manually. Alternatively, we
1113 1111 # could update srcrepo.svfs and other variables to point to the new
1114 1112 # location. This is simpler.
1115 1113 backupvfs.unlink(b'store/lock')
1116 1114
1117 1115 return backuppath
1118 1116
1119 1117
1120 1118 def upgraderepo(
1121 1119 ui,
1122 1120 repo,
1123 1121 run=False,
1124 1122 optimize=None,
1125 1123 backup=True,
1126 1124 manifest=None,
1127 1125 changelog=None,
1128 1126 ):
1129 1127 """Upgrade a repository in place."""
1130 1128 if optimize is None:
1131 1129 optimize = []
1132 1130 optimize = {legacy_opts_map.get(o, o) for o in optimize}
1133 1131 repo = repo.unfiltered()
1134 1132
1135 1133 revlogs = set(UPGRADE_ALL_REVLOGS)
1136 1134 specentries = ((b'c', changelog), (b'm', manifest))
1137 1135 specified = [(y, x) for (y, x) in specentries if x is not None]
1138 1136 if specified:
1139 1137 # we have some limitation on revlogs to be recloned
1140 1138 if any(x for y, x in specified):
1141 1139 revlogs = set()
1142 1140 for r, enabled in specified:
1143 1141 if enabled:
1144 1142 if r == b'c':
1145 1143 revlogs.add(UPGRADE_CHANGELOG)
1146 1144 elif r == b'm':
1147 1145 revlogs.add(UPGRADE_MANIFEST)
1148 1146 else:
1149 1147 # none are enabled
1150 1148 for r, __ in specified:
1151 1149 if r == b'c':
1152 1150 revlogs.discard(UPGRADE_CHANGELOG)
1153 1151 elif r == b'm':
1154 1152 revlogs.discard(UPGRADE_MANIFEST)
1155 1153
1156 1154 # Ensure the repository can be upgraded.
1157 1155 missingreqs = requiredsourcerequirements(repo) - repo.requirements
1158 1156 if missingreqs:
1159 1157 raise error.Abort(
1160 1158 _(b'cannot upgrade repository; requirement missing: %s')
1161 1159 % _(b', ').join(sorted(missingreqs))
1162 1160 )
1163 1161
1164 1162 blockedreqs = blocksourcerequirements(repo) & repo.requirements
1165 1163 if blockedreqs:
1166 1164 raise error.Abort(
1167 1165 _(
1168 1166 b'cannot upgrade repository; unsupported source '
1169 1167 b'requirement: %s'
1170 1168 )
1171 1169 % _(b', ').join(sorted(blockedreqs))
1172 1170 )
1173 1171
1174 1172 # FUTURE there is potentially a need to control the wanted requirements via
1175 1173 # command arguments or via an extension hook point.
1176 1174 newreqs = localrepo.newreporequirements(
1177 1175 repo.ui, localrepo.defaultcreateopts(repo.ui)
1178 1176 )
1179 1177 newreqs.update(preservedrequirements(repo))
1180 1178
1181 1179 noremovereqs = (
1182 1180 repo.requirements - newreqs - supportremovedrequirements(repo)
1183 1181 )
1184 1182 if noremovereqs:
1185 1183 raise error.Abort(
1186 1184 _(
1187 1185 b'cannot upgrade repository; requirement would be '
1188 1186 b'removed: %s'
1189 1187 )
1190 1188 % _(b', ').join(sorted(noremovereqs))
1191 1189 )
1192 1190
1193 1191 noaddreqs = newreqs - repo.requirements - allowednewrequirements(repo)
1194 1192 if noaddreqs:
1195 1193 raise error.Abort(
1196 1194 _(
1197 1195 b'cannot upgrade repository; do not support adding '
1198 1196 b'requirement: %s'
1199 1197 )
1200 1198 % _(b', ').join(sorted(noaddreqs))
1201 1199 )
1202 1200
1203 1201 unsupportedreqs = newreqs - supporteddestrequirements(repo)
1204 1202 if unsupportedreqs:
1205 1203 raise error.Abort(
1206 1204 _(
1207 1205 b'cannot upgrade repository; do not support '
1208 1206 b'destination requirement: %s'
1209 1207 )
1210 1208 % _(b', ').join(sorted(unsupportedreqs))
1211 1209 )
1212 1210
1213 1211 # Find and validate all improvements that can be made.
1214 1212 alloptimizations = findoptimizations(repo)
1215 1213
1216 1214 # Apply and Validate arguments.
1217 1215 optimizations = []
1218 1216 for o in alloptimizations:
1219 1217 if o.name in optimize:
1220 1218 optimizations.append(o)
1221 1219 optimize.discard(o.name)
1222 1220
1223 1221 if optimize: # anything left is unknown
1224 1222 raise error.Abort(
1225 1223 _(b'unknown optimization action requested: %s')
1226 1224 % b', '.join(sorted(optimize)),
1227 1225 hint=_(b'run without arguments to see valid optimizations'),
1228 1226 )
1229 1227
1230 1228 deficiencies = finddeficiencies(repo)
1231 1229 actions = determineactions(repo, deficiencies, repo.requirements, newreqs)
1232 1230 actions.extend(
1233 1231 o
1234 1232 for o in sorted(optimizations)
1235 1233 # determineactions could have added optimisation
1236 1234 if o not in actions
1237 1235 )
1238 1236
1239 1237 removedreqs = repo.requirements - newreqs
1240 1238 addedreqs = newreqs - repo.requirements
1241 1239
1242 1240 if revlogs != UPGRADE_ALL_REVLOGS:
1243 1241 incompatible = RECLONES_REQUIREMENTS & (removedreqs | addedreqs)
1244 1242 if incompatible:
1245 1243 msg = _(
1246 1244 b'ignoring revlogs selection flags, format requirements '
1247 1245 b'change: %s\n'
1248 1246 )
1249 1247 ui.warn(msg % b', '.join(sorted(incompatible)))
1250 1248 revlogs = UPGRADE_ALL_REVLOGS
1251 1249
1252 1250 def write_labeled(l, label):
1253 1251 first = True
1254 1252 for r in sorted(l):
1255 1253 if not first:
1256 1254 ui.write(b', ')
1257 1255 ui.write(r, label=label)
1258 1256 first = False
1259 1257
1260 1258 def printrequirements():
1261 1259 ui.write(_(b'requirements\n'))
1262 1260 ui.write(_(b' preserved: '))
1263 1261 write_labeled(
1264 1262 newreqs & repo.requirements, "upgrade-repo.requirement.preserved"
1265 1263 )
1266 1264 ui.write((b'\n'))
1267 1265 removed = repo.requirements - newreqs
1268 1266 if repo.requirements - newreqs:
1269 1267 ui.write(_(b' removed: '))
1270 1268 write_labeled(removed, "upgrade-repo.requirement.removed")
1271 1269 ui.write((b'\n'))
1272 1270 added = newreqs - repo.requirements
1273 1271 if added:
1274 1272 ui.write(_(b' added: '))
1275 1273 write_labeled(added, "upgrade-repo.requirement.added")
1276 1274 ui.write((b'\n'))
1277 1275 ui.write(b'\n')
1278 1276
1279 1277 def printupgradeactions():
1280 1278 for a in actions:
1281 1279 ui.write(b'%s\n %s\n\n' % (a.name, a.upgrademessage))
1282 1280
1283 1281 if not run:
1284 1282 fromconfig = []
1285 1283 onlydefault = []
1286 1284
1287 1285 for d in deficiencies:
1288 1286 if d.fromconfig(repo):
1289 1287 fromconfig.append(d)
1290 1288 elif d.default:
1291 1289 onlydefault.append(d)
1292 1290
1293 1291 if fromconfig or onlydefault:
1294 1292
1295 1293 if fromconfig:
1296 1294 ui.write(
1297 1295 _(
1298 1296 b'repository lacks features recommended by '
1299 1297 b'current config options:\n\n'
1300 1298 )
1301 1299 )
1302 1300 for i in fromconfig:
1303 1301 ui.write(b'%s\n %s\n\n' % (i.name, i.description))
1304 1302
1305 1303 if onlydefault:
1306 1304 ui.write(
1307 1305 _(
1308 1306 b'repository lacks features used by the default '
1309 1307 b'config options:\n\n'
1310 1308 )
1311 1309 )
1312 1310 for i in onlydefault:
1313 1311 ui.write(b'%s\n %s\n\n' % (i.name, i.description))
1314 1312
1315 1313 ui.write(b'\n')
1316 1314 else:
1317 1315 ui.write(
1318 1316 _(
1319 1317 b'(no feature deficiencies found in existing '
1320 1318 b'repository)\n'
1321 1319 )
1322 1320 )
1323 1321
1324 1322 ui.write(
1325 1323 _(
1326 1324 b'performing an upgrade with "--run" will make the following '
1327 1325 b'changes:\n\n'
1328 1326 )
1329 1327 )
1330 1328
1331 1329 printrequirements()
1332 1330 printupgradeactions()
1333 1331
1334 1332 unusedoptimize = [i for i in alloptimizations if i not in actions]
1335 1333
1336 1334 if unusedoptimize:
1337 1335 ui.write(
1338 1336 _(
1339 1337 b'additional optimizations are available by specifying '
1340 1338 b'"--optimize <name>":\n\n'
1341 1339 )
1342 1340 )
1343 1341 for i in unusedoptimize:
1344 1342 ui.write(_(b'%s\n %s\n\n') % (i.name, i.description))
1345 1343 return
1346 1344
1347 1345 # Else we're in the run=true case.
1348 1346 ui.write(_(b'upgrade will perform the following actions:\n\n'))
1349 1347 printrequirements()
1350 1348 printupgradeactions()
1351 1349
1352 1350 upgradeactions = [a.name for a in actions]
1353 1351
1354 1352 ui.write(_(b'beginning upgrade...\n'))
1355 1353 with repo.wlock(), repo.lock():
1356 1354 ui.write(_(b'repository locked and read-only\n'))
1357 1355 # Our strategy for upgrading the repository is to create a new,
1358 1356 # temporary repository, write data to it, then do a swap of the
1359 1357 # data. There are less heavyweight ways to do this, but it is easier
1360 1358 # to create a new repo object than to instantiate all the components
1361 1359 # (like the store) separately.
1362 1360 tmppath = pycompat.mkdtemp(prefix=b'upgrade.', dir=repo.path)
1363 1361 backuppath = None
1364 1362 try:
1365 1363 ui.write(
1366 1364 _(
1367 1365 b'creating temporary repository to stage migrated '
1368 1366 b'data: %s\n'
1369 1367 )
1370 1368 % tmppath
1371 1369 )
1372 1370
1373 1371 # clone ui without using ui.copy because repo.ui is protected
1374 1372 repoui = repo.ui.__class__(repo.ui)
1375 1373 dstrepo = hg.repository(repoui, path=tmppath, create=True)
1376 1374
1377 1375 with dstrepo.wlock(), dstrepo.lock():
1378 1376 backuppath = _upgraderepo(
1379 1377 ui, repo, dstrepo, newreqs, upgradeactions, revlogs=revlogs
1380 1378 )
1381 1379 if not (backup or backuppath is None):
1382 1380 ui.write(_(b'removing old repository content%s\n') % backuppath)
1383 1381 repo.vfs.rmtree(backuppath, forcibly=True)
1384 1382 backuppath = None
1385 1383
1386 1384 finally:
1387 1385 ui.write(_(b'removing temporary repository %s\n') % tmppath)
1388 1386 repo.vfs.rmtree(tmppath, forcibly=True)
1389 1387
1390 1388 if backuppath:
1391 1389 ui.warn(
1392 1390 _(b'copy of old repository backed up at %s\n') % backuppath
1393 1391 )
1394 1392 ui.warn(
1395 1393 _(
1396 1394 b'the old repository will not be deleted; remove '
1397 1395 b'it to free up disk space once the upgraded '
1398 1396 b'repository is verified\n'
1399 1397 )
1400 1398 )
@@ -1,911 +1,913 b''
1 1 // status.rs
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
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 //! Rust implementation of dirstate.status (dirstate.py).
9 9 //! It is currently missing a lot of functionality compared to the Python one
10 10 //! and will only be triggered in narrow cases.
11 11
12 12 use crate::{
13 13 dirstate::SIZE_FROM_OTHER_PARENT,
14 14 filepatterns::PatternFileWarning,
15 15 matchers::{get_ignore_function, Matcher, VisitChildrenSet},
16 16 utils::{
17 17 files::{find_dirs, HgMetadata},
18 18 hg_path::{
19 19 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
20 20 HgPathError,
21 21 },
22 22 path_auditor::PathAuditor,
23 23 },
24 24 CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap,
25 25 PatternError,
26 26 };
27 27 use lazy_static::lazy_static;
28 28 use micro_timer::timed;
29 29 use rayon::prelude::*;
30 30 use std::{
31 31 borrow::Cow,
32 32 collections::HashSet,
33 33 fs::{read_dir, DirEntry},
34 34 io::ErrorKind,
35 35 ops::Deref,
36 36 path::{Path, PathBuf},
37 37 };
38 38
39 39 /// Wrong type of file from a `BadMatch`
40 40 /// Note: a lot of those don't exist on all platforms.
41 41 #[derive(Debug, Copy, Clone)]
42 42 pub enum BadType {
43 43 CharacterDevice,
44 44 BlockDevice,
45 45 FIFO,
46 46 Socket,
47 47 Directory,
48 48 Unknown,
49 49 }
50 50
51 51 impl ToString for BadType {
52 52 fn to_string(&self) -> String {
53 53 match self {
54 54 BadType::CharacterDevice => "character device",
55 55 BadType::BlockDevice => "block device",
56 56 BadType::FIFO => "fifo",
57 57 BadType::Socket => "socket",
58 58 BadType::Directory => "directory",
59 59 BadType::Unknown => "unknown",
60 60 }
61 61 .to_string()
62 62 }
63 63 }
64 64
65 65 /// Was explicitly matched but cannot be found/accessed
66 66 #[derive(Debug, Copy, Clone)]
67 67 pub enum BadMatch {
68 68 OsError(i32),
69 69 BadType(BadType),
70 70 }
71 71
72 72 /// Marker enum used to dispatch new status entries into the right collections.
73 73 /// Is similar to `crate::EntryState`, but represents the transient state of
74 74 /// entries during the lifetime of a command.
75 75 #[derive(Debug, Copy, Clone)]
76 76 enum Dispatch {
77 77 Unsure,
78 78 Modified,
79 79 Added,
80 80 Removed,
81 81 Deleted,
82 82 Clean,
83 83 Unknown,
84 84 Ignored,
85 85 /// Empty dispatch, the file is not worth listing
86 86 None,
87 87 /// Was explicitly matched but cannot be found/accessed
88 88 Bad(BadMatch),
89 89 Directory {
90 90 /// True if the directory used to be a file in the dmap so we can say
91 91 /// that it's been removed.
92 92 was_file: bool,
93 93 },
94 94 }
95 95
96 96 type IoResult<T> = std::io::Result<T>;
97 97 /// `Box<dyn Trait>` is syntactic sugar for `Box<dyn Trait, 'static>`, so add
98 98 /// an explicit lifetime here to not fight `'static` bounds "out of nowhere".
99 99 type IgnoreFnType<'a> = Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>;
100 100
101 101 /// Dates and times that are outside the 31-bit signed range are compared
102 102 /// modulo 2^31. This should prevent hg from behaving badly with very large
103 103 /// files or corrupt dates while still having a high probability of detecting
104 104 /// changes. (issue2608)
105 105 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
106 106 /// is not defined for `i32`, and there is no `As` trait. This forces the
107 107 /// caller to cast `b` as `i32`.
108 108 fn mod_compare(a: i32, b: i32) -> bool {
109 109 a & i32::max_value() != b & i32::max_value()
110 110 }
111 111
112 112 /// Return a sorted list containing information about the entries
113 113 /// in the directory.
114 114 ///
115 115 /// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
116 116 fn list_directory(
117 117 path: impl AsRef<Path>,
118 118 skip_dot_hg: bool,
119 119 ) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
120 120 let mut results = vec![];
121 121 let entries = read_dir(path.as_ref())?;
122 122
123 123 for entry in entries {
124 124 let entry = entry?;
125 125 let filename = os_string_to_hg_path_buf(entry.file_name())?;
126 126 let file_type = entry.file_type()?;
127 127 if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
128 128 return Ok(vec![]);
129 129 } else {
130 130 results.push((HgPathBuf::from(filename), entry))
131 131 }
132 132 }
133 133
134 134 results.sort_unstable_by_key(|e| e.0.clone());
135 135 Ok(results)
136 136 }
137 137
138 138 /// The file corresponding to the dirstate entry was found on the filesystem.
139 139 fn dispatch_found(
140 140 filename: impl AsRef<HgPath>,
141 141 entry: DirstateEntry,
142 142 metadata: HgMetadata,
143 143 copy_map: &CopyMap,
144 144 options: StatusOptions,
145 145 ) -> Dispatch {
146 146 let DirstateEntry {
147 147 state,
148 148 mode,
149 149 mtime,
150 150 size,
151 151 } = entry;
152 152
153 153 let HgMetadata {
154 154 st_mode,
155 155 st_size,
156 156 st_mtime,
157 157 ..
158 158 } = metadata;
159 159
160 160 match state {
161 161 EntryState::Normal => {
162 162 let size_changed = mod_compare(size, st_size as i32);
163 163 let mode_changed =
164 164 (mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
165 165 let metadata_changed = size >= 0 && (size_changed || mode_changed);
166 166 let other_parent = size == SIZE_FROM_OTHER_PARENT;
167 167 if metadata_changed
168 168 || other_parent
169 169 || copy_map.contains_key(filename.as_ref())
170 170 {
171 171 Dispatch::Modified
172 172 } else if mod_compare(mtime, st_mtime as i32) {
173 173 Dispatch::Unsure
174 174 } else if st_mtime == options.last_normal_time {
175 175 // the file may have just been marked as normal and
176 176 // it may have changed in the same second without
177 177 // changing its size. This can happen if we quickly
178 178 // do multiple commits. Force lookup, so we don't
179 179 // miss such a racy file change.
180 180 Dispatch::Unsure
181 181 } else if options.list_clean {
182 182 Dispatch::Clean
183 183 } else {
184 184 Dispatch::None
185 185 }
186 186 }
187 187 EntryState::Merged => Dispatch::Modified,
188 188 EntryState::Added => Dispatch::Added,
189 189 EntryState::Removed => Dispatch::Removed,
190 190 EntryState::Unknown => Dispatch::Unknown,
191 191 }
192 192 }
193 193
194 194 /// The file corresponding to this Dirstate entry is missing.
195 195 fn dispatch_missing(state: EntryState) -> Dispatch {
196 196 match state {
197 197 // File was removed from the filesystem during commands
198 198 EntryState::Normal | EntryState::Merged | EntryState::Added => {
199 199 Dispatch::Deleted
200 200 }
201 201 // File was removed, everything is normal
202 202 EntryState::Removed => Dispatch::Removed,
203 203 // File is unknown to Mercurial, everything is normal
204 204 EntryState::Unknown => Dispatch::Unknown,
205 205 }
206 206 }
207 207
208 208 lazy_static! {
209 209 static ref DEFAULT_WORK: HashSet<&'static HgPath> = {
210 210 let mut h = HashSet::new();
211 211 h.insert(HgPath::new(b""));
212 212 h
213 213 };
214 214 }
215 215
216 216 /// Get stat data about the files explicitly specified by match.
217 217 /// TODO subrepos
218 218 #[timed]
219 219 fn walk_explicit<'a>(
220 220 files: Option<&'a HashSet<&HgPath>>,
221 221 dmap: &'a DirstateMap,
222 222 root_dir: impl AsRef<Path> + Sync + Send + 'a,
223 223 options: StatusOptions,
224 224 ) -> impl ParallelIterator<Item = IoResult<(&'a HgPath, Dispatch)>> {
225 225 files
226 226 .unwrap_or(&DEFAULT_WORK)
227 227 .par_iter()
228 228 .map(move |filename| {
229 229 // TODO normalization
230 230 let normalized = filename.as_ref();
231 231
232 232 let buf = match hg_path_to_path_buf(normalized) {
233 233 Ok(x) => x,
234 234 Err(e) => return Some(Err(e.into())),
235 235 };
236 236 let target = root_dir.as_ref().join(buf);
237 237 let st = target.symlink_metadata();
238 238 let in_dmap = dmap.get(normalized);
239 239 match st {
240 240 Ok(meta) => {
241 241 let file_type = meta.file_type();
242 242 return if file_type.is_file() || file_type.is_symlink() {
243 243 if let Some(entry) = in_dmap {
244 244 return Some(Ok((
245 245 normalized,
246 246 dispatch_found(
247 247 &normalized,
248 248 *entry,
249 249 HgMetadata::from_metadata(meta),
250 250 &dmap.copy_map,
251 251 options,
252 252 ),
253 253 )));
254 254 }
255 255 Some(Ok((normalized, Dispatch::Unknown)))
256 256 } else {
257 257 if file_type.is_dir() {
258 258 Some(Ok((
259 259 normalized,
260 260 Dispatch::Directory {
261 261 was_file: in_dmap.is_some(),
262 262 },
263 263 )))
264 264 } else {
265 265 Some(Ok((
266 266 normalized,
267 267 Dispatch::Bad(BadMatch::BadType(
268 268 // TODO do more than unknown
269 269 // Support for all `BadType` variant
270 270 // varies greatly between platforms.
271 271 // So far, no tests check the type and
272 272 // this should be good enough for most
273 273 // users.
274 274 BadType::Unknown,
275 275 )),
276 276 )))
277 277 }
278 278 };
279 279 }
280 280 Err(_) => {
281 281 if let Some(entry) = in_dmap {
282 282 return Some(Ok((
283 283 normalized,
284 284 dispatch_missing(entry.state),
285 285 )));
286 286 }
287 287 }
288 288 };
289 289 None
290 290 })
291 291 .flatten()
292 292 }
293 293
294 294 #[derive(Debug, Copy, Clone)]
295 295 pub struct StatusOptions {
296 296 /// Remember the most recent modification timeslot for status, to make
297 297 /// sure we won't miss future size-preserving file content modifications
298 298 /// that happen within the same timeslot.
299 299 pub last_normal_time: i64,
300 300 /// Whether we are on a filesystem with UNIX-like exec flags
301 301 pub check_exec: bool,
302 302 pub list_clean: bool,
303 303 pub list_unknown: bool,
304 304 pub list_ignored: bool,
305 305 }
306 306
307 307 /// Dispatch a single entry (file, folder, symlink...) found during `traverse`.
308 308 /// If the entry is a folder that needs to be traversed, it will be handled
309 309 /// in a separate thread.
310
311 310 fn handle_traversed_entry<'a>(
312 311 scope: &rayon::Scope<'a>,
313 312 files_sender: &'a crossbeam::Sender<IoResult<(HgPathBuf, Dispatch)>>,
314 313 matcher: &'a (impl Matcher + Sync),
315 314 root_dir: impl AsRef<Path> + Sync + Send + Copy + 'a,
316 315 dmap: &'a DirstateMap,
317 316 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
318 317 ignore_fn: &'a IgnoreFnType,
319 318 dir_ignore_fn: &'a IgnoreFnType,
320 319 options: StatusOptions,
321 320 filename: HgPathBuf,
322 321 dir_entry: DirEntry,
323 322 ) -> IoResult<()> {
324 323 let file_type = dir_entry.file_type()?;
325 324 let entry_option = dmap.get(&filename);
326 325
326 if filename.as_bytes() == b".hg" {
327 // Could be a directory or a symlink
328 return Ok(());
329 }
330
327 331 if file_type.is_dir() {
328 332 handle_traversed_dir(
329 333 scope,
330 334 files_sender,
331 335 matcher,
332 336 root_dir,
333 337 dmap,
334 338 old_results,
335 339 ignore_fn,
336 340 dir_ignore_fn,
337 341 options,
338 342 entry_option,
339 343 filename,
340 344 );
341 345 } else if file_type.is_file() || file_type.is_symlink() {
342 346 if let Some(entry) = entry_option {
343 347 if matcher.matches_everything() || matcher.matches(&filename) {
344 348 let metadata = dir_entry.metadata()?;
345 349 files_sender
346 350 .send(Ok((
347 351 filename.to_owned(),
348 352 dispatch_found(
349 353 &filename,
350 354 *entry,
351 355 HgMetadata::from_metadata(metadata),
352 356 &dmap.copy_map,
353 357 options,
354 358 ),
355 359 )))
356 360 .unwrap();
357 361 }
358 362 } else if (matcher.matches_everything() || matcher.matches(&filename))
359 363 && !ignore_fn(&filename)
360 364 {
361 365 if (options.list_ignored || matcher.exact_match(&filename))
362 366 && dir_ignore_fn(&filename)
363 367 {
364 368 if options.list_ignored {
365 369 files_sender
366 370 .send(Ok((filename.to_owned(), Dispatch::Ignored)))
367 371 .unwrap();
368 372 }
369 373 } else {
370 374 files_sender
371 375 .send(Ok((filename.to_owned(), Dispatch::Unknown)))
372 376 .unwrap();
373 377 }
374 378 } else if ignore_fn(&filename) && options.list_ignored {
375 379 files_sender
376 380 .send(Ok((filename.to_owned(), Dispatch::Ignored)))
377 381 .unwrap();
378 382 }
379 383 } else if let Some(entry) = entry_option {
380 384 // Used to be a file or a folder, now something else.
381 385 if matcher.matches_everything() || matcher.matches(&filename) {
382 386 files_sender
383 387 .send(Ok((filename.to_owned(), dispatch_missing(entry.state))))
384 388 .unwrap();
385 389 }
386 390 }
387 391
388 392 Ok(())
389 393 }
390 394
391 395 /// A directory was found in the filesystem and needs to be traversed
392 396 fn handle_traversed_dir<'a>(
393 397 scope: &rayon::Scope<'a>,
394 398 files_sender: &'a crossbeam::Sender<IoResult<(HgPathBuf, Dispatch)>>,
395 399 matcher: &'a (impl Matcher + Sync),
396 400 root_dir: impl AsRef<Path> + Sync + Send + Copy + 'a,
397 401 dmap: &'a DirstateMap,
398 402 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
399 403 ignore_fn: &'a IgnoreFnType,
400 404 dir_ignore_fn: &'a IgnoreFnType,
401 405 options: StatusOptions,
402 406 entry_option: Option<&'a DirstateEntry>,
403 407 directory: HgPathBuf,
404 408 ) {
405 409 scope.spawn(move |_| {
406 410 // Nested `if` until `rust-lang/rust#53668` is stable
407 411 if let Some(entry) = entry_option {
408 412 // Used to be a file, is now a folder
409 413 if matcher.matches_everything() || matcher.matches(&directory) {
410 414 files_sender
411 415 .send(Ok((
412 416 directory.to_owned(),
413 417 dispatch_missing(entry.state),
414 418 )))
415 419 .unwrap();
416 420 }
417 421 }
418 422 // Do we need to traverse it?
419 423 if !ignore_fn(&directory) || options.list_ignored {
420 424 traverse_dir(
421 425 files_sender,
422 426 matcher,
423 427 root_dir,
424 428 dmap,
425 429 directory,
426 430 &old_results,
427 431 ignore_fn,
428 432 dir_ignore_fn,
429 433 options,
430 434 )
431 435 .unwrap_or_else(|e| files_sender.send(Err(e)).unwrap())
432 436 }
433 437 });
434 438 }
435 439
436 440 /// Decides whether the directory needs to be listed, and if so handles the
437 441 /// entries in a separate thread.
438 442 fn traverse_dir<'a>(
439 443 files_sender: &crossbeam::Sender<IoResult<(HgPathBuf, Dispatch)>>,
440 444 matcher: &'a (impl Matcher + Sync),
441 445 root_dir: impl AsRef<Path> + Sync + Send + Copy,
442 446 dmap: &'a DirstateMap,
443 447 directory: impl AsRef<HgPath>,
444 448 old_results: &FastHashMap<Cow<'a, HgPath>, Dispatch>,
445 449 ignore_fn: &IgnoreFnType,
446 450 dir_ignore_fn: &IgnoreFnType,
447 451 options: StatusOptions,
448 452 ) -> IoResult<()> {
449 453 let directory = directory.as_ref();
450 if directory.as_bytes() == b".hg" {
451 return Ok(());
452 }
454
453 455 let visit_entries = match matcher.visit_children_set(directory) {
454 456 VisitChildrenSet::Empty => return Ok(()),
455 457 VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
456 458 VisitChildrenSet::Set(set) => Some(set),
457 459 };
458 460 let buf = hg_path_to_path_buf(directory)?;
459 461 let dir_path = root_dir.as_ref().join(buf);
460 462
461 463 let skip_dot_hg = !directory.as_bytes().is_empty();
462 464 let entries = match list_directory(dir_path, skip_dot_hg) {
463 465 Err(e) => match e.kind() {
464 466 ErrorKind::NotFound | ErrorKind::PermissionDenied => {
465 467 files_sender
466 468 .send(Ok((
467 469 directory.to_owned(),
468 470 Dispatch::Bad(BadMatch::OsError(
469 471 // Unwrapping here is OK because the error always
470 472 // is a real os error
471 473 e.raw_os_error().unwrap(),
472 474 )),
473 475 )))
474 476 .unwrap();
475 477 return Ok(());
476 478 }
477 479 _ => return Err(e),
478 480 },
479 481 Ok(entries) => entries,
480 482 };
481 483
482 484 rayon::scope(|scope| -> IoResult<()> {
483 485 for (filename, dir_entry) in entries {
484 486 if let Some(ref set) = visit_entries {
485 487 if !set.contains(filename.deref()) {
486 488 continue;
487 489 }
488 490 }
489 491 // TODO normalize
490 492 let filename = if directory.is_empty() {
491 493 filename.to_owned()
492 494 } else {
493 495 directory.join(&filename)
494 496 };
495 497
496 498 if !old_results.contains_key(filename.deref()) {
497 499 handle_traversed_entry(
498 500 scope,
499 501 files_sender,
500 502 matcher,
501 503 root_dir,
502 504 dmap,
503 505 old_results,
504 506 ignore_fn,
505 507 dir_ignore_fn,
506 508 options,
507 509 filename,
508 510 dir_entry,
509 511 )?;
510 512 }
511 513 }
512 514 Ok(())
513 515 })
514 516 }
515 517
516 518 /// Walk the working directory recursively to look for changes compared to the
517 519 /// current `DirstateMap`.
518 520 ///
519 521 /// This takes a mutable reference to the results to account for the `extend`
520 522 /// in timings
521 523 #[timed]
522 524 fn traverse<'a>(
523 525 matcher: &'a (impl Matcher + Sync),
524 526 root_dir: impl AsRef<Path> + Sync + Send + Copy,
525 527 dmap: &'a DirstateMap,
526 528 path: impl AsRef<HgPath>,
527 529 old_results: &FastHashMap<Cow<'a, HgPath>, Dispatch>,
528 530 ignore_fn: &IgnoreFnType,
529 531 dir_ignore_fn: &IgnoreFnType,
530 532 options: StatusOptions,
531 533 results: &mut Vec<(Cow<'a, HgPath>, Dispatch)>,
532 534 ) -> IoResult<()> {
533 535 let root_dir = root_dir.as_ref();
534 536
535 537 // The traversal is done in parallel, so use a channel to gather entries.
536 538 // `crossbeam::Sender` is `Send`, while `mpsc::Sender` is not.
537 539 let (files_transmitter, files_receiver) = crossbeam::channel::unbounded();
538 540
539 541 traverse_dir(
540 542 &files_transmitter,
541 543 matcher,
542 544 root_dir,
543 545 &dmap,
544 546 path,
545 547 &old_results,
546 548 &ignore_fn,
547 549 &dir_ignore_fn,
548 550 options,
549 551 )?;
550 552
551 553 // Disconnect the channel so the receiver stops waiting
552 554 drop(files_transmitter);
553 555
554 556 // TODO don't collect. Find a way of replicating the behavior of
555 557 // `itertools::process_results`, but for `rayon::ParallelIterator`
556 558 let new_results: IoResult<Vec<(Cow<'a, HgPath>, Dispatch)>> =
557 559 files_receiver
558 560 .into_iter()
559 561 .map(|item| {
560 562 let (f, d) = item?;
561 563 Ok((Cow::Owned(f), d))
562 564 })
563 565 .collect();
564 566
565 567 results.par_extend(new_results?);
566 568
567 569 Ok(())
568 570 }
569 571
570 572 /// Stat all entries in the `DirstateMap` and mark them for dispatch.
571 573 fn stat_dmap_entries(
572 574 dmap: &DirstateMap,
573 575 root_dir: impl AsRef<Path> + Sync + Send,
574 576 options: StatusOptions,
575 577 ) -> impl ParallelIterator<Item = IoResult<(&HgPath, Dispatch)>> {
576 578 dmap.par_iter().map(move |(filename, entry)| {
577 579 let filename: &HgPath = filename;
578 580 let filename_as_path = hg_path_to_path_buf(filename)?;
579 581 let meta = root_dir.as_ref().join(filename_as_path).symlink_metadata();
580 582
581 583 match meta {
582 584 Ok(ref m)
583 585 if !(m.file_type().is_file()
584 586 || m.file_type().is_symlink()) =>
585 587 {
586 588 Ok((filename, dispatch_missing(entry.state)))
587 589 }
588 590 Ok(m) => Ok((
589 591 filename,
590 592 dispatch_found(
591 593 filename,
592 594 *entry,
593 595 HgMetadata::from_metadata(m),
594 596 &dmap.copy_map,
595 597 options,
596 598 ),
597 599 )),
598 600 Err(ref e)
599 601 if e.kind() == ErrorKind::NotFound
600 602 || e.raw_os_error() == Some(20) =>
601 603 {
602 604 // Rust does not yet have an `ErrorKind` for
603 605 // `NotADirectory` (errno 20)
604 606 // It happens if the dirstate contains `foo/bar` and
605 607 // foo is not a directory
606 608 Ok((filename, dispatch_missing(entry.state)))
607 609 }
608 610 Err(e) => Err(e),
609 611 }
610 612 })
611 613 }
612 614
613 615 /// This takes a mutable reference to the results to account for the `extend`
614 616 /// in timings
615 617 #[timed]
616 618 fn extend_from_dmap<'a>(
617 619 dmap: &'a DirstateMap,
618 620 root_dir: impl AsRef<Path> + Sync + Send,
619 621 options: StatusOptions,
620 622 results: &mut Vec<(Cow<'a, HgPath>, Dispatch)>,
621 623 ) {
622 624 results.par_extend(
623 625 stat_dmap_entries(dmap, root_dir, options)
624 626 .flatten()
625 627 .map(|(filename, dispatch)| (Cow::Borrowed(filename), dispatch)),
626 628 );
627 629 }
628 630
629 631 #[derive(Debug)]
630 632 pub struct DirstateStatus<'a> {
631 633 pub modified: Vec<Cow<'a, HgPath>>,
632 634 pub added: Vec<Cow<'a, HgPath>>,
633 635 pub removed: Vec<Cow<'a, HgPath>>,
634 636 pub deleted: Vec<Cow<'a, HgPath>>,
635 637 pub clean: Vec<Cow<'a, HgPath>>,
636 638 pub ignored: Vec<Cow<'a, HgPath>>,
637 639 pub unknown: Vec<Cow<'a, HgPath>>,
638 640 pub bad: Vec<(Cow<'a, HgPath>, BadMatch)>,
639 641 }
640 642
641 643 #[timed]
642 644 fn build_response<'a>(
643 645 results: impl IntoIterator<Item = (Cow<'a, HgPath>, Dispatch)>,
644 646 ) -> (Vec<Cow<'a, HgPath>>, DirstateStatus<'a>) {
645 647 let mut lookup = vec![];
646 648 let mut modified = vec![];
647 649 let mut added = vec![];
648 650 let mut removed = vec![];
649 651 let mut deleted = vec![];
650 652 let mut clean = vec![];
651 653 let mut ignored = vec![];
652 654 let mut unknown = vec![];
653 655 let mut bad = vec![];
654 656
655 657 for (filename, dispatch) in results.into_iter() {
656 658 match dispatch {
657 659 Dispatch::Unknown => unknown.push(filename),
658 660 Dispatch::Unsure => lookup.push(filename),
659 661 Dispatch::Modified => modified.push(filename),
660 662 Dispatch::Added => added.push(filename),
661 663 Dispatch::Removed => removed.push(filename),
662 664 Dispatch::Deleted => deleted.push(filename),
663 665 Dispatch::Clean => clean.push(filename),
664 666 Dispatch::Ignored => ignored.push(filename),
665 667 Dispatch::None => {}
666 668 Dispatch::Bad(reason) => bad.push((filename, reason)),
667 669 Dispatch::Directory { .. } => {}
668 670 }
669 671 }
670 672
671 673 (
672 674 lookup,
673 675 DirstateStatus {
674 676 modified,
675 677 added,
676 678 removed,
677 679 deleted,
678 680 clean,
679 681 ignored,
680 682 unknown,
681 683 bad,
682 684 },
683 685 )
684 686 }
685 687
686 688 #[derive(Debug)]
687 689 pub enum StatusError {
688 690 IO(std::io::Error),
689 691 Path(HgPathError),
690 692 Pattern(PatternError),
691 693 }
692 694
693 695 pub type StatusResult<T> = Result<T, StatusError>;
694 696
695 697 impl From<PatternError> for StatusError {
696 698 fn from(e: PatternError) -> Self {
697 699 StatusError::Pattern(e)
698 700 }
699 701 }
700 702 impl From<HgPathError> for StatusError {
701 703 fn from(e: HgPathError) -> Self {
702 704 StatusError::Path(e)
703 705 }
704 706 }
705 707 impl From<std::io::Error> for StatusError {
706 708 fn from(e: std::io::Error) -> Self {
707 709 StatusError::IO(e)
708 710 }
709 711 }
710 712
711 713 impl ToString for StatusError {
712 714 fn to_string(&self) -> String {
713 715 match self {
714 716 StatusError::IO(e) => e.to_string(),
715 717 StatusError::Path(e) => e.to_string(),
716 718 StatusError::Pattern(e) => e.to_string(),
717 719 }
718 720 }
719 721 }
720 722
721 723 /// This takes a mutable reference to the results to account for the `extend`
722 724 /// in timings
723 725 #[timed]
724 726 fn handle_unknowns<'a>(
725 727 dmap: &'a DirstateMap,
726 728 matcher: &(impl Matcher + Sync),
727 729 root_dir: impl AsRef<Path> + Sync + Send + Copy,
728 730 options: StatusOptions,
729 731 results: &mut Vec<(Cow<'a, HgPath>, Dispatch)>,
730 732 ) -> IoResult<()> {
731 733 let to_visit: Vec<(&HgPath, &DirstateEntry)> = if results.is_empty()
732 734 && matcher.matches_everything()
733 735 {
734 736 dmap.iter().map(|(f, e)| (f.deref(), e)).collect()
735 737 } else {
736 738 // Only convert to a hashmap if needed.
737 739 let old_results: FastHashMap<_, _> = results.iter().cloned().collect();
738 740 dmap.iter()
739 741 .filter_map(move |(f, e)| {
740 742 if !old_results.contains_key(f.deref()) && matcher.matches(f) {
741 743 Some((f.deref(), e))
742 744 } else {
743 745 None
744 746 }
745 747 })
746 748 .collect()
747 749 };
748 750
749 751 // We walked all dirs under the roots that weren't ignored, and
750 752 // everything that matched was stat'ed and is already in results.
751 753 // The rest must thus be ignored or under a symlink.
752 754 let path_auditor = PathAuditor::new(root_dir);
753 755
754 756 // TODO don't collect. Find a way of replicating the behavior of
755 757 // `itertools::process_results`, but for `rayon::ParallelIterator`
756 758 let new_results: IoResult<Vec<_>> = to_visit
757 759 .into_par_iter()
758 760 .filter_map(|(filename, entry)| -> Option<IoResult<_>> {
759 761 // Report ignored items in the dmap as long as they are not
760 762 // under a symlink directory.
761 763 if path_auditor.check(filename) {
762 764 // TODO normalize for case-insensitive filesystems
763 765 let buf = match hg_path_to_path_buf(filename) {
764 766 Ok(x) => x,
765 767 Err(e) => return Some(Err(e.into())),
766 768 };
767 769 Some(Ok((
768 770 Cow::Borrowed(filename),
769 771 match root_dir.as_ref().join(&buf).symlink_metadata() {
770 772 // File was just ignored, no links, and exists
771 773 Ok(meta) => {
772 774 let metadata = HgMetadata::from_metadata(meta);
773 775 dispatch_found(
774 776 filename,
775 777 *entry,
776 778 metadata,
777 779 &dmap.copy_map,
778 780 options,
779 781 )
780 782 }
781 783 // File doesn't exist
782 784 Err(_) => dispatch_missing(entry.state),
783 785 },
784 786 )))
785 787 } else {
786 788 // It's either missing or under a symlink directory which
787 789 // we, in this case, report as missing.
788 790 Some(Ok((
789 791 Cow::Borrowed(filename),
790 792 dispatch_missing(entry.state),
791 793 )))
792 794 }
793 795 })
794 796 .collect();
795 797
796 798 results.par_extend(new_results?);
797 799
798 800 Ok(())
799 801 }
800 802
801 803 /// Get the status of files in the working directory.
802 804 ///
803 805 /// This is the current entry-point for `hg-core` and is realistically unusable
804 806 /// outside of a Python context because its arguments need to provide a lot of
805 807 /// information that will not be necessary in the future.
806 808 #[timed]
807 809 pub fn status<'a: 'c, 'b: 'c, 'c>(
808 810 dmap: &'a DirstateMap,
809 811 matcher: &'b (impl Matcher + Sync),
810 812 root_dir: impl AsRef<Path> + Sync + Send + Copy + 'c,
811 813 ignore_files: Vec<PathBuf>,
812 814 options: StatusOptions,
813 815 ) -> StatusResult<(
814 816 (Vec<Cow<'c, HgPath>>, DirstateStatus<'c>),
815 817 Vec<PatternFileWarning>,
816 818 )> {
817 819 // Needs to outlive `dir_ignore_fn` since it's captured.
818 820 let mut ignore_fn: IgnoreFnType;
819 821
820 822 // Only involve real ignore mechanism if we're listing unknowns or ignored.
821 823 let (dir_ignore_fn, warnings): (IgnoreFnType, _) = if options.list_ignored
822 824 || options.list_unknown
823 825 {
824 826 let (ignore, warnings) = get_ignore_function(ignore_files, root_dir)?;
825 827
826 828 ignore_fn = ignore;
827 829 let dir_ignore_fn = Box::new(|dir: &_| {
828 830 // Is the path or one of its ancestors ignored?
829 831 if ignore_fn(dir) {
830 832 true
831 833 } else {
832 834 for p in find_dirs(dir) {
833 835 if ignore_fn(p) {
834 836 return true;
835 837 }
836 838 }
837 839 false
838 840 }
839 841 });
840 842 (dir_ignore_fn, warnings)
841 843 } else {
842 844 ignore_fn = Box::new(|&_| true);
843 845 (Box::new(|&_| true), vec![])
844 846 };
845 847
846 848 let files = matcher.file_set();
847 849
848 850 // Step 1: check the files explicitly mentioned by the user
849 851 let explicit = walk_explicit(files, &dmap, root_dir, options);
850 852
851 853 // Collect results into a `Vec` because we do very few lookups in most
852 854 // cases.
853 855 let (work, mut results): (Vec<_>, Vec<_>) = explicit
854 856 .filter_map(Result::ok)
855 857 .map(|(filename, dispatch)| (Cow::Borrowed(filename), dispatch))
856 858 .partition(|(_, dispatch)| match dispatch {
857 859 Dispatch::Directory { .. } => true,
858 860 _ => false,
859 861 });
860 862
861 863 if !work.is_empty() {
862 864 // Hashmaps are quite a bit slower to build than vecs, so only build it
863 865 // if needed.
864 866 let old_results = results.iter().cloned().collect();
865 867
866 868 // Step 2: recursively check the working directory for changes if
867 869 // needed
868 870 for (dir, dispatch) in work {
869 871 match dispatch {
870 872 Dispatch::Directory { was_file } => {
871 873 if was_file {
872 874 results.push((dir.to_owned(), Dispatch::Removed));
873 875 }
874 876 if options.list_ignored
875 877 || options.list_unknown && !dir_ignore_fn(&dir)
876 878 {
877 879 traverse(
878 880 matcher,
879 881 root_dir,
880 882 &dmap,
881 883 &dir,
882 884 &old_results,
883 885 &ignore_fn,
884 886 &dir_ignore_fn,
885 887 options,
886 888 &mut results,
887 889 )?;
888 890 }
889 891 }
890 892 _ => unreachable!("There can only be directories in `work`"),
891 893 }
892 894 }
893 895 }
894 896
895 897 if !matcher.is_exact() {
896 898 // Step 3: Check the remaining files from the dmap.
897 899 // If a dmap file is not in results yet, it was either
898 900 // a) not matched b) ignored, c) missing, or d) under a
899 901 // symlink directory.
900 902
901 903 if options.list_unknown {
902 904 handle_unknowns(dmap, matcher, root_dir, options, &mut results)?;
903 905 } else {
904 906 // We may not have walked the full directory tree above, so stat
905 907 // and check everything we missed.
906 908 extend_from_dmap(&dmap, root_dir, options, &mut results);
907 909 }
908 910 }
909 911
910 912 Ok((build_response(results), warnings))
911 913 }
@@ -1,1753 +1,1760 b''
1 1 #
2 2 # This is the mercurial setup script.
3 3 #
4 4 # 'python setup.py install', or
5 5 # 'python setup.py --help' for more options
6 6 import os
7 7
8 8 # Mercurial will never work on Python 3 before 3.5 due to a lack
9 9 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
10 10 # due to a bug in % formatting in bytestrings.
11 11 # We cannot support Python 3.5.0, 3.5.1, 3.5.2 because of bug in
12 12 # codecs.escape_encode() where it raises SystemError on empty bytestring
13 13 # bug link: https://bugs.python.org/issue25270
14 14 supportedpy = ','.join(
15 15 [
16 16 '>=2.7',
17 17 '!=3.0.*',
18 18 '!=3.1.*',
19 19 '!=3.2.*',
20 20 '!=3.3.*',
21 21 '!=3.4.*',
22 22 '!=3.5.0',
23 23 '!=3.5.1',
24 24 '!=3.5.2',
25 25 '!=3.6.0',
26 26 '!=3.6.1',
27 27 ]
28 28 )
29 29
30 30 import sys, platform
31 31 import sysconfig
32 32
33 33 if sys.version_info[0] >= 3:
34 34 printf = eval('print')
35 35 libdir_escape = 'unicode_escape'
36 36
37 37 def sysstr(s):
38 38 return s.decode('latin-1')
39 39
40 40
41 41 else:
42 42 libdir_escape = 'string_escape'
43 43
44 44 def printf(*args, **kwargs):
45 45 f = kwargs.get('file', sys.stdout)
46 46 end = kwargs.get('end', '\n')
47 47 f.write(b' '.join(args) + end)
48 48
49 49 def sysstr(s):
50 50 return s
51 51
52 52
53 53 # Attempt to guide users to a modern pip - this means that 2.6 users
54 54 # should have a chance of getting a 4.2 release, and when we ratchet
55 55 # the version requirement forward again hopefully everyone will get
56 56 # something that works for them.
57 57 if sys.version_info < (2, 7, 0, 'final'):
58 58 pip_message = (
59 59 'This may be due to an out of date pip. '
60 60 'Make sure you have pip >= 9.0.1.'
61 61 )
62 62 try:
63 63 import pip
64 64
65 65 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
66 66 if pip_version < (9, 0, 1):
67 67 pip_message = (
68 68 'Your pip version is out of date, please install '
69 69 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__)
70 70 )
71 71 else:
72 72 # pip is new enough - it must be something else
73 73 pip_message = ''
74 74 except Exception:
75 75 pass
76 76 error = """
77 77 Mercurial does not support Python older than 2.7.
78 78 Python {py} detected.
79 79 {pip}
80 80 """.format(
81 81 py=sys.version_info, pip=pip_message
82 82 )
83 83 printf(error, file=sys.stderr)
84 84 sys.exit(1)
85 85
86 86 if sys.version_info[0] >= 3:
87 87 DYLIB_SUFFIX = sysconfig.get_config_vars()['EXT_SUFFIX']
88 88 else:
89 89 # deprecated in Python 3
90 90 DYLIB_SUFFIX = sysconfig.get_config_vars()['SO']
91 91
92 92 # Solaris Python packaging brain damage
93 93 try:
94 94 import hashlib
95 95
96 96 sha = hashlib.sha1()
97 97 except ImportError:
98 98 try:
99 99 import sha
100 100
101 101 sha.sha # silence unused import warning
102 102 except ImportError:
103 103 raise SystemExit(
104 104 "Couldn't import standard hashlib (incomplete Python install)."
105 105 )
106 106
107 107 try:
108 108 import zlib
109 109
110 110 zlib.compressobj # silence unused import warning
111 111 except ImportError:
112 112 raise SystemExit(
113 113 "Couldn't import standard zlib (incomplete Python install)."
114 114 )
115 115
116 116 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
117 117 isironpython = False
118 118 try:
119 119 isironpython = (
120 120 platform.python_implementation().lower().find("ironpython") != -1
121 121 )
122 122 except AttributeError:
123 123 pass
124 124
125 125 if isironpython:
126 126 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
127 127 else:
128 128 try:
129 129 import bz2
130 130
131 131 bz2.BZ2Compressor # silence unused import warning
132 132 except ImportError:
133 133 raise SystemExit(
134 134 "Couldn't import standard bz2 (incomplete Python install)."
135 135 )
136 136
137 137 ispypy = "PyPy" in sys.version
138 138
139 139 import ctypes
140 140 import errno
141 141 import stat, subprocess, time
142 142 import re
143 143 import shutil
144 144 import tempfile
145 145 from distutils import log
146 146
147 147 # We have issues with setuptools on some platforms and builders. Until
148 148 # those are resolved, setuptools is opt-in except for platforms where
149 149 # we don't have issues.
150 150 issetuptools = os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ
151 151 if issetuptools:
152 152 from setuptools import setup
153 153 else:
154 154 from distutils.core import setup
155 155 from distutils.ccompiler import new_compiler
156 156 from distutils.core import Command, Extension
157 157 from distutils.dist import Distribution
158 158 from distutils.command.build import build
159 159 from distutils.command.build_ext import build_ext
160 160 from distutils.command.build_py import build_py
161 161 from distutils.command.build_scripts import build_scripts
162 162 from distutils.command.install import install
163 163 from distutils.command.install_lib import install_lib
164 164 from distutils.command.install_scripts import install_scripts
165 165 from distutils.spawn import spawn, find_executable
166 166 from distutils import file_util
167 167 from distutils.errors import (
168 168 CCompilerError,
169 169 DistutilsError,
170 170 DistutilsExecError,
171 171 )
172 172 from distutils.sysconfig import get_python_inc, get_config_var
173 173 from distutils.version import StrictVersion
174 174
175 175 # Explain to distutils.StrictVersion how our release candidates are versionned
176 176 StrictVersion.version_re = re.compile(r'^(\d+)\.(\d+)(\.(\d+))?-?(rc(\d+))?$')
177 177
178 178
179 179 def write_if_changed(path, content):
180 180 """Write content to a file iff the content hasn't changed."""
181 181 if os.path.exists(path):
182 182 with open(path, 'rb') as fh:
183 183 current = fh.read()
184 184 else:
185 185 current = b''
186 186
187 187 if current != content:
188 188 with open(path, 'wb') as fh:
189 189 fh.write(content)
190 190
191 191
192 192 scripts = ['hg']
193 193 if os.name == 'nt':
194 194 # We remove hg.bat if we are able to build hg.exe.
195 195 scripts.append('contrib/win32/hg.bat')
196 196
197 197
198 198 def cancompile(cc, code):
199 199 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
200 200 devnull = oldstderr = None
201 201 try:
202 202 fname = os.path.join(tmpdir, 'testcomp.c')
203 203 f = open(fname, 'w')
204 204 f.write(code)
205 205 f.close()
206 206 # Redirect stderr to /dev/null to hide any error messages
207 207 # from the compiler.
208 208 # This will have to be changed if we ever have to check
209 209 # for a function on Windows.
210 210 devnull = open('/dev/null', 'w')
211 211 oldstderr = os.dup(sys.stderr.fileno())
212 212 os.dup2(devnull.fileno(), sys.stderr.fileno())
213 213 objects = cc.compile([fname], output_dir=tmpdir)
214 214 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
215 215 return True
216 216 except Exception:
217 217 return False
218 218 finally:
219 219 if oldstderr is not None:
220 220 os.dup2(oldstderr, sys.stderr.fileno())
221 221 if devnull is not None:
222 222 devnull.close()
223 223 shutil.rmtree(tmpdir)
224 224
225 225
226 226 # simplified version of distutils.ccompiler.CCompiler.has_function
227 227 # that actually removes its temporary files.
228 228 def hasfunction(cc, funcname):
229 229 code = 'int main(void) { %s(); }\n' % funcname
230 230 return cancompile(cc, code)
231 231
232 232
233 233 def hasheader(cc, headername):
234 234 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
235 235 return cancompile(cc, code)
236 236
237 237
238 238 # py2exe needs to be installed to work
239 239 try:
240 240 import py2exe
241 241
242 242 py2exe.Distribution # silence unused import warning
243 243 py2exeloaded = True
244 244 # import py2exe's patched Distribution class
245 245 from distutils.core import Distribution
246 246 except ImportError:
247 247 py2exeloaded = False
248 248
249 249
250 250 def runcmd(cmd, env, cwd=None):
251 251 p = subprocess.Popen(
252 252 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=cwd
253 253 )
254 254 out, err = p.communicate()
255 255 return p.returncode, out, err
256 256
257 257
258 258 class hgcommand(object):
259 259 def __init__(self, cmd, env):
260 260 self.cmd = cmd
261 261 self.env = env
262 262
263 263 def run(self, args):
264 264 cmd = self.cmd + args
265 265 returncode, out, err = runcmd(cmd, self.env)
266 266 err = filterhgerr(err)
267 267 if err or returncode != 0:
268 268 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
269 269 printf(err, file=sys.stderr)
270 270 return b''
271 271 return out
272 272
273 273
274 274 def filterhgerr(err):
275 275 # If root is executing setup.py, but the repository is owned by
276 276 # another user (as in "sudo python setup.py install") we will get
277 277 # trust warnings since the .hg/hgrc file is untrusted. That is
278 278 # fine, we don't want to load it anyway. Python may warn about
279 279 # a missing __init__.py in mercurial/locale, we also ignore that.
280 280 err = [
281 281 e
282 282 for e in err.splitlines()
283 283 if (
284 284 not e.startswith(b'not trusting file')
285 285 and not e.startswith(b'warning: Not importing')
286 286 and not e.startswith(b'obsolete feature not enabled')
287 287 and not e.startswith(b'*** failed to import extension')
288 288 and not e.startswith(b'devel-warn:')
289 289 and not (
290 290 e.startswith(b'(third party extension')
291 291 and e.endswith(b'or newer of Mercurial; disabling)')
292 292 )
293 293 )
294 294 ]
295 295 return b'\n'.join(b' ' + e for e in err)
296 296
297 297
298 298 def findhg():
299 299 """Try to figure out how we should invoke hg for examining the local
300 300 repository contents.
301 301
302 302 Returns an hgcommand object."""
303 303 # By default, prefer the "hg" command in the user's path. This was
304 304 # presumably the hg command that the user used to create this repository.
305 305 #
306 306 # This repository may require extensions or other settings that would not
307 307 # be enabled by running the hg script directly from this local repository.
308 308 hgenv = os.environ.copy()
309 309 # Use HGPLAIN to disable hgrc settings that would change output formatting,
310 310 # and disable localization for the same reasons.
311 311 hgenv['HGPLAIN'] = '1'
312 312 hgenv['LANGUAGE'] = 'C'
313 313 hgcmd = ['hg']
314 314 # Run a simple "hg log" command just to see if using hg from the user's
315 315 # path works and can successfully interact with this repository. Windows
316 316 # gives precedence to hg.exe in the current directory, so fall back to the
317 317 # python invocation of local hg, where pythonXY.dll can always be found.
318 318 check_cmd = ['log', '-r.', '-Ttest']
319 319 if os.name != 'nt' or not os.path.exists("hg.exe"):
320 320 try:
321 321 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
322 322 except EnvironmentError:
323 323 retcode = -1
324 324 if retcode == 0 and not filterhgerr(err):
325 325 return hgcommand(hgcmd, hgenv)
326 326
327 327 # Fall back to trying the local hg installation.
328 328 hgenv = localhgenv()
329 329 hgcmd = [sys.executable, 'hg']
330 330 try:
331 331 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
332 332 except EnvironmentError:
333 333 retcode = -1
334 334 if retcode == 0 and not filterhgerr(err):
335 335 return hgcommand(hgcmd, hgenv)
336 336
337 337 raise SystemExit(
338 338 'Unable to find a working hg binary to extract the '
339 339 'version from the repository tags'
340 340 )
341 341
342 342
343 343 def localhgenv():
344 344 """Get an environment dictionary to use for invoking or importing
345 345 mercurial from the local repository."""
346 346 # Execute hg out of this directory with a custom environment which takes
347 347 # care to not use any hgrc files and do no localization.
348 348 env = {
349 349 'HGMODULEPOLICY': 'py',
350 350 'HGRCPATH': '',
351 351 'LANGUAGE': 'C',
352 352 'PATH': '',
353 353 } # make pypi modules that use os.environ['PATH'] happy
354 354 if 'LD_LIBRARY_PATH' in os.environ:
355 355 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
356 356 if 'SystemRoot' in os.environ:
357 357 # SystemRoot is required by Windows to load various DLLs. See:
358 358 # https://bugs.python.org/issue13524#msg148850
359 359 env['SystemRoot'] = os.environ['SystemRoot']
360 360 return env
361 361
362 362
363 363 version = ''
364 364
365 365 if os.path.isdir('.hg'):
366 366 hg = findhg()
367 367 cmd = ['log', '-r', '.', '--template', '{tags}\n']
368 368 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
369 369 hgid = sysstr(hg.run(['id', '-i'])).strip()
370 370 if not hgid:
371 371 # Bail out if hg is having problems interacting with this repository,
372 372 # rather than falling through and producing a bogus version number.
373 373 # Continuing with an invalid version number will break extensions
374 374 # that define minimumhgversion.
375 375 raise SystemExit('Unable to determine hg version from local repository')
376 376 if numerictags: # tag(s) found
377 377 version = numerictags[-1]
378 378 if hgid.endswith('+'): # propagate the dirty status to the tag
379 379 version += '+'
380 380 else: # no tag found
381 381 ltagcmd = ['parents', '--template', '{latesttag}']
382 382 ltag = sysstr(hg.run(ltagcmd))
383 383 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
384 384 changessince = len(hg.run(changessincecmd).splitlines())
385 385 version = '%s+%s-%s' % (ltag, changessince, hgid)
386 386 if version.endswith('+'):
387 387 version += time.strftime('%Y%m%d')
388 388 elif os.path.exists('.hg_archival.txt'):
389 389 kw = dict(
390 390 [[t.strip() for t in l.split(':', 1)] for l in open('.hg_archival.txt')]
391 391 )
392 392 if 'tag' in kw:
393 393 version = kw['tag']
394 394 elif 'latesttag' in kw:
395 395 if 'changessincelatesttag' in kw:
396 396 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
397 397 else:
398 398 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
399 399 else:
400 400 version = kw.get('node', '')[:12]
401 401
402 402 if version:
403 403 versionb = version
404 404 if not isinstance(versionb, bytes):
405 405 versionb = versionb.encode('ascii')
406 406
407 407 write_if_changed(
408 408 'mercurial/__version__.py',
409 409 b''.join(
410 410 [
411 411 b'# this file is autogenerated by setup.py\n'
412 412 b'version = b"%s"\n' % versionb,
413 413 ]
414 414 ),
415 415 )
416 416
417 417 try:
418 418 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
419 419 os.environ['HGMODULEPOLICY'] = 'py'
420 420 from mercurial import __version__
421 421
422 422 version = __version__.version
423 423 except ImportError:
424 424 version = b'unknown'
425 425 finally:
426 426 if oldpolicy is None:
427 427 del os.environ['HGMODULEPOLICY']
428 428 else:
429 429 os.environ['HGMODULEPOLICY'] = oldpolicy
430 430
431 431
432 432 class hgbuild(build):
433 433 # Insert hgbuildmo first so that files in mercurial/locale/ are found
434 434 # when build_py is run next.
435 435 sub_commands = [('build_mo', None)] + build.sub_commands
436 436
437 437
438 438 class hgbuildmo(build):
439 439
440 440 description = "build translations (.mo files)"
441 441
442 442 def run(self):
443 443 if not find_executable('msgfmt'):
444 444 self.warn(
445 445 "could not find msgfmt executable, no translations "
446 446 "will be built"
447 447 )
448 448 return
449 449
450 450 podir = 'i18n'
451 451 if not os.path.isdir(podir):
452 452 self.warn("could not find %s/ directory" % podir)
453 453 return
454 454
455 455 join = os.path.join
456 456 for po in os.listdir(podir):
457 457 if not po.endswith('.po'):
458 458 continue
459 459 pofile = join(podir, po)
460 460 modir = join('locale', po[:-3], 'LC_MESSAGES')
461 461 mofile = join(modir, 'hg.mo')
462 462 mobuildfile = join('mercurial', mofile)
463 463 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
464 464 if sys.platform != 'sunos5':
465 465 # msgfmt on Solaris does not know about -c
466 466 cmd.append('-c')
467 467 self.mkpath(join('mercurial', modir))
468 468 self.make_file([pofile], mobuildfile, spawn, (cmd,))
469 469
470 470
471 471 class hgdist(Distribution):
472 472 pure = False
473 473 rust = False
474 474 no_rust = False
475 475 cffi = ispypy
476 476
477 477 global_options = Distribution.global_options + [
478 478 ('pure', None, "use pure (slow) Python code instead of C extensions"),
479 479 ('rust', None, "use Rust extensions additionally to C extensions"),
480 480 (
481 481 'no-rust',
482 482 None,
483 483 "do not use Rust extensions additionally to C extensions",
484 484 ),
485 485 ]
486 486
487 487 negative_opt = Distribution.negative_opt.copy()
488 488 boolean_options = ['pure', 'rust', 'no-rust']
489 489 negative_opt['no-rust'] = 'rust'
490 490
491 491 def _set_command_options(self, command_obj, option_dict=None):
492 492 # Not all distutils versions in the wild have boolean_options.
493 493 # This should be cleaned up when we're Python 3 only.
494 494 command_obj.boolean_options = (
495 495 getattr(command_obj, 'boolean_options', []) + self.boolean_options
496 496 )
497 497 return Distribution._set_command_options(
498 498 self, command_obj, option_dict=option_dict
499 499 )
500 500
501 501 def parse_command_line(self):
502 502 ret = Distribution.parse_command_line(self)
503 503 if not (self.rust or self.no_rust):
504 504 hgrustext = os.environ.get('HGWITHRUSTEXT')
505 505 # TODO record it for proper rebuild upon changes
506 506 # (see mercurial/__modulepolicy__.py)
507 507 if hgrustext != 'cpython' and hgrustext is not None:
508 508 if hgrustext:
509 509 msg = 'unkown HGWITHRUSTEXT value: %s' % hgrustext
510 510 printf(msg, file=sys.stderr)
511 511 hgrustext = None
512 512 self.rust = hgrustext is not None
513 513 self.no_rust = not self.rust
514 514 return ret
515 515
516 516 def has_ext_modules(self):
517 517 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
518 518 # too late for some cases
519 519 return not self.pure and Distribution.has_ext_modules(self)
520 520
521 521
522 522 # This is ugly as a one-liner. So use a variable.
523 523 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
524 524 buildextnegops['no-zstd'] = 'zstd'
525 525 buildextnegops['no-rust'] = 'rust'
526 526
527 527
528 528 class hgbuildext(build_ext):
529 529 user_options = build_ext.user_options + [
530 530 ('zstd', None, 'compile zstd bindings [default]'),
531 531 ('no-zstd', None, 'do not compile zstd bindings'),
532 532 (
533 533 'rust',
534 534 None,
535 535 'compile Rust extensions if they are in use '
536 536 '(requires Cargo) [default]',
537 537 ),
538 538 ('no-rust', None, 'do not compile Rust extensions'),
539 539 ]
540 540
541 541 boolean_options = build_ext.boolean_options + ['zstd', 'rust']
542 542 negative_opt = buildextnegops
543 543
544 544 def initialize_options(self):
545 545 self.zstd = True
546 546 self.rust = True
547 547
548 548 return build_ext.initialize_options(self)
549 549
550 550 def finalize_options(self):
551 551 # Unless overridden by the end user, build extensions in parallel.
552 552 # Only influences behavior on Python 3.5+.
553 553 if getattr(self, 'parallel', None) is None:
554 554 self.parallel = True
555 555
556 556 return build_ext.finalize_options(self)
557 557
558 558 def build_extensions(self):
559 559 ruststandalones = [
560 560 e for e in self.extensions if isinstance(e, RustStandaloneExtension)
561 561 ]
562 562 self.extensions = [
563 563 e for e in self.extensions if e not in ruststandalones
564 564 ]
565 565 # Filter out zstd if disabled via argument.
566 566 if not self.zstd:
567 567 self.extensions = [
568 568 e for e in self.extensions if e.name != 'mercurial.zstd'
569 569 ]
570 570
571 571 # Build Rust standalon extensions if it'll be used
572 572 # and its build is not explictely disabled (for external build
573 573 # as Linux distributions would do)
574 574 if self.distribution.rust and self.rust:
575 575 for rustext in ruststandalones:
576 576 rustext.build('' if self.inplace else self.build_lib)
577 577
578 578 return build_ext.build_extensions(self)
579 579
580 580 def build_extension(self, ext):
581 581 if (
582 582 self.distribution.rust
583 583 and self.rust
584 584 and isinstance(ext, RustExtension)
585 585 ):
586 586 ext.rustbuild()
587 587 try:
588 588 build_ext.build_extension(self, ext)
589 589 except CCompilerError:
590 590 if not getattr(ext, 'optional', False):
591 591 raise
592 592 log.warn(
593 593 "Failed to build optional extension '%s' (skipping)", ext.name
594 594 )
595 595
596 596
597 597 class hgbuildscripts(build_scripts):
598 598 def run(self):
599 599 if os.name != 'nt' or self.distribution.pure:
600 600 return build_scripts.run(self)
601 601
602 602 exebuilt = False
603 603 try:
604 604 self.run_command('build_hgexe')
605 605 exebuilt = True
606 606 except (DistutilsError, CCompilerError):
607 607 log.warn('failed to build optional hg.exe')
608 608
609 609 if exebuilt:
610 610 # Copying hg.exe to the scripts build directory ensures it is
611 611 # installed by the install_scripts command.
612 612 hgexecommand = self.get_finalized_command('build_hgexe')
613 613 dest = os.path.join(self.build_dir, 'hg.exe')
614 614 self.mkpath(self.build_dir)
615 615 self.copy_file(hgexecommand.hgexepath, dest)
616 616
617 617 # Remove hg.bat because it is redundant with hg.exe.
618 618 self.scripts.remove('contrib/win32/hg.bat')
619 619
620 620 return build_scripts.run(self)
621 621
622 622
623 623 class hgbuildpy(build_py):
624 624 def finalize_options(self):
625 625 build_py.finalize_options(self)
626 626
627 627 if self.distribution.pure:
628 628 self.distribution.ext_modules = []
629 629 elif self.distribution.cffi:
630 630 from mercurial.cffi import (
631 631 bdiffbuild,
632 632 mpatchbuild,
633 633 )
634 634
635 635 exts = [
636 636 mpatchbuild.ffi.distutils_extension(),
637 637 bdiffbuild.ffi.distutils_extension(),
638 638 ]
639 639 # cffi modules go here
640 640 if sys.platform == 'darwin':
641 641 from mercurial.cffi import osutilbuild
642 642
643 643 exts.append(osutilbuild.ffi.distutils_extension())
644 644 self.distribution.ext_modules = exts
645 645 else:
646 646 h = os.path.join(get_python_inc(), 'Python.h')
647 647 if not os.path.exists(h):
648 648 raise SystemExit(
649 649 'Python headers are required to build '
650 650 'Mercurial but weren\'t found in %s' % h
651 651 )
652 652
653 653 def run(self):
654 654 basepath = os.path.join(self.build_lib, 'mercurial')
655 655 self.mkpath(basepath)
656 656
657 657 rust = self.distribution.rust
658 658 if self.distribution.pure:
659 659 modulepolicy = 'py'
660 660 elif self.build_lib == '.':
661 661 # in-place build should run without rebuilding and Rust extensions
662 662 modulepolicy = 'rust+c-allow' if rust else 'allow'
663 663 else:
664 664 modulepolicy = 'rust+c' if rust else 'c'
665 665
666 666 content = b''.join(
667 667 [
668 668 b'# this file is autogenerated by setup.py\n',
669 669 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
670 670 ]
671 671 )
672 672 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'), content)
673 673
674 674 build_py.run(self)
675 675
676 676
677 677 class buildhgextindex(Command):
678 678 description = 'generate prebuilt index of hgext (for frozen package)'
679 679 user_options = []
680 680 _indexfilename = 'hgext/__index__.py'
681 681
682 682 def initialize_options(self):
683 683 pass
684 684
685 685 def finalize_options(self):
686 686 pass
687 687
688 688 def run(self):
689 689 if os.path.exists(self._indexfilename):
690 690 with open(self._indexfilename, 'w') as f:
691 691 f.write('# empty\n')
692 692
693 693 # here no extension enabled, disabled() lists up everything
694 694 code = (
695 695 'import pprint; from mercurial import extensions; '
696 696 'ext = extensions.disabled();'
697 697 'ext.pop("__index__", None);'
698 698 'pprint.pprint(ext)'
699 699 )
700 700 returncode, out, err = runcmd(
701 701 [sys.executable, '-c', code], localhgenv()
702 702 )
703 703 if err or returncode != 0:
704 704 raise DistutilsExecError(err)
705 705
706 706 with open(self._indexfilename, 'wb') as f:
707 707 f.write(b'# this file is autogenerated by setup.py\n')
708 708 f.write(b'docs = ')
709 709 f.write(out)
710 710
711 711
712 712 class buildhgexe(build_ext):
713 713 description = 'compile hg.exe from mercurial/exewrapper.c'
714 714 user_options = build_ext.user_options + [
715 715 (
716 716 'long-paths-support',
717 717 None,
718 718 'enable support for long paths on '
719 719 'Windows (off by default and '
720 720 'experimental)',
721 721 ),
722 722 ]
723 723
724 724 LONG_PATHS_MANIFEST = """
725 725 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
726 726 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
727 727 <application>
728 728 <windowsSettings
729 729 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
730 730 <ws2:longPathAware>true</ws2:longPathAware>
731 731 </windowsSettings>
732 732 </application>
733 733 </assembly>"""
734 734
735 735 def initialize_options(self):
736 736 build_ext.initialize_options(self)
737 737 self.long_paths_support = False
738 738
739 739 def build_extensions(self):
740 740 if os.name != 'nt':
741 741 return
742 742 if isinstance(self.compiler, HackedMingw32CCompiler):
743 743 self.compiler.compiler_so = self.compiler.compiler # no -mdll
744 744 self.compiler.dll_libraries = [] # no -lmsrvc90
745 745
746 746 pythonlib = None
747 747
748 748 if getattr(sys, 'dllhandle', None):
749 749 # Different Python installs can have different Python library
750 750 # names. e.g. the official CPython distribution uses pythonXY.dll
751 751 # and MinGW uses libpythonX.Y.dll.
752 752 _kernel32 = ctypes.windll.kernel32
753 753 _kernel32.GetModuleFileNameA.argtypes = [
754 754 ctypes.c_void_p,
755 755 ctypes.c_void_p,
756 756 ctypes.c_ulong,
757 757 ]
758 758 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
759 759 size = 1000
760 760 buf = ctypes.create_string_buffer(size + 1)
761 761 filelen = _kernel32.GetModuleFileNameA(
762 762 sys.dllhandle, ctypes.byref(buf), size
763 763 )
764 764
765 765 if filelen > 0 and filelen != size:
766 766 dllbasename = os.path.basename(buf.value)
767 767 if not dllbasename.lower().endswith(b'.dll'):
768 768 raise SystemExit(
769 769 'Python DLL does not end with .dll: %s' % dllbasename
770 770 )
771 771 pythonlib = dllbasename[:-4]
772 772
773 773 if not pythonlib:
774 774 log.warn(
775 775 'could not determine Python DLL filename; assuming pythonXY'
776 776 )
777 777
778 778 hv = sys.hexversion
779 779 pythonlib = b'python%d%d' % (hv >> 24, (hv >> 16) & 0xFF)
780 780
781 781 log.info('using %s as Python library name' % pythonlib)
782 782 with open('mercurial/hgpythonlib.h', 'wb') as f:
783 783 f.write(b'/* this file is autogenerated by setup.py */\n')
784 784 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
785 785
786 786 macros = None
787 787 if sys.version_info[0] >= 3:
788 788 macros = [('_UNICODE', None), ('UNICODE', None)]
789 789
790 790 objects = self.compiler.compile(
791 791 ['mercurial/exewrapper.c'],
792 792 output_dir=self.build_temp,
793 793 macros=macros,
794 794 )
795 795 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
796 796 self.hgtarget = os.path.join(dir, 'hg')
797 797 self.compiler.link_executable(
798 798 objects, self.hgtarget, libraries=[], output_dir=self.build_temp
799 799 )
800 800 if self.long_paths_support:
801 801 self.addlongpathsmanifest()
802 802
803 803 def addlongpathsmanifest(self):
804 804 r"""Add manifest pieces so that hg.exe understands long paths
805 805
806 806 This is an EXPERIMENTAL feature, use with care.
807 807 To enable long paths support, one needs to do two things:
808 808 - build Mercurial with --long-paths-support option
809 809 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
810 810 LongPathsEnabled to have value 1.
811 811
812 812 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
813 813 it happens because Mercurial uses mt.exe circa 2008, which is not
814 814 yet aware of long paths support in the manifest (I think so at least).
815 815 This does not stop mt.exe from embedding/merging the XML properly.
816 816
817 817 Why resource #1 should be used for .exe manifests? I don't know and
818 818 wasn't able to find an explanation for mortals. But it seems to work.
819 819 """
820 820 exefname = self.compiler.executable_filename(self.hgtarget)
821 821 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
822 822 os.close(fdauto)
823 823 with open(manfname, 'w') as f:
824 824 f.write(self.LONG_PATHS_MANIFEST)
825 825 log.info("long paths manifest is written to '%s'" % manfname)
826 826 inputresource = '-inputresource:%s;#1' % exefname
827 827 outputresource = '-outputresource:%s;#1' % exefname
828 828 log.info("running mt.exe to update hg.exe's manifest in-place")
829 829 # supplying both -manifest and -inputresource to mt.exe makes
830 830 # it merge the embedded and supplied manifests in the -outputresource
831 831 self.spawn(
832 832 [
833 833 'mt.exe',
834 834 '-nologo',
835 835 '-manifest',
836 836 manfname,
837 837 inputresource,
838 838 outputresource,
839 839 ]
840 840 )
841 841 log.info("done updating hg.exe's manifest")
842 842 os.remove(manfname)
843 843
844 844 @property
845 845 def hgexepath(self):
846 846 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
847 847 return os.path.join(self.build_temp, dir, 'hg.exe')
848 848
849 849
850 850 class hgbuilddoc(Command):
851 851 description = 'build documentation'
852 852 user_options = [
853 853 ('man', None, 'generate man pages'),
854 854 ('html', None, 'generate html pages'),
855 855 ]
856 856
857 857 def initialize_options(self):
858 858 self.man = None
859 859 self.html = None
860 860
861 861 def finalize_options(self):
862 862 # If --man or --html are set, only generate what we're told to.
863 863 # Otherwise generate everything.
864 864 have_subset = self.man is not None or self.html is not None
865 865
866 866 if have_subset:
867 867 self.man = True if self.man else False
868 868 self.html = True if self.html else False
869 869 else:
870 870 self.man = True
871 871 self.html = True
872 872
873 873 def run(self):
874 874 def normalizecrlf(p):
875 875 with open(p, 'rb') as fh:
876 876 orig = fh.read()
877 877
878 878 if b'\r\n' not in orig:
879 879 return
880 880
881 881 log.info('normalizing %s to LF line endings' % p)
882 882 with open(p, 'wb') as fh:
883 883 fh.write(orig.replace(b'\r\n', b'\n'))
884 884
885 885 def gentxt(root):
886 886 txt = 'doc/%s.txt' % root
887 887 log.info('generating %s' % txt)
888 888 res, out, err = runcmd(
889 889 [sys.executable, 'gendoc.py', root], os.environ, cwd='doc'
890 890 )
891 891 if res:
892 892 raise SystemExit(
893 'error running gendoc.py: %s' % '\n'.join([out, err])
893 'error running gendoc.py: %s'
894 % '\n'.join([sysstr(out), sysstr(err)])
894 895 )
895 896
896 897 with open(txt, 'wb') as fh:
897 898 fh.write(out)
898 899
899 900 def gengendoc(root):
900 901 gendoc = 'doc/%s.gendoc.txt' % root
901 902
902 903 log.info('generating %s' % gendoc)
903 904 res, out, err = runcmd(
904 905 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
905 906 os.environ,
906 907 cwd='doc',
907 908 )
908 909 if res:
909 910 raise SystemExit(
910 'error running gendoc: %s' % '\n'.join([out, err])
911 'error running gendoc: %s'
912 % '\n'.join([sysstr(out), sysstr(err)])
911 913 )
912 914
913 915 with open(gendoc, 'wb') as fh:
914 916 fh.write(out)
915 917
916 918 def genman(root):
917 919 log.info('generating doc/%s' % root)
918 920 res, out, err = runcmd(
919 921 [
920 922 sys.executable,
921 923 'runrst',
922 924 'hgmanpage',
923 925 '--halt',
924 926 'warning',
925 927 '--strip-elements-with-class',
926 928 'htmlonly',
927 929 '%s.txt' % root,
928 930 root,
929 931 ],
930 932 os.environ,
931 933 cwd='doc',
932 934 )
933 935 if res:
934 936 raise SystemExit(
935 'error running runrst: %s' % '\n'.join([out, err])
937 'error running runrst: %s'
938 % '\n'.join([sysstr(out), sysstr(err)])
936 939 )
937 940
938 941 normalizecrlf('doc/%s' % root)
939 942
940 943 def genhtml(root):
941 944 log.info('generating doc/%s.html' % root)
942 945 res, out, err = runcmd(
943 946 [
944 947 sys.executable,
945 948 'runrst',
946 949 'html',
947 950 '--halt',
948 951 'warning',
949 952 '--link-stylesheet',
950 953 '--stylesheet-path',
951 954 'style.css',
952 955 '%s.txt' % root,
953 956 '%s.html' % root,
954 957 ],
955 958 os.environ,
956 959 cwd='doc',
957 960 )
958 961 if res:
959 962 raise SystemExit(
960 'error running runrst: %s' % '\n'.join([out, err])
963 'error running runrst: %s'
964 % '\n'.join([sysstr(out), sysstr(err)])
961 965 )
962 966
963 967 normalizecrlf('doc/%s.html' % root)
964 968
965 969 # This logic is duplicated in doc/Makefile.
966 970 sources = {
967 971 f
968 972 for f in os.listdir('mercurial/helptext')
969 973 if re.search(r'[0-9]\.txt$', f)
970 974 }
971 975
972 976 # common.txt is a one-off.
973 977 gentxt('common')
974 978
975 979 for source in sorted(sources):
976 980 assert source[-4:] == '.txt'
977 981 root = source[:-4]
978 982
979 983 gentxt(root)
980 984 gengendoc(root)
981 985
982 986 if self.man:
983 987 genman(root)
984 988 if self.html:
985 989 genhtml(root)
986 990
987 991
988 992 class hginstall(install):
989 993
990 994 user_options = install.user_options + [
991 995 (
992 996 'old-and-unmanageable',
993 997 None,
994 998 'noop, present for eggless setuptools compat',
995 999 ),
996 1000 (
997 1001 'single-version-externally-managed',
998 1002 None,
999 1003 'noop, present for eggless setuptools compat',
1000 1004 ),
1001 1005 ]
1002 1006
1003 1007 # Also helps setuptools not be sad while we refuse to create eggs.
1004 1008 single_version_externally_managed = True
1005 1009
1006 1010 def get_sub_commands(self):
1007 1011 # Screen out egg related commands to prevent egg generation. But allow
1008 1012 # mercurial.egg-info generation, since that is part of modern
1009 1013 # packaging.
1010 1014 excl = {'bdist_egg'}
1011 1015 return filter(lambda x: x not in excl, install.get_sub_commands(self))
1012 1016
1013 1017
1014 1018 class hginstalllib(install_lib):
1015 1019 '''
1016 1020 This is a specialization of install_lib that replaces the copy_file used
1017 1021 there so that it supports setting the mode of files after copying them,
1018 1022 instead of just preserving the mode that the files originally had. If your
1019 1023 system has a umask of something like 027, preserving the permissions when
1020 1024 copying will lead to a broken install.
1021 1025
1022 1026 Note that just passing keep_permissions=False to copy_file would be
1023 1027 insufficient, as it might still be applying a umask.
1024 1028 '''
1025 1029
1026 1030 def run(self):
1027 1031 realcopyfile = file_util.copy_file
1028 1032
1029 1033 def copyfileandsetmode(*args, **kwargs):
1030 1034 src, dst = args[0], args[1]
1031 1035 dst, copied = realcopyfile(*args, **kwargs)
1032 1036 if copied:
1033 1037 st = os.stat(src)
1034 1038 # Persist executable bit (apply it to group and other if user
1035 1039 # has it)
1036 1040 if st[stat.ST_MODE] & stat.S_IXUSR:
1037 1041 setmode = int('0755', 8)
1038 1042 else:
1039 1043 setmode = int('0644', 8)
1040 1044 m = stat.S_IMODE(st[stat.ST_MODE])
1041 1045 m = (m & ~int('0777', 8)) | setmode
1042 1046 os.chmod(dst, m)
1043 1047
1044 1048 file_util.copy_file = copyfileandsetmode
1045 1049 try:
1046 1050 install_lib.run(self)
1047 1051 finally:
1048 1052 file_util.copy_file = realcopyfile
1049 1053
1050 1054
1051 1055 class hginstallscripts(install_scripts):
1052 1056 '''
1053 1057 This is a specialization of install_scripts that replaces the @LIBDIR@ with
1054 1058 the configured directory for modules. If possible, the path is made relative
1055 1059 to the directory for scripts.
1056 1060 '''
1057 1061
1058 1062 def initialize_options(self):
1059 1063 install_scripts.initialize_options(self)
1060 1064
1061 1065 self.install_lib = None
1062 1066
1063 1067 def finalize_options(self):
1064 1068 install_scripts.finalize_options(self)
1065 1069 self.set_undefined_options('install', ('install_lib', 'install_lib'))
1066 1070
1067 1071 def run(self):
1068 1072 install_scripts.run(self)
1069 1073
1070 1074 # It only makes sense to replace @LIBDIR@ with the install path if
1071 1075 # the install path is known. For wheels, the logic below calculates
1072 1076 # the libdir to be "../..". This is because the internal layout of a
1073 1077 # wheel archive looks like:
1074 1078 #
1075 1079 # mercurial-3.6.1.data/scripts/hg
1076 1080 # mercurial/__init__.py
1077 1081 #
1078 1082 # When installing wheels, the subdirectories of the "<pkg>.data"
1079 1083 # directory are translated to system local paths and files therein
1080 1084 # are copied in place. The mercurial/* files are installed into the
1081 1085 # site-packages directory. However, the site-packages directory
1082 1086 # isn't known until wheel install time. This means we have no clue
1083 1087 # at wheel generation time what the installed site-packages directory
1084 1088 # will be. And, wheels don't appear to provide the ability to register
1085 1089 # custom code to run during wheel installation. This all means that
1086 1090 # we can't reliably set the libdir in wheels: the default behavior
1087 1091 # of looking in sys.path must do.
1088 1092
1089 1093 if (
1090 1094 os.path.splitdrive(self.install_dir)[0]
1091 1095 != os.path.splitdrive(self.install_lib)[0]
1092 1096 ):
1093 1097 # can't make relative paths from one drive to another, so use an
1094 1098 # absolute path instead
1095 1099 libdir = self.install_lib
1096 1100 else:
1097 1101 libdir = os.path.relpath(self.install_lib, self.install_dir)
1098 1102
1099 1103 for outfile in self.outfiles:
1100 1104 with open(outfile, 'rb') as fp:
1101 1105 data = fp.read()
1102 1106
1103 1107 # skip binary files
1104 1108 if b'\0' in data:
1105 1109 continue
1106 1110
1107 1111 # During local installs, the shebang will be rewritten to the final
1108 1112 # install path. During wheel packaging, the shebang has a special
1109 1113 # value.
1110 1114 if data.startswith(b'#!python'):
1111 1115 log.info(
1112 1116 'not rewriting @LIBDIR@ in %s because install path '
1113 1117 'not known' % outfile
1114 1118 )
1115 1119 continue
1116 1120
1117 1121 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
1118 1122 with open(outfile, 'wb') as fp:
1119 1123 fp.write(data)
1120 1124
1121 1125
1122 1126 # virtualenv installs custom distutils/__init__.py and
1123 1127 # distutils/distutils.cfg files which essentially proxy back to the
1124 1128 # "real" distutils in the main Python install. The presence of this
1125 1129 # directory causes py2exe to pick up the "hacked" distutils package
1126 1130 # from the virtualenv and "import distutils" will fail from the py2exe
1127 1131 # build because the "real" distutils files can't be located.
1128 1132 #
1129 1133 # We work around this by monkeypatching the py2exe code finding Python
1130 1134 # modules to replace the found virtualenv distutils modules with the
1131 1135 # original versions via filesystem scanning. This is a bit hacky. But
1132 1136 # it allows us to use virtualenvs for py2exe packaging, which is more
1133 1137 # deterministic and reproducible.
1134 1138 #
1135 1139 # It's worth noting that the common StackOverflow suggestions for this
1136 1140 # problem involve copying the original distutils files into the
1137 1141 # virtualenv or into the staging directory after setup() is invoked.
1138 1142 # The former is very brittle and can easily break setup(). Our hacking
1139 1143 # of the found modules routine has a similar result as copying the files
1140 1144 # manually. But it makes fewer assumptions about how py2exe works and
1141 1145 # is less brittle.
1142 1146
1143 1147 # This only catches virtualenvs made with virtualenv (as opposed to
1144 1148 # venv, which is likely what Python 3 uses).
1145 1149 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
1146 1150
1147 1151 if py2exehacked:
1148 1152 from distutils.command.py2exe import py2exe as buildpy2exe
1149 1153 from py2exe.mf import Module as py2exemodule
1150 1154
1151 1155 class hgbuildpy2exe(buildpy2exe):
1152 1156 def find_needed_modules(self, mf, files, modules):
1153 1157 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
1154 1158
1155 1159 # Replace virtualenv's distutils modules with the real ones.
1156 1160 modules = {}
1157 1161 for k, v in res.modules.items():
1158 1162 if k != 'distutils' and not k.startswith('distutils.'):
1159 1163 modules[k] = v
1160 1164
1161 1165 res.modules = modules
1162 1166
1163 1167 import opcode
1164 1168
1165 1169 distutilsreal = os.path.join(
1166 1170 os.path.dirname(opcode.__file__), 'distutils'
1167 1171 )
1168 1172
1169 1173 for root, dirs, files in os.walk(distutilsreal):
1170 1174 for f in sorted(files):
1171 1175 if not f.endswith('.py'):
1172 1176 continue
1173 1177
1174 1178 full = os.path.join(root, f)
1175 1179
1176 1180 parents = ['distutils']
1177 1181
1178 1182 if root != distutilsreal:
1179 1183 rel = os.path.relpath(root, distutilsreal)
1180 1184 parents.extend(p for p in rel.split(os.sep))
1181 1185
1182 1186 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1183 1187
1184 1188 if modname.startswith('distutils.tests.'):
1185 1189 continue
1186 1190
1187 1191 if modname.endswith('.__init__'):
1188 1192 modname = modname[: -len('.__init__')]
1189 1193 path = os.path.dirname(full)
1190 1194 else:
1191 1195 path = None
1192 1196
1193 1197 res.modules[modname] = py2exemodule(
1194 1198 modname, full, path=path
1195 1199 )
1196 1200
1197 1201 if 'distutils' not in res.modules:
1198 1202 raise SystemExit('could not find distutils modules')
1199 1203
1200 1204 return res
1201 1205
1202 1206
1203 1207 cmdclass = {
1204 1208 'build': hgbuild,
1205 1209 'build_doc': hgbuilddoc,
1206 1210 'build_mo': hgbuildmo,
1207 1211 'build_ext': hgbuildext,
1208 1212 'build_py': hgbuildpy,
1209 1213 'build_scripts': hgbuildscripts,
1210 1214 'build_hgextindex': buildhgextindex,
1211 1215 'install': hginstall,
1212 1216 'install_lib': hginstalllib,
1213 1217 'install_scripts': hginstallscripts,
1214 1218 'build_hgexe': buildhgexe,
1215 1219 }
1216 1220
1217 1221 if py2exehacked:
1218 1222 cmdclass['py2exe'] = hgbuildpy2exe
1219 1223
1220 1224 packages = [
1221 1225 'mercurial',
1222 1226 'mercurial.cext',
1223 1227 'mercurial.cffi',
1224 1228 'mercurial.defaultrc',
1225 1229 'mercurial.helptext',
1226 1230 'mercurial.helptext.internals',
1227 1231 'mercurial.hgweb',
1228 1232 'mercurial.interfaces',
1229 1233 'mercurial.pure',
1230 1234 'mercurial.thirdparty',
1231 1235 'mercurial.thirdparty.attr',
1232 1236 'mercurial.thirdparty.zope',
1233 1237 'mercurial.thirdparty.zope.interface',
1234 1238 'mercurial.utils',
1235 1239 'mercurial.revlogutils',
1236 1240 'mercurial.testing',
1237 1241 'hgext',
1238 1242 'hgext.convert',
1239 1243 'hgext.fsmonitor',
1240 1244 'hgext.fastannotate',
1241 1245 'hgext.fsmonitor.pywatchman',
1242 1246 'hgext.git',
1243 1247 'hgext.highlight',
1244 1248 'hgext.hooklib',
1245 1249 'hgext.infinitepush',
1246 1250 'hgext.largefiles',
1247 1251 'hgext.lfs',
1248 1252 'hgext.narrow',
1249 1253 'hgext.remotefilelog',
1250 1254 'hgext.zeroconf',
1251 1255 'hgext3rd',
1252 1256 'hgdemandimport',
1253 1257 ]
1254 1258 if sys.version_info[0] == 2:
1255 1259 packages.extend(
1256 1260 [
1257 1261 'mercurial.thirdparty.concurrent',
1258 1262 'mercurial.thirdparty.concurrent.futures',
1259 1263 ]
1260 1264 )
1261 1265
1262 1266 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1263 1267 # py2exe can't cope with namespace packages very well, so we have to
1264 1268 # install any hgext3rd.* extensions that we want in the final py2exe
1265 1269 # image here. This is gross, but you gotta do what you gotta do.
1266 1270 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1267 1271
1268 1272 common_depends = [
1269 1273 'mercurial/bitmanipulation.h',
1270 1274 'mercurial/compat.h',
1271 1275 'mercurial/cext/util.h',
1272 1276 ]
1273 1277 common_include_dirs = ['mercurial']
1274 1278
1275 1279 common_cflags = []
1276 1280
1277 1281 # MSVC 2008 still needs declarations at the top of the scope, but Python 3.9
1278 1282 # makes declarations not at the top of a scope in the headers.
1279 1283 if os.name != 'nt' and sys.version_info[1] < 9:
1280 1284 common_cflags = ['-Werror=declaration-after-statement']
1281 1285
1282 1286 osutil_cflags = []
1283 1287 osutil_ldflags = []
1284 1288
1285 1289 # platform specific macros
1286 1290 for plat, func in [('bsd', 'setproctitle')]:
1287 1291 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1288 1292 osutil_cflags.append('-DHAVE_%s' % func.upper())
1289 1293
1290 1294 for plat, macro, code in [
1291 1295 (
1292 1296 'bsd|darwin',
1293 1297 'BSD_STATFS',
1294 1298 '''
1295 1299 #include <sys/param.h>
1296 1300 #include <sys/mount.h>
1297 1301 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1298 1302 ''',
1299 1303 ),
1300 1304 (
1301 1305 'linux',
1302 1306 'LINUX_STATFS',
1303 1307 '''
1304 1308 #include <linux/magic.h>
1305 1309 #include <sys/vfs.h>
1306 1310 int main() { struct statfs s; return sizeof(s.f_type); }
1307 1311 ''',
1308 1312 ),
1309 1313 ]:
1310 1314 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1311 1315 osutil_cflags.append('-DHAVE_%s' % macro)
1312 1316
1313 1317 if sys.platform == 'darwin':
1314 1318 osutil_ldflags += ['-framework', 'ApplicationServices']
1315 1319
1320 if sys.platform == 'sunos5':
1321 osutil_ldflags += ['-lsocket']
1322
1316 1323 xdiff_srcs = [
1317 1324 'mercurial/thirdparty/xdiff/xdiffi.c',
1318 1325 'mercurial/thirdparty/xdiff/xprepare.c',
1319 1326 'mercurial/thirdparty/xdiff/xutils.c',
1320 1327 ]
1321 1328
1322 1329 xdiff_headers = [
1323 1330 'mercurial/thirdparty/xdiff/xdiff.h',
1324 1331 'mercurial/thirdparty/xdiff/xdiffi.h',
1325 1332 'mercurial/thirdparty/xdiff/xinclude.h',
1326 1333 'mercurial/thirdparty/xdiff/xmacros.h',
1327 1334 'mercurial/thirdparty/xdiff/xprepare.h',
1328 1335 'mercurial/thirdparty/xdiff/xtypes.h',
1329 1336 'mercurial/thirdparty/xdiff/xutils.h',
1330 1337 ]
1331 1338
1332 1339
1333 1340 class RustCompilationError(CCompilerError):
1334 1341 """Exception class for Rust compilation errors."""
1335 1342
1336 1343
1337 1344 class RustExtension(Extension):
1338 1345 """Base classes for concrete Rust Extension classes.
1339 1346 """
1340 1347
1341 1348 rusttargetdir = os.path.join('rust', 'target', 'release')
1342 1349
1343 1350 def __init__(
1344 1351 self, mpath, sources, rustlibname, subcrate, py3_features=None, **kw
1345 1352 ):
1346 1353 Extension.__init__(self, mpath, sources, **kw)
1347 1354 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1348 1355 self.py3_features = py3_features
1349 1356
1350 1357 # adding Rust source and control files to depends so that the extension
1351 1358 # gets rebuilt if they've changed
1352 1359 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1353 1360 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1354 1361 if os.path.exists(cargo_lock):
1355 1362 self.depends.append(cargo_lock)
1356 1363 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1357 1364 self.depends.extend(
1358 1365 os.path.join(dirpath, fname)
1359 1366 for fname in fnames
1360 1367 if os.path.splitext(fname)[1] == '.rs'
1361 1368 )
1362 1369
1363 1370 @staticmethod
1364 1371 def rustdylibsuffix():
1365 1372 """Return the suffix for shared libraries produced by rustc.
1366 1373
1367 1374 See also: https://doc.rust-lang.org/reference/linkage.html
1368 1375 """
1369 1376 if sys.platform == 'darwin':
1370 1377 return '.dylib'
1371 1378 elif os.name == 'nt':
1372 1379 return '.dll'
1373 1380 else:
1374 1381 return '.so'
1375 1382
1376 1383 def rustbuild(self):
1377 1384 env = os.environ.copy()
1378 1385 if 'HGTEST_RESTOREENV' in env:
1379 1386 # Mercurial tests change HOME to a temporary directory,
1380 1387 # but, if installed with rustup, the Rust toolchain needs
1381 1388 # HOME to be correct (otherwise the 'no default toolchain'
1382 1389 # error message is issued and the build fails).
1383 1390 # This happens currently with test-hghave.t, which does
1384 1391 # invoke this build.
1385 1392
1386 1393 # Unix only fix (os.path.expanduser not really reliable if
1387 1394 # HOME is shadowed like this)
1388 1395 import pwd
1389 1396
1390 1397 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1391 1398
1392 1399 cargocmd = ['cargo', 'rustc', '-vv', '--release']
1393 1400
1394 1401 feature_flags = []
1395 1402
1396 1403 if sys.version_info[0] == 3 and self.py3_features is not None:
1397 1404 feature_flags.append(self.py3_features)
1398 1405 cargocmd.append('--no-default-features')
1399 1406
1400 1407 rust_features = env.get("HG_RUST_FEATURES")
1401 1408 if rust_features:
1402 1409 feature_flags.append(rust_features)
1403 1410
1404 1411 cargocmd.extend(('--features', " ".join(feature_flags)))
1405 1412
1406 1413 cargocmd.append('--')
1407 1414 if sys.platform == 'darwin':
1408 1415 cargocmd.extend(
1409 1416 ("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup")
1410 1417 )
1411 1418 try:
1412 1419 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1413 1420 except OSError as exc:
1414 1421 if exc.errno == errno.ENOENT:
1415 1422 raise RustCompilationError("Cargo not found")
1416 1423 elif exc.errno == errno.EACCES:
1417 1424 raise RustCompilationError(
1418 1425 "Cargo found, but permisssion to execute it is denied"
1419 1426 )
1420 1427 else:
1421 1428 raise
1422 1429 except subprocess.CalledProcessError:
1423 1430 raise RustCompilationError(
1424 1431 "Cargo failed. Working directory: %r, "
1425 1432 "command: %r, environment: %r"
1426 1433 % (self.rustsrcdir, cargocmd, env)
1427 1434 )
1428 1435
1429 1436
1430 1437 class RustStandaloneExtension(RustExtension):
1431 1438 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1432 1439 RustExtension.__init__(
1433 1440 self, pydottedname, [], dylibname, rustcrate, **kw
1434 1441 )
1435 1442 self.dylibname = dylibname
1436 1443
1437 1444 def build(self, target_dir):
1438 1445 self.rustbuild()
1439 1446 target = [target_dir]
1440 1447 target.extend(self.name.split('.'))
1441 1448 target[-1] += DYLIB_SUFFIX
1442 1449 shutil.copy2(
1443 1450 os.path.join(
1444 1451 self.rusttargetdir, self.dylibname + self.rustdylibsuffix()
1445 1452 ),
1446 1453 os.path.join(*target),
1447 1454 )
1448 1455
1449 1456
1450 1457 extmodules = [
1451 1458 Extension(
1452 1459 'mercurial.cext.base85',
1453 1460 ['mercurial/cext/base85.c'],
1454 1461 include_dirs=common_include_dirs,
1455 1462 extra_compile_args=common_cflags,
1456 1463 depends=common_depends,
1457 1464 ),
1458 1465 Extension(
1459 1466 'mercurial.cext.bdiff',
1460 1467 ['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1461 1468 include_dirs=common_include_dirs,
1462 1469 extra_compile_args=common_cflags,
1463 1470 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers,
1464 1471 ),
1465 1472 Extension(
1466 1473 'mercurial.cext.mpatch',
1467 1474 ['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'],
1468 1475 include_dirs=common_include_dirs,
1469 1476 extra_compile_args=common_cflags,
1470 1477 depends=common_depends,
1471 1478 ),
1472 1479 Extension(
1473 1480 'mercurial.cext.parsers',
1474 1481 [
1475 1482 'mercurial/cext/charencode.c',
1476 1483 'mercurial/cext/dirs.c',
1477 1484 'mercurial/cext/manifest.c',
1478 1485 'mercurial/cext/parsers.c',
1479 1486 'mercurial/cext/pathencode.c',
1480 1487 'mercurial/cext/revlog.c',
1481 1488 ],
1482 1489 include_dirs=common_include_dirs,
1483 1490 extra_compile_args=common_cflags,
1484 1491 depends=common_depends
1485 1492 + ['mercurial/cext/charencode.h', 'mercurial/cext/revlog.h',],
1486 1493 ),
1487 1494 Extension(
1488 1495 'mercurial.cext.osutil',
1489 1496 ['mercurial/cext/osutil.c'],
1490 1497 include_dirs=common_include_dirs,
1491 1498 extra_compile_args=common_cflags + osutil_cflags,
1492 1499 extra_link_args=osutil_ldflags,
1493 1500 depends=common_depends,
1494 1501 ),
1495 1502 Extension(
1496 1503 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations',
1497 1504 [
1498 1505 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1499 1506 ],
1500 1507 extra_compile_args=common_cflags,
1501 1508 ),
1502 1509 Extension(
1503 1510 'mercurial.thirdparty.sha1dc',
1504 1511 [
1505 1512 'mercurial/thirdparty/sha1dc/cext.c',
1506 1513 'mercurial/thirdparty/sha1dc/lib/sha1.c',
1507 1514 'mercurial/thirdparty/sha1dc/lib/ubc_check.c',
1508 1515 ],
1509 1516 extra_compile_args=common_cflags,
1510 1517 ),
1511 1518 Extension(
1512 1519 'hgext.fsmonitor.pywatchman.bser',
1513 1520 ['hgext/fsmonitor/pywatchman/bser.c'],
1514 1521 extra_compile_args=common_cflags,
1515 1522 ),
1516 1523 RustStandaloneExtension(
1517 1524 'mercurial.rustext', 'hg-cpython', 'librusthg', py3_features='python3'
1518 1525 ),
1519 1526 ]
1520 1527
1521 1528
1522 1529 sys.path.insert(0, 'contrib/python-zstandard')
1523 1530 import setup_zstd
1524 1531
1525 1532 zstd = setup_zstd.get_c_extension(
1526 1533 name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__))
1527 1534 )
1528 1535 zstd.extra_compile_args += common_cflags
1529 1536 extmodules.append(zstd)
1530 1537
1531 1538 try:
1532 1539 from distutils import cygwinccompiler
1533 1540
1534 1541 # the -mno-cygwin option has been deprecated for years
1535 1542 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1536 1543
1537 1544 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1538 1545 def __init__(self, *args, **kwargs):
1539 1546 mingw32compilerclass.__init__(self, *args, **kwargs)
1540 1547 for i in 'compiler compiler_so linker_exe linker_so'.split():
1541 1548 try:
1542 1549 getattr(self, i).remove('-mno-cygwin')
1543 1550 except ValueError:
1544 1551 pass
1545 1552
1546 1553 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1547 1554 except ImportError:
1548 1555 # the cygwinccompiler package is not available on some Python
1549 1556 # distributions like the ones from the optware project for Synology
1550 1557 # DiskStation boxes
1551 1558 class HackedMingw32CCompiler(object):
1552 1559 pass
1553 1560
1554 1561
1555 1562 if os.name == 'nt':
1556 1563 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1557 1564 # extra_link_args to distutils.extensions.Extension() doesn't have any
1558 1565 # effect.
1559 1566 from distutils import msvccompiler
1560 1567
1561 1568 msvccompilerclass = msvccompiler.MSVCCompiler
1562 1569
1563 1570 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1564 1571 def initialize(self):
1565 1572 msvccompilerclass.initialize(self)
1566 1573 # "warning LNK4197: export 'func' specified multiple times"
1567 1574 self.ldflags_shared.append('/ignore:4197')
1568 1575 self.ldflags_shared_debug.append('/ignore:4197')
1569 1576
1570 1577 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1571 1578
1572 1579 packagedata = {
1573 1580 'mercurial': [
1574 1581 'locale/*/LC_MESSAGES/hg.mo',
1575 1582 'defaultrc/*.rc',
1576 1583 'dummycert.pem',
1577 1584 ],
1578 1585 'mercurial.helptext': ['*.txt',],
1579 1586 'mercurial.helptext.internals': ['*.txt',],
1580 1587 }
1581 1588
1582 1589
1583 1590 def ordinarypath(p):
1584 1591 return p and p[0] != '.' and p[-1] != '~'
1585 1592
1586 1593
1587 1594 for root in ('templates',):
1588 1595 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1589 1596 curdir = curdir.split(os.sep, 1)[1]
1590 1597 dirs[:] = filter(ordinarypath, dirs)
1591 1598 for f in filter(ordinarypath, files):
1592 1599 f = os.path.join(curdir, f)
1593 1600 packagedata['mercurial'].append(f)
1594 1601
1595 1602 datafiles = []
1596 1603
1597 1604 # distutils expects version to be str/unicode. Converting it to
1598 1605 # unicode on Python 2 still works because it won't contain any
1599 1606 # non-ascii bytes and will be implicitly converted back to bytes
1600 1607 # when operated on.
1601 1608 assert isinstance(version, bytes)
1602 1609 setupversion = version.decode('ascii')
1603 1610
1604 1611 extra = {}
1605 1612
1606 1613 py2exepackages = [
1607 1614 'hgdemandimport',
1608 1615 'hgext3rd',
1609 1616 'hgext',
1610 1617 'email',
1611 1618 # implicitly imported per module policy
1612 1619 # (cffi wouldn't be used as a frozen exe)
1613 1620 'mercurial.cext',
1614 1621 #'mercurial.cffi',
1615 1622 'mercurial.pure',
1616 1623 ]
1617 1624
1618 1625 py2exeexcludes = []
1619 1626 py2exedllexcludes = ['crypt32.dll']
1620 1627
1621 1628 if issetuptools:
1622 1629 extra['python_requires'] = supportedpy
1623 1630
1624 1631 if py2exeloaded:
1625 1632 extra['console'] = [
1626 1633 {
1627 1634 'script': 'hg',
1628 1635 'copyright': 'Copyright (C) 2005-2020 Matt Mackall and others',
1629 1636 'product_version': version,
1630 1637 }
1631 1638 ]
1632 1639 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1633 1640 # Need to override hgbuild because it has a private copy of
1634 1641 # build.sub_commands.
1635 1642 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1636 1643 # put dlls in sub directory so that they won't pollute PATH
1637 1644 extra['zipfile'] = 'lib/library.zip'
1638 1645
1639 1646 # We allow some configuration to be supplemented via environment
1640 1647 # variables. This is better than setup.cfg files because it allows
1641 1648 # supplementing configs instead of replacing them.
1642 1649 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1643 1650 if extrapackages:
1644 1651 py2exepackages.extend(extrapackages.split(' '))
1645 1652
1646 1653 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1647 1654 if excludes:
1648 1655 py2exeexcludes.extend(excludes.split(' '))
1649 1656
1650 1657 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1651 1658 if dllexcludes:
1652 1659 py2exedllexcludes.extend(dllexcludes.split(' '))
1653 1660
1654 1661 if os.name == 'nt':
1655 1662 # Windows binary file versions for exe/dll files must have the
1656 1663 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1657 1664 setupversion = setupversion.split(r'+', 1)[0]
1658 1665
1659 1666 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1660 1667 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1661 1668 if version:
1662 1669 version = version[0]
1663 1670 if sys.version_info[0] == 3:
1664 1671 version = version.decode('utf-8')
1665 1672 xcode4 = version.startswith('Xcode') and StrictVersion(
1666 1673 version.split()[1]
1667 1674 ) >= StrictVersion('4.0')
1668 1675 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1669 1676 else:
1670 1677 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1671 1678 # installed, but instead with only command-line tools. Assume
1672 1679 # that only happens on >= Lion, thus no PPC support.
1673 1680 xcode4 = True
1674 1681 xcode51 = False
1675 1682
1676 1683 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1677 1684 # distutils.sysconfig
1678 1685 if xcode4:
1679 1686 os.environ['ARCHFLAGS'] = ''
1680 1687
1681 1688 # XCode 5.1 changes clang such that it now fails to compile if the
1682 1689 # -mno-fused-madd flag is passed, but the version of Python shipped with
1683 1690 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1684 1691 # C extension modules, and a bug has been filed upstream at
1685 1692 # http://bugs.python.org/issue21244. We also need to patch this here
1686 1693 # so Mercurial can continue to compile in the meantime.
1687 1694 if xcode51:
1688 1695 cflags = get_config_var('CFLAGS')
1689 1696 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1690 1697 os.environ['CFLAGS'] = (
1691 1698 os.environ.get('CFLAGS', '') + ' -Qunused-arguments'
1692 1699 )
1693 1700
1694 1701 setup(
1695 1702 name='mercurial',
1696 1703 version=setupversion,
1697 1704 author='Matt Mackall and many others',
1698 1705 author_email='mercurial@mercurial-scm.org',
1699 1706 url='https://mercurial-scm.org/',
1700 1707 download_url='https://mercurial-scm.org/release/',
1701 1708 description=(
1702 1709 'Fast scalable distributed SCM (revision control, version '
1703 1710 'control) system'
1704 1711 ),
1705 1712 long_description=(
1706 1713 'Mercurial is a distributed SCM tool written in Python.'
1707 1714 ' It is used by a number of large projects that require'
1708 1715 ' fast, reliable distributed revision control, such as '
1709 1716 'Mozilla.'
1710 1717 ),
1711 1718 license='GNU GPLv2 or any later version',
1712 1719 classifiers=[
1713 1720 'Development Status :: 6 - Mature',
1714 1721 'Environment :: Console',
1715 1722 'Intended Audience :: Developers',
1716 1723 'Intended Audience :: System Administrators',
1717 1724 'License :: OSI Approved :: GNU General Public License (GPL)',
1718 1725 'Natural Language :: Danish',
1719 1726 'Natural Language :: English',
1720 1727 'Natural Language :: German',
1721 1728 'Natural Language :: Italian',
1722 1729 'Natural Language :: Japanese',
1723 1730 'Natural Language :: Portuguese (Brazilian)',
1724 1731 'Operating System :: Microsoft :: Windows',
1725 1732 'Operating System :: OS Independent',
1726 1733 'Operating System :: POSIX',
1727 1734 'Programming Language :: C',
1728 1735 'Programming Language :: Python',
1729 1736 'Topic :: Software Development :: Version Control',
1730 1737 ],
1731 1738 scripts=scripts,
1732 1739 packages=packages,
1733 1740 ext_modules=extmodules,
1734 1741 data_files=datafiles,
1735 1742 package_data=packagedata,
1736 1743 cmdclass=cmdclass,
1737 1744 distclass=hgdist,
1738 1745 options={
1739 1746 'py2exe': {
1740 1747 'bundle_files': 3,
1741 1748 'dll_excludes': py2exedllexcludes,
1742 1749 'excludes': py2exeexcludes,
1743 1750 'packages': py2exepackages,
1744 1751 },
1745 1752 'bdist_mpkg': {
1746 1753 'zipdist': False,
1747 1754 'license': 'COPYING',
1748 1755 'readme': 'contrib/packaging/macosx/Readme.html',
1749 1756 'welcome': 'contrib/packaging/macosx/Welcome.html',
1750 1757 },
1751 1758 },
1752 1759 **extra
1753 1760 )
@@ -1,227 +1,227 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > convert=
4 4 > [convert]
5 5 > hg.saverev=False
6 6 > EOF
7 7 $ hg init orig
8 8 $ cd orig
9 9 $ echo foo > foo
10 10 $ echo bar > bar
11 11 $ hg ci -qAm 'add foo bar' -d '0 0'
12 12 $ echo >> foo
13 13 $ hg ci -m 'change foo' -d '1 0'
14 14 $ hg up -qC 0
15 15 $ hg copy --after --force foo bar
16 16 $ hg copy foo baz
17 17 $ hg ci -m 'make bar and baz copies of foo' -d '2 0'
18 18 created new head
19 19
20 20 Test that template can print all file copies (issue4362)
21 21 $ hg log -r . --template "{file_copies % ' File: {file_copy}\n'}"
22 22 File: bar (foo)
23 23 File: baz (foo)
24 24
25 25 $ hg bookmark premerge1
26 26 $ hg merge -r 1
27 27 merging baz and foo to baz
28 28 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
29 29 (branch merge, don't forget to commit)
30 30 $ hg ci -m 'merge local copy' -d '3 0'
31 31 $ hg up -C 1
32 32 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
33 33 (leaving bookmark premerge1)
34 34 $ hg bookmark premerge2
35 35 $ hg merge 2
36 36 merging foo and baz to baz
37 37 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
38 38 (branch merge, don't forget to commit)
39 39 $ hg ci -m 'merge remote copy' -d '4 0'
40 40 created new head
41 41
42 42 Make and delete some tags
43 43
44 44 $ hg tag that
45 45 $ hg tag --remove that
46 46 $ hg tag this
47 47
48 48 #if execbit
49 49 $ chmod +x baz
50 50 #else
51 51 $ echo some other change to make sure we get a rev 5 > baz
52 52 #endif
53 53 $ hg ci -m 'mark baz executable' -d '5 0'
54 54 $ cd ..
55 55 $ hg convert --datesort orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
56 56 initializing destination new repository
57 57 scanning source...
58 58 sorting...
59 59 converting...
60 60 8 add foo bar
61 61 7 change foo
62 62 6 make bar and baz copies of foo
63 63 5 merge local copy
64 64 4 merge remote copy
65 65 3 Added tag that for changeset 8601262d7472
66 66 2 Removed tag that
67 67 1 Added tag this for changeset 706614b458c1
68 68 0 mark baz executable
69 69 updating bookmarks
70 70 $ cd new
71 71 $ hg out ../orig
72 72 comparing with ../orig
73 73 searching for changes
74 74 no changes found
75 75 [1]
76 76 #if execbit
77 77 $ hg bookmarks
78 78 premerge1 3:973ef48a98a4
79 79 premerge2 8:c4968fdf2e5d
80 80 #else
81 81 Different hash because no x bit
82 82 $ hg bookmarks
83 83 premerge1 3:973ef48a98a4
84 premerge2 8:3537b15eaaca
84 premerge2 8:1cc21e701444
85 85 #endif
86 86
87 87 Test that redoing a convert results in an identical graph
88 88 $ cd ../
89 89 $ rm new/.hg/shamap
90 90 $ hg convert --datesort orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
91 91 scanning source...
92 92 sorting...
93 93 converting...
94 94 8 add foo bar
95 95 7 change foo
96 96 6 make bar and baz copies of foo
97 97 5 merge local copy
98 98 4 merge remote copy
99 99 3 Added tag that for changeset 8601262d7472
100 100 2 Removed tag that
101 101 1 Added tag this for changeset 706614b458c1
102 102 0 mark baz executable
103 103 updating bookmarks
104 104 $ hg -R new log -G -T '{rev} {desc}'
105 105 o 8 mark baz executable
106 106 |
107 107 o 7 Added tag this for changeset 706614b458c1
108 108 |
109 109 o 6 Removed tag that
110 110 |
111 111 o 5 Added tag that for changeset 8601262d7472
112 112 |
113 113 o 4 merge remote copy
114 114 |\
115 115 +---o 3 merge local copy
116 116 | |/
117 117 | o 2 make bar and baz copies of foo
118 118 | |
119 119 o | 1 change foo
120 120 |/
121 121 o 0 add foo bar
122 122
123 123
124 124 check shamap LF and CRLF handling
125 125
126 126 $ cat > rewrite.py <<EOF
127 127 > import sys
128 128 > # Interlace LF and CRLF
129 129 > lines = [(l.rstrip() + ((i % 2) and b'\n' or b'\r\n'))
130 130 > for i, l in enumerate(open(sys.argv[1], 'rb'))]
131 131 > open(sys.argv[1], 'wb').write(b''.join(lines))
132 132 > EOF
133 133 $ "$PYTHON" rewrite.py new/.hg/shamap
134 134 $ cd orig
135 135 $ hg up -qC 1
136 136 $ echo foo >> foo
137 137 $ hg ci -qm 'change foo again'
138 138 $ hg up -qC 2
139 139 $ echo foo >> foo
140 140 $ hg ci -qm 'change foo again again'
141 141 $ cd ..
142 142 $ hg convert --datesort orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
143 143 scanning source...
144 144 sorting...
145 145 converting...
146 146 1 change foo again again
147 147 0 change foo again
148 148 updating bookmarks
149 149
150 150 init broken repository
151 151
152 152 $ hg init broken
153 153 $ cd broken
154 154 $ echo a >> a
155 155 $ echo b >> b
156 156 $ hg ci -qAm init
157 157 $ echo a >> a
158 158 $ echo b >> b
159 159 $ hg copy b c
160 160 $ hg ci -qAm changeall
161 161 $ hg up -qC 0
162 162 $ echo bc >> b
163 163 $ hg ci -m changebagain
164 164 created new head
165 165 $ HGMERGE=internal:local hg -q merge
166 166 $ hg ci -m merge
167 167 $ hg mv b d
168 168 $ hg ci -m moveb
169 169
170 170 break it
171 171
172 172 #if reporevlogstore
173 173 $ rm .hg/store/data/b.*
174 174 #endif
175 175 #if reposimplestore
176 176 $ rm .hg/store/data/b/*
177 177 #endif
178 178 $ cd ..
179 179 $ hg --config convert.hg.ignoreerrors=True convert broken fixed
180 180 initializing destination fixed repository
181 181 scanning source...
182 182 sorting...
183 183 converting...
184 184 4 init
185 185 ignoring: data/b.i@1e88685f5dde: no match found (reporevlogstore !)
186 186 ignoring: data/b/index@1e88685f5dde: no node (reposimplestore !)
187 187 3 changeall
188 188 2 changebagain
189 189 1 merge
190 190 0 moveb
191 191 $ hg -R fixed verify
192 192 checking changesets
193 193 checking manifests
194 194 crosschecking files in changesets and manifests
195 195 checking files
196 196 checked 5 changesets with 5 changes to 3 files
197 197
198 198 manifest -r 0
199 199
200 200 $ hg -R fixed manifest -r 0
201 201 a
202 202
203 203 manifest -r tip
204 204
205 205 $ hg -R fixed manifest -r tip
206 206 a
207 207 c
208 208 d
209 209 $ cd ..
210 210
211 211 $ hg init commit-references
212 212 $ cd commit-references
213 213 $ echo a > a
214 214 $ hg ci -Aqm initial
215 215 $ echo b > b
216 216 $ hg ci -Aqm 'the previous commit was 1451231c8757'
217 217 $ echo c > c
218 218 $ hg ci -Aqm 'the working copy is called ffffffffffff'
219 219
220 220 $ cd ..
221 221 $ hg convert commit-references new-commit-references -q \
222 222 > --config convert.hg.sourcename=yes
223 223 $ cd new-commit-references
224 224 $ hg log -T '{node|short} {desc}\n'
225 225 fe295c9e6bc6 the working copy is called ffffffffffff
226 226 642508659503 the previous commit was c2491f685436
227 227 c2491f685436 initial
@@ -1,443 +1,443 b''
1 1 #require no-reposimplestore
2 2
3 3 Testing the case when there is no infinitepush extension present on the client
4 4 side and the server routes each push to bundlestore. This case is very much
5 5 similar to CI use case.
6 6
7 7 Setup
8 8 -----
9 9
10 10 $ . "$TESTDIR/library-infinitepush.sh"
11 11 $ cat >> $HGRCPATH <<EOF
12 12 > [ui]
13 13 > ssh = python "$TESTDIR/dummyssh"
14 14 > [alias]
15 15 > glog = log -GT "{rev}:{node|short} {desc}\n{phase}"
16 16 > EOF
17 17 $ cp $HGRCPATH $TESTTMP/defaulthgrc
18 18 $ hg init repo
19 19 $ cd repo
20 20 $ setupserver
21 21 $ echo "pushtobundlestore = True" >> .hg/hgrc
22 22 $ echo "[extensions]" >> .hg/hgrc
23 23 $ echo "infinitepush=" >> .hg/hgrc
24 24 $ echo initialcommit > initialcommit
25 25 $ hg ci -Aqm "initialcommit"
26 26 $ hg phase --public .
27 27
28 28 $ cd ..
29 29 $ hg clone repo client -q
30 30 $ hg clone repo client2 -q
31 31 $ hg clone ssh://user@dummy/repo client3 -q
32 32 $ cd client
33 33
34 34 Pushing a new commit from the client to the server
35 35 -----------------------------------------------------
36 36
37 37 $ echo foobar > a
38 38 $ hg ci -Aqm "added a"
39 39 $ hg glog
40 40 @ 1:6cb0989601f1 added a
41 41 | draft
42 42 o 0:67145f466344 initialcommit
43 43 public
44 44
45 45 $ hg push
46 46 pushing to $TESTTMP/repo
47 47 searching for changes
48 48 storing changesets on the bundlestore
49 49 pushing 1 commit:
50 50 6cb0989601f1 added a
51 51
52 52 $ scratchnodes
53 53 6cb0989601f1fb5805238edfb16f3606713d9a0b 3b414252ff8acab801318445d88ff48faf4a28c3
54 54
55 55 Understanding how data is stored on the bundlestore in server
56 56 -------------------------------------------------------------
57 57
58 58 There are two things, filebundlestore and index
59 59 $ ls ../repo/.hg/scratchbranches
60 60 filebundlestore
61 61 index
62 62
63 63 filebundlestore stores the bundles
64 64 $ ls ../repo/.hg/scratchbranches/filebundlestore/3b/41/
65 65 3b414252ff8acab801318445d88ff48faf4a28c3
66 66
67 67 index/nodemap stores a map of node id and file in which bundle is stored in filebundlestore
68 68 $ ls ../repo/.hg/scratchbranches/index/
69 69 nodemap
70 70 $ ls ../repo/.hg/scratchbranches/index/nodemap/
71 71 6cb0989601f1fb5805238edfb16f3606713d9a0b
72 72
73 73 $ cd ../repo
74 74
75 75 Checking that the commit was not applied to revlog on the server
76 76 ------------------------------------------------------------------
77 77
78 78 $ hg glog
79 79 @ 0:67145f466344 initialcommit
80 80 public
81 81
82 82 Applying the changeset from the bundlestore
83 83 --------------------------------------------
84 84
85 85 $ hg unbundle .hg/scratchbranches/filebundlestore/3b/41/3b414252ff8acab801318445d88ff48faf4a28c3
86 86 adding changesets
87 87 adding manifests
88 88 adding file changes
89 89 added 1 changesets with 1 changes to 1 files
90 90 new changesets 6cb0989601f1
91 91 (run 'hg update' to get a working copy)
92 92
93 93 $ hg glog
94 94 o 1:6cb0989601f1 added a
95 95 | public
96 96 @ 0:67145f466344 initialcommit
97 97 public
98 98
99 99 Pushing more changesets from the local repo
100 100 --------------------------------------------
101 101
102 102 $ cd ../client
103 103 $ echo b > b
104 104 $ hg ci -Aqm "added b"
105 105 $ echo c > c
106 106 $ hg ci -Aqm "added c"
107 107 $ hg glog
108 108 @ 3:bf8a6e3011b3 added c
109 109 | draft
110 110 o 2:eaba929e866c added b
111 111 | draft
112 112 o 1:6cb0989601f1 added a
113 113 | public
114 114 o 0:67145f466344 initialcommit
115 115 public
116 116
117 117 $ hg push
118 118 pushing to $TESTTMP/repo
119 119 searching for changes
120 120 storing changesets on the bundlestore
121 121 pushing 2 commits:
122 122 eaba929e866c added b
123 123 bf8a6e3011b3 added c
124 124
125 125 Checking that changesets are not applied on the server
126 126 ------------------------------------------------------
127 127
128 128 $ hg glog -R ../repo
129 129 o 1:6cb0989601f1 added a
130 130 | public
131 131 @ 0:67145f466344 initialcommit
132 132 public
133 133
134 134 Both of the new changesets are stored in a single bundle-file
135 135 $ scratchnodes
136 136 6cb0989601f1fb5805238edfb16f3606713d9a0b 3b414252ff8acab801318445d88ff48faf4a28c3
137 137 bf8a6e3011b345146bbbedbcb1ebd4837571492a 239585f5e61f0c09ce7106bdc1097bff731738f4
138 138 eaba929e866c59bc9a6aada5a9dd2f6990db83c0 239585f5e61f0c09ce7106bdc1097bff731738f4
139 139
140 140 Pushing more changesets to the server
141 141 -------------------------------------
142 142
143 143 $ echo d > d
144 144 $ hg ci -Aqm "added d"
145 145 $ echo e > e
146 146 $ hg ci -Aqm "added e"
147 147
148 148 XXX: we should have pushed only the parts which are not in bundlestore
149 149 $ hg push
150 150 pushing to $TESTTMP/repo
151 151 searching for changes
152 152 storing changesets on the bundlestore
153 153 pushing 4 commits:
154 154 eaba929e866c added b
155 155 bf8a6e3011b3 added c
156 156 1bb96358eda2 added d
157 157 b4e4bce66051 added e
158 158
159 159 Sneak peek into the bundlestore at the server
160 160 $ scratchnodes
161 161 1bb96358eda285b536c6d1c66846a7cdb2336cea 98fbae0016662521b0007da1b7bc349cd3caacd1
162 162 6cb0989601f1fb5805238edfb16f3606713d9a0b 3b414252ff8acab801318445d88ff48faf4a28c3
163 163 b4e4bce660512ad3e71189e14588a70ac8e31fef 98fbae0016662521b0007da1b7bc349cd3caacd1
164 164 bf8a6e3011b345146bbbedbcb1ebd4837571492a 98fbae0016662521b0007da1b7bc349cd3caacd1
165 165 eaba929e866c59bc9a6aada5a9dd2f6990db83c0 98fbae0016662521b0007da1b7bc349cd3caacd1
166 166
167 167 Checking if `hg pull` pulls something or `hg incoming` shows something
168 168 -----------------------------------------------------------------------
169 169
170 170 $ hg incoming
171 171 comparing with $TESTTMP/repo
172 172 searching for changes
173 173 no changes found
174 174 [1]
175 175
176 176 $ hg pull
177 177 pulling from $TESTTMP/repo
178 178 searching for changes
179 179 no changes found
180 180
181 181 Pulling from second client which is a localpeer to test `hg pull -r <rev>`
182 182 --------------------------------------------------------------------------
183 183
184 184 Pulling the revision which is applied
185 185
186 186 $ cd ../client2
187 187 $ hg pull -r 6cb0989601f1
188 188 pulling from $TESTTMP/repo
189 189 searching for changes
190 190 adding changesets
191 191 adding manifests
192 192 adding file changes
193 193 added 1 changesets with 1 changes to 1 files
194 194 new changesets 6cb0989601f1
195 195 (run 'hg update' to get a working copy)
196 196 $ hg glog
197 197 o 1:6cb0989601f1 added a
198 198 | public
199 199 @ 0:67145f466344 initialcommit
200 200 public
201 201
202 202 Pulling the revision which is in bundlestore
203 203 XXX: we should support pulling revisions from a local peers bundlestore without
204 204 client side wrapping
205 205
206 206 $ hg pull -r b4e4bce660512ad3e71189e14588a70ac8e31fef
207 207 pulling from $TESTTMP/repo
208 208 abort: unknown revision 'b4e4bce660512ad3e71189e14588a70ac8e31fef'!
209 209 [255]
210 210 $ hg glog
211 211 o 1:6cb0989601f1 added a
212 212 | public
213 213 @ 0:67145f466344 initialcommit
214 214 public
215 215
216 216 $ cd ../client
217 217
218 218 Pulling from third client which is not a localpeer
219 219 ---------------------------------------------------
220 220
221 221 Pulling the revision which is applied
222 222
223 223 $ cd ../client3
224 224 $ hg pull -r 6cb0989601f1
225 225 pulling from ssh://user@dummy/repo
226 226 searching for changes
227 227 adding changesets
228 228 adding manifests
229 229 adding file changes
230 230 added 1 changesets with 1 changes to 1 files
231 231 new changesets 6cb0989601f1
232 232 (run 'hg update' to get a working copy)
233 233 $ hg glog
234 234 o 1:6cb0989601f1 added a
235 235 | public
236 236 @ 0:67145f466344 initialcommit
237 237 public
238 238
239 239 Pulling the revision which is in bundlestore
240 240
241 241 Trying to specify short hash
242 242 XXX: we should support this
243 243 $ hg pull -r b4e4bce660512
244 244 pulling from ssh://user@dummy/repo
245 245 abort: unknown revision 'b4e4bce660512'!
246 246 [255]
247 247
248 248 XXX: we should show better message when the pull is happening from bundlestore
249 249 $ hg pull -r b4e4bce660512ad3e71189e14588a70ac8e31fef
250 250 pulling from ssh://user@dummy/repo
251 251 searching for changes
252 no changes found
253 252 adding changesets
254 253 adding manifests
255 254 adding file changes
256 255 added 4 changesets with 4 changes to 4 files
257 256 new changesets eaba929e866c:b4e4bce66051
257 (run 'hg update' to get a working copy)
258 258 $ hg glog
259 259 o 5:b4e4bce66051 added e
260 260 | public
261 261 o 4:1bb96358eda2 added d
262 262 | public
263 263 o 3:bf8a6e3011b3 added c
264 264 | public
265 265 o 2:eaba929e866c added b
266 266 | public
267 267 o 1:6cb0989601f1 added a
268 268 | public
269 269 @ 0:67145f466344 initialcommit
270 270 public
271 271
272 272 $ cd ../client
273 273
274 274 Checking storage of phase information with the bundle on bundlestore
275 275 ---------------------------------------------------------------------
276 276
277 277 creating a draft commit
278 278 $ cat >> $HGRCPATH <<EOF
279 279 > [phases]
280 280 > publish = False
281 281 > EOF
282 282 $ echo f > f
283 283 $ hg ci -Aqm "added f"
284 284 $ hg glog -r '.^::'
285 285 @ 6:9b42578d4447 added f
286 286 | draft
287 287 o 5:b4e4bce66051 added e
288 288 | public
289 289 ~
290 290
291 291 $ hg push
292 292 pushing to $TESTTMP/repo
293 293 searching for changes
294 294 storing changesets on the bundlestore
295 295 pushing 5 commits:
296 296 eaba929e866c added b
297 297 bf8a6e3011b3 added c
298 298 1bb96358eda2 added d
299 299 b4e4bce66051 added e
300 300 9b42578d4447 added f
301 301
302 302 XXX: the phase of 9b42578d4447 should not be changed here
303 303 $ hg glog -r .
304 304 @ 6:9b42578d4447 added f
305 305 | public
306 306 ~
307 307
308 308 applying the bundle on the server to check preservation of phase-information
309 309
310 310 $ cd ../repo
311 311 $ scratchnodes
312 312 1bb96358eda285b536c6d1c66846a7cdb2336cea 280a46a259a268f0e740c81c5a7751bdbfaec85f
313 313 6cb0989601f1fb5805238edfb16f3606713d9a0b 3b414252ff8acab801318445d88ff48faf4a28c3
314 314 9b42578d44473575994109161430d65dd147d16d 280a46a259a268f0e740c81c5a7751bdbfaec85f
315 315 b4e4bce660512ad3e71189e14588a70ac8e31fef 280a46a259a268f0e740c81c5a7751bdbfaec85f
316 316 bf8a6e3011b345146bbbedbcb1ebd4837571492a 280a46a259a268f0e740c81c5a7751bdbfaec85f
317 317 eaba929e866c59bc9a6aada5a9dd2f6990db83c0 280a46a259a268f0e740c81c5a7751bdbfaec85f
318 318
319 319 $ hg unbundle .hg/scratchbranches/filebundlestore/28/0a/280a46a259a268f0e740c81c5a7751bdbfaec85f
320 320 adding changesets
321 321 adding manifests
322 322 adding file changes
323 323 added 5 changesets with 5 changes to 5 files
324 324 new changesets eaba929e866c:9b42578d4447 (1 drafts)
325 325 (run 'hg update' to get a working copy)
326 326
327 327 $ hg glog
328 328 o 6:9b42578d4447 added f
329 329 | draft
330 330 o 5:b4e4bce66051 added e
331 331 | public
332 332 o 4:1bb96358eda2 added d
333 333 | public
334 334 o 3:bf8a6e3011b3 added c
335 335 | public
336 336 o 2:eaba929e866c added b
337 337 | public
338 338 o 1:6cb0989601f1 added a
339 339 | public
340 340 @ 0:67145f466344 initialcommit
341 341 public
342 342
343 343 Checking storage of obsmarkers in the bundlestore
344 344 --------------------------------------------------
345 345
346 346 enabling obsmarkers and rebase extension
347 347
348 348 $ cat >> $HGRCPATH << EOF
349 349 > [experimental]
350 350 > evolution = all
351 351 > [extensions]
352 352 > rebase =
353 353 > EOF
354 354
355 355 $ cd ../client
356 356
357 357 $ hg phase -r . --draft --force
358 358 $ hg rebase -r 6 -d 3
359 359 rebasing 6:9b42578d4447 "added f" (tip)
360 360
361 361 $ hg glog
362 362 @ 7:99949238d9ac added f
363 363 | draft
364 364 | o 5:b4e4bce66051 added e
365 365 | | public
366 366 | o 4:1bb96358eda2 added d
367 367 |/ public
368 368 o 3:bf8a6e3011b3 added c
369 369 | public
370 370 o 2:eaba929e866c added b
371 371 | public
372 372 o 1:6cb0989601f1 added a
373 373 | public
374 374 o 0:67145f466344 initialcommit
375 375 public
376 376
377 377 $ hg push -f
378 378 pushing to $TESTTMP/repo
379 379 searching for changes
380 380 storing changesets on the bundlestore
381 381 pushing 1 commit:
382 382 99949238d9ac added f
383 383
384 384 XXX: the phase should not have changed here
385 385 $ hg glog -r .
386 386 @ 7:99949238d9ac added f
387 387 | public
388 388 ~
389 389
390 390 Unbundling on server to see obsmarkers being applied
391 391
392 392 $ cd ../repo
393 393
394 394 $ scratchnodes
395 395 1bb96358eda285b536c6d1c66846a7cdb2336cea 280a46a259a268f0e740c81c5a7751bdbfaec85f
396 396 6cb0989601f1fb5805238edfb16f3606713d9a0b 3b414252ff8acab801318445d88ff48faf4a28c3
397 397 99949238d9ac7f2424a33a46dface6f866afd059 090a24fe63f31d3b4bee714447f835c8c362ff57
398 398 9b42578d44473575994109161430d65dd147d16d 280a46a259a268f0e740c81c5a7751bdbfaec85f
399 399 b4e4bce660512ad3e71189e14588a70ac8e31fef 280a46a259a268f0e740c81c5a7751bdbfaec85f
400 400 bf8a6e3011b345146bbbedbcb1ebd4837571492a 280a46a259a268f0e740c81c5a7751bdbfaec85f
401 401 eaba929e866c59bc9a6aada5a9dd2f6990db83c0 280a46a259a268f0e740c81c5a7751bdbfaec85f
402 402
403 403 $ hg glog
404 404 o 6:9b42578d4447 added f
405 405 | draft
406 406 o 5:b4e4bce66051 added e
407 407 | public
408 408 o 4:1bb96358eda2 added d
409 409 | public
410 410 o 3:bf8a6e3011b3 added c
411 411 | public
412 412 o 2:eaba929e866c added b
413 413 | public
414 414 o 1:6cb0989601f1 added a
415 415 | public
416 416 @ 0:67145f466344 initialcommit
417 417 public
418 418
419 419 $ hg unbundle .hg/scratchbranches/filebundlestore/09/0a/090a24fe63f31d3b4bee714447f835c8c362ff57
420 420 adding changesets
421 421 adding manifests
422 422 adding file changes
423 423 added 1 changesets with 0 changes to 1 files (+1 heads)
424 424 1 new obsolescence markers
425 425 obsoleted 1 changesets
426 426 new changesets 99949238d9ac (1 drafts)
427 427 (run 'hg heads' to see heads, 'hg merge' to merge)
428 428
429 429 $ hg glog
430 430 o 7:99949238d9ac added f
431 431 | draft
432 432 | o 5:b4e4bce66051 added e
433 433 | | public
434 434 | o 4:1bb96358eda2 added d
435 435 |/ public
436 436 o 3:bf8a6e3011b3 added c
437 437 | public
438 438 o 2:eaba929e866c added b
439 439 | public
440 440 o 1:6cb0989601f1 added a
441 441 | public
442 442 @ 0:67145f466344 initialcommit
443 443 public
@@ -1,721 +1,715 b''
1 1 #testcases lfsremote-on lfsremote-off
2 2 #require serve no-reposimplestore no-chg
3 3
4 4 This test splits `hg serve` with and without using the extension into separate
5 5 tests cases. The tests are broken down as follows, where "LFS"/"No-LFS"
6 6 indicates whether or not there are commits that use an LFS file, and "D"/"E"
7 7 indicates whether or not the extension is loaded. The "X" cases are not tested
8 8 individually, because the lfs requirement causes the process to bail early if
9 9 the extension is disabled.
10 10
11 11 . Server
12 12 .
13 13 . No-LFS LFS
14 14 . +----------------------------+
15 15 . | || D | E | D | E |
16 16 . |---++=======================|
17 17 . C | D || N/A | #1 | X | #4 |
18 18 . l No +---++-----------------------|
19 19 . i LFS | E || #2 | #2 | X | #5 |
20 20 . e +---++-----------------------|
21 21 . n | D || X | X | X | X |
22 22 . t LFS |---++-----------------------|
23 23 . | E || #3 | #3 | X | #6 |
24 24 . |---++-----------------------+
25 25
26 26 make command server magic visible
27 27
28 28 #if windows
29 29 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
30 30 #else
31 31 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
32 32 #endif
33 33 $ export PYTHONPATH
34 34
35 35 $ hg init server
36 36 $ SERVER_REQUIRES="$TESTTMP/server/.hg/requires"
37 37
38 38 $ cat > $TESTTMP/debugprocessors.py <<EOF
39 39 > from mercurial import (
40 40 > cmdutil,
41 41 > commands,
42 42 > pycompat,
43 43 > registrar,
44 44 > )
45 45 > cmdtable = {}
46 46 > command = registrar.command(cmdtable)
47 47 > @command(b'debugprocessors', [], b'FILE')
48 48 > def debugprocessors(ui, repo, file_=None, **opts):
49 49 > opts = pycompat.byteskwargs(opts)
50 50 > opts[b'changelog'] = False
51 51 > opts[b'manifest'] = False
52 52 > opts[b'dir'] = False
53 53 > rl = cmdutil.openrevlog(repo, b'debugprocessors', file_, opts)
54 54 > for flag, proc in rl._flagprocessors.items():
55 55 > ui.status(b"registered processor '%#x'\n" % (flag))
56 56 > EOF
57 57
58 58 Skip the experimental.changegroup3=True config. Failure to agree on this comes
59 59 first, and causes an "abort: no common changegroup version" if the extension is
60 60 only loaded on one side. If that *is* enabled, the subsequent failure is "abort:
61 61 missing processor for flag '0x2000'!" if the extension is only loaded on one side
62 62 (possibly also masked by the Internal Server Error message).
63 63 $ cat >> $HGRCPATH <<EOF
64 64 > [extensions]
65 65 > debugprocessors = $TESTTMP/debugprocessors.py
66 66 > [experimental]
67 67 > lfs.disableusercache = True
68 68 > lfs.worker-enable = False
69 69 > [lfs]
70 70 > threshold=10
71 71 > [web]
72 72 > allow_push=*
73 73 > push_ssl=False
74 74 > EOF
75 75
76 76 $ cp $HGRCPATH $HGRCPATH.orig
77 77
78 78 #if lfsremote-on
79 79 $ hg --config extensions.lfs= -R server \
80 80 > serve -p $HGPORT -d --pid-file=hg.pid --errorlog=$TESTTMP/errors.log
81 81 #else
82 82 $ hg --config extensions.lfs=! -R server \
83 83 > serve -p $HGPORT -d --pid-file=hg.pid --errorlog=$TESTTMP/errors.log
84 84 #endif
85 85
86 86 $ cat hg.pid >> $DAEMON_PIDS
87 87 $ hg clone -q http://localhost:$HGPORT client
88 88 $ grep 'lfs' client/.hg/requires $SERVER_REQUIRES
89 89 [1]
90 90
91 91 This trivial repo will force commandserver to load the extension, but not call
92 92 reposetup() on another repo actually being operated on. This gives coverage
93 93 that wrapper functions are not assuming reposetup() was called.
94 94
95 95 $ hg init $TESTTMP/cmdservelfs
96 96 $ cat >> $TESTTMP/cmdservelfs/.hg/hgrc << EOF
97 97 > [extensions]
98 98 > lfs =
99 99 > EOF
100 100
101 101 --------------------------------------------------------------------------------
102 102 Case #1: client with non-lfs content and the extension disabled; server with
103 103 non-lfs content, and the extension enabled.
104 104
105 105 $ cd client
106 106 $ echo 'non-lfs' > nonlfs.txt
107 107 >>> from __future__ import absolute_import
108 108 >>> from hgclient import check, readchannel, runcommand
109 109 >>> @check
110 110 ... def diff(server):
111 111 ... readchannel(server)
112 112 ... # run an arbitrary command in the repo with the extension loaded
113 113 ... runcommand(server, [b'id', b'-R', b'../cmdservelfs'])
114 114 ... # now run a command in a repo without the extension to ensure that
115 115 ... # files are added safely..
116 116 ... runcommand(server, [b'ci', b'-Aqm', b'non-lfs'])
117 117 ... # .. and that scmutil.prefetchfiles() safely no-ops..
118 118 ... runcommand(server, [b'diff', b'-r', b'.~1'])
119 119 ... # .. and that debugupgraderepo safely no-ops.
120 120 ... runcommand(server, [b'debugupgraderepo', b'-q', b'--run'])
121 121 *** runcommand id -R ../cmdservelfs
122 122 000000000000 tip
123 123 *** runcommand ci -Aqm non-lfs
124 124 *** runcommand diff -r .~1
125 125 diff -r 000000000000 nonlfs.txt
126 126 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
127 127 +++ b/nonlfs.txt Thu Jan 01 00:00:00 1970 +0000
128 128 @@ -0,0 +1,1 @@
129 129 +non-lfs
130 130 *** runcommand debugupgraderepo -q --run
131 131 upgrade will perform the following actions:
132 132
133 133 requirements
134 134 preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store
135 135
136 sidedata
137 Allows storage of extra data alongside a revision.
138
139 copies-sdc
140 Allows to use more efficient algorithm to deal with copy tracing.
141
142 136 beginning upgrade...
143 137 repository locked and read-only
144 138 creating temporary repository to stage migrated data: * (glob)
145 139 (it is safe to interrupt this process any time before data migration completes)
146 140 migrating 3 total revisions (1 in filelogs, 1 in manifests, 1 in changelog)
147 141 migrating 324 bytes in store; 129 bytes tracked data
148 142 migrating 1 filelogs containing 1 revisions (73 bytes in store; 8 bytes tracked data)
149 143 finished migrating 1 filelog revisions across 1 filelogs; change in size: 0 bytes
150 144 migrating 1 manifests containing 1 revisions (117 bytes in store; 52 bytes tracked data)
151 145 finished migrating 1 manifest revisions across 1 manifests; change in size: 0 bytes
152 146 migrating changelog containing 1 revisions (134 bytes in store; 69 bytes tracked data)
153 147 finished migrating 1 changelog revisions; change in size: 0 bytes
154 148 finished migrating 3 total revisions; total change in store size: 0 bytes
155 149 copying phaseroots
156 150 data fully migrated to temporary repository
157 151 marking source repository as being upgraded; clients will be unable to read from repository
158 152 starting in-place swap of repository data
159 153 replaced files will be backed up at * (glob)
160 154 replacing store...
161 155 store replacement complete; repository was inconsistent for *s (glob)
162 156 finalizing requirements file and making repository readable again
163 157 removing temporary repository * (glob)
164 158 copy of old repository backed up at * (glob)
165 159 the old repository will not be deleted; remove it to free up disk space once the upgraded repository is verified
166 160
167 161 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
168 162 [1]
169 163
170 164 #if lfsremote-on
171 165
172 166 $ hg push -q
173 167 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
174 168 [1]
175 169
176 170 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client1_clone
177 171 $ grep 'lfs' $TESTTMP/client1_clone/.hg/requires $SERVER_REQUIRES
178 172 [1]
179 173
180 174 $ hg init $TESTTMP/client1_pull
181 175 $ hg -R $TESTTMP/client1_pull pull -q http://localhost:$HGPORT
182 176 $ grep 'lfs' $TESTTMP/client1_pull/.hg/requires $SERVER_REQUIRES
183 177 [1]
184 178
185 179 $ hg identify http://localhost:$HGPORT
186 180 d437e1d24fbd
187 181
188 182 #endif
189 183
190 184 --------------------------------------------------------------------------------
191 185 Case #2: client with non-lfs content and the extension enabled; server with
192 186 non-lfs content, and the extension state controlled by #testcases.
193 187
194 188 $ cat >> $HGRCPATH <<EOF
195 189 > [extensions]
196 190 > lfs =
197 191 > EOF
198 192 $ echo 'non-lfs' > nonlfs2.txt
199 193 $ hg ci -Aqm 'non-lfs file with lfs client'
200 194
201 195 Since no lfs content has been added yet, the push is allowed, even when the
202 196 extension is not enabled remotely.
203 197
204 198 $ hg push -q
205 199 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
206 200 [1]
207 201
208 202 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client2_clone
209 203 $ grep 'lfs' $TESTTMP/client2_clone/.hg/requires $SERVER_REQUIRES
210 204 [1]
211 205
212 206 $ hg init $TESTTMP/client2_pull
213 207 $ hg -R $TESTTMP/client2_pull pull -q http://localhost:$HGPORT
214 208 $ grep 'lfs' $TESTTMP/client2_pull/.hg/requires $SERVER_REQUIRES
215 209 [1]
216 210
217 211 $ hg identify http://localhost:$HGPORT
218 212 1477875038c6
219 213
220 214 --------------------------------------------------------------------------------
221 215 Case #3: client with lfs content and the extension enabled; server with
222 216 non-lfs content, and the extension state controlled by #testcases. The server
223 217 should have an 'lfs' requirement after it picks up its first commit with a blob.
224 218
225 219 $ echo 'this is a big lfs file' > lfs.bin
226 220 $ hg ci -Aqm 'lfs'
227 221 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
228 222 .hg/requires:lfs
229 223
230 224 #if lfsremote-off
231 225 $ hg push -q
232 226 abort: required features are not supported in the destination: lfs
233 227 (enable the lfs extension on the server)
234 228 [255]
235 229 #else
236 230 $ hg push -q
237 231 #endif
238 232 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
239 233 .hg/requires:lfs
240 234 $TESTTMP/server/.hg/requires:lfs (lfsremote-on !)
241 235
242 236 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client3_clone
243 237 $ grep 'lfs' $TESTTMP/client3_clone/.hg/requires $SERVER_REQUIRES || true
244 238 $TESTTMP/client3_clone/.hg/requires:lfs (lfsremote-on !)
245 239 $TESTTMP/server/.hg/requires:lfs (lfsremote-on !)
246 240
247 241 $ hg init $TESTTMP/client3_pull
248 242 $ hg -R $TESTTMP/client3_pull pull -q http://localhost:$HGPORT
249 243 $ grep 'lfs' $TESTTMP/client3_pull/.hg/requires $SERVER_REQUIRES || true
250 244 $TESTTMP/client3_pull/.hg/requires:lfs (lfsremote-on !)
251 245 $TESTTMP/server/.hg/requires:lfs (lfsremote-on !)
252 246
253 247 Test that the commit/changegroup requirement check hook can be run multiple
254 248 times.
255 249
256 250 $ hg clone -qr 0 http://localhost:$HGPORT $TESTTMP/cmdserve_client3
257 251
258 252 $ cd ../cmdserve_client3
259 253
260 254 >>> from __future__ import absolute_import
261 255 >>> from hgclient import check, readchannel, runcommand
262 256 >>> @check
263 257 ... def addrequirement(server):
264 258 ... readchannel(server)
265 259 ... # change the repo in a way that adds the lfs requirement
266 260 ... runcommand(server, [b'pull', b'-qu'])
267 261 ... # Now cause the requirement adding hook to fire again, without going
268 262 ... # through reposetup() again.
269 263 ... with open('file.txt', 'wb') as fp:
270 264 ... fp.write(b'data')
271 265 ... runcommand(server, [b'ci', b'-Aqm', b'non-lfs'])
272 266 *** runcommand pull -qu
273 267 *** runcommand ci -Aqm non-lfs
274 268
275 269 $ cd ../client
276 270
277 271 The difference here is the push failed above when the extension isn't
278 272 enabled on the server.
279 273 $ hg identify http://localhost:$HGPORT
280 274 8374dc4052cb (lfsremote-on !)
281 275 1477875038c6 (lfsremote-off !)
282 276
283 277 Don't bother testing the lfsremote-off cases- the server won't be able
284 278 to launch if there's lfs content and the extension is disabled.
285 279
286 280 #if lfsremote-on
287 281
288 282 --------------------------------------------------------------------------------
289 283 Case #4: client with non-lfs content and the extension disabled; server with
290 284 lfs content, and the extension enabled.
291 285
292 286 $ cat >> $HGRCPATH <<EOF
293 287 > [extensions]
294 288 > lfs = !
295 289 > EOF
296 290
297 291 $ hg init $TESTTMP/client4
298 292 $ cd $TESTTMP/client4
299 293 $ cat >> .hg/hgrc <<EOF
300 294 > [paths]
301 295 > default = http://localhost:$HGPORT
302 296 > EOF
303 297 $ echo 'non-lfs' > nonlfs2.txt
304 298 $ hg ci -Aqm 'non-lfs'
305 299 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
306 300 $TESTTMP/server/.hg/requires:lfs
307 301
308 302 $ hg push -q --force
309 303 warning: repository is unrelated
310 304 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
311 305 $TESTTMP/server/.hg/requires:lfs
312 306
313 307 $ hg clone http://localhost:$HGPORT $TESTTMP/client4_clone
314 308 (remote is using large file support (lfs), but it is explicitly disabled in the local configuration)
315 309 abort: repository requires features unknown to this Mercurial: lfs!
316 310 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
317 311 [255]
318 312 $ grep 'lfs' $TESTTMP/client4_clone/.hg/requires $SERVER_REQUIRES
319 313 grep: $TESTTMP/client4_clone/.hg/requires: $ENOENT$
320 314 $TESTTMP/server/.hg/requires:lfs
321 315 [2]
322 316
323 317 TODO: fail more gracefully.
324 318
325 319 $ hg init $TESTTMP/client4_pull
326 320 $ hg -R $TESTTMP/client4_pull pull http://localhost:$HGPORT
327 321 pulling from http://localhost:$HGPORT/
328 322 requesting all changes
329 323 remote: abort: no common changegroup version
330 324 abort: pull failed on remote
331 325 [255]
332 326 $ grep 'lfs' $TESTTMP/client4_pull/.hg/requires $SERVER_REQUIRES
333 327 $TESTTMP/server/.hg/requires:lfs
334 328
335 329 $ hg identify http://localhost:$HGPORT
336 330 03b080fa9d93
337 331
338 332 --------------------------------------------------------------------------------
339 333 Case #5: client with non-lfs content and the extension enabled; server with
340 334 lfs content, and the extension enabled.
341 335
342 336 $ cat >> $HGRCPATH <<EOF
343 337 > [extensions]
344 338 > lfs =
345 339 > EOF
346 340 $ echo 'non-lfs' > nonlfs3.txt
347 341 $ hg ci -Aqm 'non-lfs file with lfs client'
348 342
349 343 $ hg push -q
350 344 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
351 345 $TESTTMP/server/.hg/requires:lfs
352 346
353 347 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client5_clone
354 348 $ grep 'lfs' $TESTTMP/client5_clone/.hg/requires $SERVER_REQUIRES
355 349 $TESTTMP/client5_clone/.hg/requires:lfs
356 350 $TESTTMP/server/.hg/requires:lfs
357 351
358 352 $ hg init $TESTTMP/client5_pull
359 353 $ hg -R $TESTTMP/client5_pull pull -q http://localhost:$HGPORT
360 354 $ grep 'lfs' $TESTTMP/client5_pull/.hg/requires $SERVER_REQUIRES
361 355 $TESTTMP/client5_pull/.hg/requires:lfs
362 356 $TESTTMP/server/.hg/requires:lfs
363 357
364 358 $ hg identify http://localhost:$HGPORT
365 359 c729025cc5e3
366 360
367 361 $ mv $HGRCPATH $HGRCPATH.tmp
368 362 $ cp $HGRCPATH.orig $HGRCPATH
369 363
370 364 >>> from __future__ import absolute_import
371 365 >>> from hgclient import bprint, check, readchannel, runcommand, stdout
372 366 >>> @check
373 367 ... def checkflags(server):
374 368 ... readchannel(server)
375 369 ... bprint(b'')
376 370 ... bprint(b'# LFS required- both lfs and non-lfs revlogs have 0x2000 flag')
377 371 ... stdout.flush()
378 372 ... runcommand(server, [b'debugprocessors', b'lfs.bin', b'-R',
379 373 ... b'../server'])
380 374 ... runcommand(server, [b'debugprocessors', b'nonlfs2.txt', b'-R',
381 375 ... b'../server'])
382 376 ... runcommand(server, [b'config', b'extensions', b'--cwd',
383 377 ... b'../server'])
384 378 ...
385 379 ... bprint(b"\n# LFS not enabled- revlogs don't have 0x2000 flag")
386 380 ... stdout.flush()
387 381 ... runcommand(server, [b'debugprocessors', b'nonlfs3.txt'])
388 382 ... runcommand(server, [b'config', b'extensions'])
389 383
390 384 # LFS required- both lfs and non-lfs revlogs have 0x2000 flag
391 385 *** runcommand debugprocessors lfs.bin -R ../server
392 386 registered processor '0x8000'
393 387 registered processor '0x2000'
394 388 *** runcommand debugprocessors nonlfs2.txt -R ../server
395 389 registered processor '0x8000'
396 390 registered processor '0x2000'
397 391 *** runcommand config extensions --cwd ../server
398 392 extensions.debugprocessors=$TESTTMP/debugprocessors.py
399 393 extensions.lfs=
400 394
401 395 # LFS not enabled- revlogs don't have 0x2000 flag
402 396 *** runcommand debugprocessors nonlfs3.txt
403 397 registered processor '0x8000'
404 398 *** runcommand config extensions
405 399 extensions.debugprocessors=$TESTTMP/debugprocessors.py
406 400
407 401 $ rm $HGRCPATH
408 402 $ mv $HGRCPATH.tmp $HGRCPATH
409 403
410 404 $ hg clone $TESTTMP/client $TESTTMP/nonlfs -qr 0 --config extensions.lfs=
411 405 $ cat >> $TESTTMP/nonlfs/.hg/hgrc <<EOF
412 406 > [extensions]
413 407 > lfs = !
414 408 > EOF
415 409
416 410 >>> from __future__ import absolute_import, print_function
417 411 >>> from hgclient import bprint, check, readchannel, runcommand, stdout
418 412 >>> @check
419 413 ... def checkflags2(server):
420 414 ... readchannel(server)
421 415 ... bprint(b'')
422 416 ... bprint(b'# LFS enabled- both lfs and non-lfs revlogs have 0x2000 flag')
423 417 ... stdout.flush()
424 418 ... runcommand(server, [b'debugprocessors', b'lfs.bin', b'-R',
425 419 ... b'../server'])
426 420 ... runcommand(server, [b'debugprocessors', b'nonlfs2.txt', b'-R',
427 421 ... b'../server'])
428 422 ... runcommand(server, [b'config', b'extensions', b'--cwd',
429 423 ... b'../server'])
430 424 ...
431 425 ... bprint(b'\n# LFS enabled without requirement- revlogs have 0x2000 flag')
432 426 ... stdout.flush()
433 427 ... runcommand(server, [b'debugprocessors', b'nonlfs3.txt'])
434 428 ... runcommand(server, [b'config', b'extensions'])
435 429 ...
436 430 ... bprint(b"\n# LFS disabled locally- revlogs don't have 0x2000 flag")
437 431 ... stdout.flush()
438 432 ... runcommand(server, [b'debugprocessors', b'nonlfs.txt', b'-R',
439 433 ... b'../nonlfs'])
440 434 ... runcommand(server, [b'config', b'extensions', b'--cwd',
441 435 ... b'../nonlfs'])
442 436
443 437 # LFS enabled- both lfs and non-lfs revlogs have 0x2000 flag
444 438 *** runcommand debugprocessors lfs.bin -R ../server
445 439 registered processor '0x8000'
446 440 registered processor '0x2000'
447 441 *** runcommand debugprocessors nonlfs2.txt -R ../server
448 442 registered processor '0x8000'
449 443 registered processor '0x2000'
450 444 *** runcommand config extensions --cwd ../server
451 445 extensions.debugprocessors=$TESTTMP/debugprocessors.py
452 446 extensions.lfs=
453 447
454 448 # LFS enabled without requirement- revlogs have 0x2000 flag
455 449 *** runcommand debugprocessors nonlfs3.txt
456 450 registered processor '0x8000'
457 451 registered processor '0x2000'
458 452 *** runcommand config extensions
459 453 extensions.debugprocessors=$TESTTMP/debugprocessors.py
460 454 extensions.lfs=
461 455
462 456 # LFS disabled locally- revlogs don't have 0x2000 flag
463 457 *** runcommand debugprocessors nonlfs.txt -R ../nonlfs
464 458 registered processor '0x8000'
465 459 *** runcommand config extensions --cwd ../nonlfs
466 460 extensions.debugprocessors=$TESTTMP/debugprocessors.py
467 461 extensions.lfs=!
468 462
469 463 --------------------------------------------------------------------------------
470 464 Case #6: client with lfs content and the extension enabled; server with
471 465 lfs content, and the extension enabled.
472 466
473 467 $ echo 'this is another lfs file' > lfs2.txt
474 468 $ hg ci -Aqm 'lfs file with lfs client'
475 469
476 470 $ hg --config paths.default= push -v http://localhost:$HGPORT
477 471 pushing to http://localhost:$HGPORT/
478 472 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
479 473 searching for changes
480 474 remote has heads on branch 'default' that are not known locally: 8374dc4052cb
481 475 lfs: uploading a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de (25 bytes)
482 476 lfs: processed: a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de
483 477 lfs: uploaded 1 files (25 bytes)
484 478 1 changesets found
485 479 uncompressed size of bundle content:
486 480 206 (changelog)
487 481 172 (manifests)
488 482 275 lfs2.txt
489 483 remote: adding changesets
490 484 remote: adding manifests
491 485 remote: adding file changes
492 486 remote: added 1 changesets with 1 changes to 1 files
493 487 $ grep 'lfs' .hg/requires $SERVER_REQUIRES
494 488 .hg/requires:lfs
495 489 $TESTTMP/server/.hg/requires:lfs
496 490
497 491 $ hg clone -q http://localhost:$HGPORT $TESTTMP/client6_clone
498 492 $ grep 'lfs' $TESTTMP/client6_clone/.hg/requires $SERVER_REQUIRES
499 493 $TESTTMP/client6_clone/.hg/requires:lfs
500 494 $TESTTMP/server/.hg/requires:lfs
501 495
502 496 $ hg init $TESTTMP/client6_pull
503 497 $ hg -R $TESTTMP/client6_pull pull -u -v http://localhost:$HGPORT
504 498 pulling from http://localhost:$HGPORT/
505 499 requesting all changes
506 500 adding changesets
507 501 adding manifests
508 502 adding file changes
509 503 calling hook pretxnchangegroup.lfs: hgext.lfs.checkrequireslfs
510 504 added 6 changesets with 5 changes to 5 files (+1 heads)
511 505 new changesets d437e1d24fbd:d3b84d50eacb
512 506 resolving manifests
513 507 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
514 508 lfs: downloading a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de (25 bytes)
515 509 lfs: processed: a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de
516 510 lfs: downloaded 1 files (25 bytes)
517 511 getting lfs2.txt
518 512 lfs: found a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de in the local lfs store
519 513 getting nonlfs2.txt
520 514 getting nonlfs3.txt
521 515 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
522 516 updated to "d3b84d50eacb: lfs file with lfs client"
523 517 1 other heads for branch "default"
524 518 (sent 3 HTTP requests and * bytes; received * bytes in responses) (glob)
525 519 $ grep 'lfs' $TESTTMP/client6_pull/.hg/requires $SERVER_REQUIRES
526 520 $TESTTMP/client6_pull/.hg/requires:lfs
527 521 $TESTTMP/server/.hg/requires:lfs
528 522
529 523 $ hg identify http://localhost:$HGPORT
530 524 d3b84d50eacb
531 525
532 526 --------------------------------------------------------------------------------
533 527 Misc: process dies early if a requirement exists and the extension is disabled
534 528
535 529 $ hg --config extensions.lfs=! summary
536 530 abort: repository requires features unknown to this Mercurial: lfs!
537 531 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
538 532 [255]
539 533
540 534 $ echo 'this is an lfs file' > $TESTTMP/client6_clone/lfspair1.bin
541 535 $ echo 'this is an lfs file too' > $TESTTMP/client6_clone/lfspair2.bin
542 536 $ hg -R $TESTTMP/client6_clone ci -Aqm 'add lfs pair'
543 537 $ hg -R $TESTTMP/client6_clone push -q
544 538
545 539 $ hg clone -qU http://localhost:$HGPORT $TESTTMP/bulkfetch
546 540
547 541 Cat doesn't prefetch unless data is needed (e.g. '-T {rawdata}' doesn't need it)
548 542
549 543 $ hg --cwd $TESTTMP/bulkfetch cat -vr tip lfspair1.bin -T '{rawdata}\n{path}\n'
550 544 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
551 545 version https://git-lfs.github.com/spec/v1
552 546 oid sha256:cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
553 547 size 20
554 548 x-is-binary 0
555 549
556 550 lfspair1.bin
557 551
558 552 $ hg --cwd $TESTTMP/bulkfetch cat -vr tip lfspair1.bin -T json
559 553 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
560 554 [lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
561 555 lfs: downloading cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 (20 bytes)
562 556 lfs: processed: cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
563 557 lfs: downloaded 1 files (20 bytes)
564 558 lfs: found cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 in the local lfs store
565 559
566 560 {
567 561 "data": "this is an lfs file\n",
568 562 "path": "lfspair1.bin",
569 563 "rawdata": "version https://git-lfs.github.com/spec/v1\noid sha256:cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782\nsize 20\nx-is-binary 0\n"
570 564 }
571 565 ]
572 566
573 567 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
574 568
575 569 $ hg --cwd $TESTTMP/bulkfetch cat -vr tip lfspair1.bin -T '{data}\n'
576 570 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
577 571 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
578 572 lfs: downloading cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 (20 bytes)
579 573 lfs: processed: cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
580 574 lfs: downloaded 1 files (20 bytes)
581 575 lfs: found cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 in the local lfs store
582 576 this is an lfs file
583 577
584 578 $ hg --cwd $TESTTMP/bulkfetch cat -vr tip lfspair2.bin
585 579 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
586 580 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
587 581 lfs: downloading d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e (24 bytes)
588 582 lfs: processed: d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e
589 583 lfs: downloaded 1 files (24 bytes)
590 584 lfs: found d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e in the local lfs store
591 585 this is an lfs file too
592 586
593 587 Export will prefetch all needed files across all needed revisions
594 588
595 589 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
596 590 $ hg -R $TESTTMP/bulkfetch -v export -r 0:tip -o all.export
597 591 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
598 592 exporting patches:
599 593 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
600 594 lfs: need to transfer 4 objects (92 bytes)
601 595 lfs: downloading a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de (25 bytes)
602 596 lfs: processed: a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de
603 597 lfs: downloading bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc (23 bytes)
604 598 lfs: processed: bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc
605 599 lfs: downloading cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 (20 bytes)
606 600 lfs: processed: cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
607 601 lfs: downloading d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e (24 bytes)
608 602 lfs: processed: d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e
609 603 lfs: downloaded 4 files (92 bytes)
610 604 all.export
611 605 lfs: found bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc in the local lfs store
612 606 lfs: found a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de in the local lfs store
613 607 lfs: found cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 in the local lfs store
614 608 lfs: found d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e in the local lfs store
615 609
616 610 Export with selected files is used with `extdiff --patch`
617 611
618 612 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
619 613 $ hg --config extensions.extdiff= \
620 614 > -R $TESTTMP/bulkfetch -v extdiff -r 2:tip --patch $TESTTMP/bulkfetch/lfs.bin
621 615 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
622 616 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
623 617 lfs: downloading bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc (23 bytes)
624 618 lfs: processed: bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc
625 619 lfs: downloaded 1 files (23 bytes)
626 620 */hg-8374dc4052cb.patch (glob)
627 621 lfs: found bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc in the local lfs store
628 622 */hg-9640b57e77b1.patch (glob)
629 623 --- */hg-8374dc4052cb.patch * (glob)
630 624 +++ */hg-9640b57e77b1.patch * (glob)
631 625 @@ -2,12 +2,7 @@
632 626 # User test
633 627 # Date 0 0
634 628 # Thu Jan 01 00:00:00 1970 +0000
635 629 -# Node ID 8374dc4052cbd388e79d9dc4ddb29784097aa354
636 630 -# Parent 1477875038c60152e391238920a16381c627b487
637 631 -lfs
638 632 +# Node ID 9640b57e77b14c3a0144fb4478b6cc13e13ea0d1
639 633 +# Parent d3b84d50eacbd56638e11abce6b8616aaba54420
640 634 +add lfs pair
641 635
642 636 -diff -r 1477875038c6 -r 8374dc4052cb lfs.bin
643 637 ---- /dev/null Thu Jan 01 00:00:00 1970 +0000
644 638 -+++ b/lfs.bin Thu Jan 01 00:00:00 1970 +0000
645 639 -@@ -0,0 +1,1 @@
646 640 -+this is a big lfs file
647 641 cleaning up temp directory
648 642 [1]
649 643
650 644 Diff will prefetch files
651 645
652 646 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
653 647 $ hg -R $TESTTMP/bulkfetch -v diff -r 2:tip
654 648 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
655 649 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
656 650 lfs: need to transfer 4 objects (92 bytes)
657 651 lfs: downloading a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de (25 bytes)
658 652 lfs: processed: a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de
659 653 lfs: downloading bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc (23 bytes)
660 654 lfs: processed: bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc
661 655 lfs: downloading cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 (20 bytes)
662 656 lfs: processed: cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782
663 657 lfs: downloading d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e (24 bytes)
664 658 lfs: processed: d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e
665 659 lfs: downloaded 4 files (92 bytes)
666 660 lfs: found bed80f00180ac404b843628ab56a1c1984d6145c391cd1628a7dd7d2598d71fc in the local lfs store
667 661 lfs: found a82f1c5cea0d40e3bb3a849686bb4e6ae47ca27e614de55c1ed0325698ef68de in the local lfs store
668 662 lfs: found cf1b2787b74e66547d931b6ebe28ff63303e803cb2baa14a8f57c4383d875782 in the local lfs store
669 663 lfs: found d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e in the local lfs store
670 664 diff -r 8374dc4052cb -r 9640b57e77b1 lfs.bin
671 665 --- a/lfs.bin Thu Jan 01 00:00:00 1970 +0000
672 666 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
673 667 @@ -1,1 +0,0 @@
674 668 -this is a big lfs file
675 669 diff -r 8374dc4052cb -r 9640b57e77b1 lfs2.txt
676 670 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
677 671 +++ b/lfs2.txt Thu Jan 01 00:00:00 1970 +0000
678 672 @@ -0,0 +1,1 @@
679 673 +this is another lfs file
680 674 diff -r 8374dc4052cb -r 9640b57e77b1 lfspair1.bin
681 675 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
682 676 +++ b/lfspair1.bin Thu Jan 01 00:00:00 1970 +0000
683 677 @@ -0,0 +1,1 @@
684 678 +this is an lfs file
685 679 diff -r 8374dc4052cb -r 9640b57e77b1 lfspair2.bin
686 680 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
687 681 +++ b/lfspair2.bin Thu Jan 01 00:00:00 1970 +0000
688 682 @@ -0,0 +1,1 @@
689 683 +this is an lfs file too
690 684 diff -r 8374dc4052cb -r 9640b57e77b1 nonlfs.txt
691 685 --- a/nonlfs.txt Thu Jan 01 00:00:00 1970 +0000
692 686 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
693 687 @@ -1,1 +0,0 @@
694 688 -non-lfs
695 689 diff -r 8374dc4052cb -r 9640b57e77b1 nonlfs3.txt
696 690 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
697 691 +++ b/nonlfs3.txt Thu Jan 01 00:00:00 1970 +0000
698 692 @@ -0,0 +1,1 @@
699 693 +non-lfs
700 694
701 695 Only the files required by diff are prefetched
702 696
703 697 $ rm -r $TESTTMP/bulkfetch/.hg/store/lfs
704 698 $ hg -R $TESTTMP/bulkfetch -v diff -r 2:tip $TESTTMP/bulkfetch/lfspair2.bin
705 699 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
706 700 lfs: assuming remote store: http://localhost:$HGPORT/.git/info/lfs
707 701 lfs: downloading d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e (24 bytes)
708 702 lfs: processed: d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e
709 703 lfs: downloaded 1 files (24 bytes)
710 704 lfs: found d96eda2c74b56e95cfb5ffb66b6503e198cc6fc4a09dc877de925feebc65786e in the local lfs store
711 705 diff -r 8374dc4052cb -r 9640b57e77b1 lfspair2.bin
712 706 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
713 707 +++ b/lfspair2.bin Thu Jan 01 00:00:00 1970 +0000
714 708 @@ -0,0 +1,1 @@
715 709 +this is an lfs file too
716 710
717 711 #endif
718 712
719 713 $ "$PYTHON" $TESTDIR/killdaemons.py $DAEMON_PIDS
720 714
721 715 $ cat $TESTTMP/errors.log
@@ -1,2790 +1,2790 b''
1 1 Log on empty repository: checking consistency
2 2
3 3 $ hg init empty
4 4 $ cd empty
5 5 $ hg log
6 6 $ hg log -r 1
7 7 abort: unknown revision '1'!
8 8 [255]
9 9 $ hg log -r -1:0
10 10 abort: unknown revision '-1'!
11 11 [255]
12 12 $ hg log -r 'branch(name)'
13 13 abort: unknown revision 'name'!
14 14 [255]
15 15 $ hg log -r null -q
16 16 -1:000000000000
17 17
18 18 $ cd ..
19 19
20 20 The g is crafted to have 2 filelog topological heads in a linear
21 21 changeset graph
22 22
23 23 $ hg init a
24 24 $ cd a
25 25 $ echo a > a
26 26 $ echo f > f
27 27 $ hg ci -Ama -d '1 0'
28 28 adding a
29 29 adding f
30 30
31 31 $ hg cp a b
32 32 $ hg cp f g
33 33 $ hg ci -mb -d '2 0'
34 34
35 35 $ mkdir dir
36 36 $ hg mv b dir
37 37 $ echo g >> g
38 38 $ echo f >> f
39 39 $ hg ci -mc -d '3 0'
40 40
41 41 $ hg mv a b
42 42 $ hg cp -f f g
43 43 $ echo a > d
44 44 $ hg add d
45 45 $ hg ci -md -d '4 0'
46 46
47 47 $ hg mv dir/b e
48 48 $ hg ci -me -d '5 0'
49 49
50 50 Make sure largefiles doesn't interfere with logging a regular file
51 51 $ hg --debug log a -T '{rev}: {desc}\n' --config extensions.largefiles=
52 52 The fsmonitor extension is incompatible with the largefiles extension and has been disabled. (fsmonitor !)
53 53 updated patterns: .hglf/a, a
54 54 0: a
55 55 $ hg log a
56 56 changeset: 0:9161b9aeaf16
57 57 user: test
58 58 date: Thu Jan 01 00:00:01 1970 +0000
59 59 summary: a
60 60
61 61 $ hg log glob:a*
62 62 changeset: 3:2ca5ba701980
63 63 user: test
64 64 date: Thu Jan 01 00:00:04 1970 +0000
65 65 summary: d
66 66
67 67 changeset: 0:9161b9aeaf16
68 68 user: test
69 69 date: Thu Jan 01 00:00:01 1970 +0000
70 70 summary: a
71 71
72 72 $ hg --debug log glob:a* -T '{rev}: {desc}\n' --config extensions.largefiles=
73 73 The fsmonitor extension is incompatible with the largefiles extension and has been disabled. (fsmonitor !)
74 74 updated patterns: glob:.hglf/a*, glob:a*
75 75 3: d
76 76 0: a
77 77
78 78 log on directory
79 79
80 80 $ hg log dir
81 81 changeset: 4:7e4639b4691b
82 82 tag: tip
83 83 user: test
84 84 date: Thu Jan 01 00:00:05 1970 +0000
85 85 summary: e
86 86
87 87 changeset: 2:f8954cd4dc1f
88 88 user: test
89 89 date: Thu Jan 01 00:00:03 1970 +0000
90 90 summary: c
91 91
92 92 $ hg log somethingthatdoesntexist dir
93 93 changeset: 4:7e4639b4691b
94 94 tag: tip
95 95 user: test
96 96 date: Thu Jan 01 00:00:05 1970 +0000
97 97 summary: e
98 98
99 99 changeset: 2:f8954cd4dc1f
100 100 user: test
101 101 date: Thu Jan 01 00:00:03 1970 +0000
102 102 summary: c
103 103
104 104
105 105 -X, with explicit path
106 106
107 107 $ hg log a -X a
108 108
109 109 -f, non-existent directory
110 110
111 111 $ hg log -f dir
112 112 abort: cannot follow file not in parent revision: "dir"
113 113 [255]
114 114
115 115 -f, directory
116 116
117 117 $ hg up -q 3
118 118 $ hg log -f dir
119 119 changeset: 2:f8954cd4dc1f
120 120 user: test
121 121 date: Thu Jan 01 00:00:03 1970 +0000
122 122 summary: c
123 123
124 124 -f, directory with --patch
125 125
126 126 $ hg log -f dir -p
127 127 changeset: 2:f8954cd4dc1f
128 128 user: test
129 129 date: Thu Jan 01 00:00:03 1970 +0000
130 130 summary: c
131 131
132 132 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
133 133 --- /dev/null* (glob)
134 134 +++ b/dir/b* (glob)
135 135 @@ -0,0 +1,1 @@
136 136 +a
137 137
138 138
139 139 -f, pattern
140 140
141 141 $ hg log -f -I 'dir**' -p
142 142 changeset: 2:f8954cd4dc1f
143 143 user: test
144 144 date: Thu Jan 01 00:00:03 1970 +0000
145 145 summary: c
146 146
147 147 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
148 148 --- /dev/null* (glob)
149 149 +++ b/dir/b* (glob)
150 150 @@ -0,0 +1,1 @@
151 151 +a
152 152
153 153 $ hg up -q 4
154 154
155 155 -f, a wrong style
156 156
157 157 $ hg log -f -l1 --style something
158 158 abort: style 'something' not found
159 159 (available styles: bisect, changelog, compact, default, phases, show, status, xml)
160 160 [255]
161 161
162 162 -f, phases style
163 163
164 164
165 165 $ hg log -f -l1 --style phases
166 166 changeset: 4:7e4639b4691b
167 167 tag: tip
168 168 phase: draft
169 169 user: test
170 170 date: Thu Jan 01 00:00:05 1970 +0000
171 171 summary: e
172 172
173 173
174 174 $ hg log -f -l1 --style phases -q
175 175 4:7e4639b4691b
176 176
177 177 -f, but no args
178 178
179 179 $ hg log -f
180 180 changeset: 4:7e4639b4691b
181 181 tag: tip
182 182 user: test
183 183 date: Thu Jan 01 00:00:05 1970 +0000
184 184 summary: e
185 185
186 186 changeset: 3:2ca5ba701980
187 187 user: test
188 188 date: Thu Jan 01 00:00:04 1970 +0000
189 189 summary: d
190 190
191 191 changeset: 2:f8954cd4dc1f
192 192 user: test
193 193 date: Thu Jan 01 00:00:03 1970 +0000
194 194 summary: c
195 195
196 196 changeset: 1:d89b0a12d229
197 197 user: test
198 198 date: Thu Jan 01 00:00:02 1970 +0000
199 199 summary: b
200 200
201 201 changeset: 0:9161b9aeaf16
202 202 user: test
203 203 date: Thu Jan 01 00:00:01 1970 +0000
204 204 summary: a
205 205
206 206
207 207 one rename
208 208
209 209 $ hg up -q 2
210 210 $ hg log -vf a
211 211 changeset: 0:9161b9aeaf16
212 212 user: test
213 213 date: Thu Jan 01 00:00:01 1970 +0000
214 214 files: a f
215 215 description:
216 216 a
217 217
218 218
219 219
220 220 many renames
221 221
222 222 $ hg up -q tip
223 223 $ hg log -vf e
224 224 changeset: 4:7e4639b4691b
225 225 tag: tip
226 226 user: test
227 227 date: Thu Jan 01 00:00:05 1970 +0000
228 228 files: dir/b e
229 229 description:
230 230 e
231 231
232 232
233 233 changeset: 2:f8954cd4dc1f
234 234 user: test
235 235 date: Thu Jan 01 00:00:03 1970 +0000
236 236 files: b dir/b f g
237 237 description:
238 238 c
239 239
240 240
241 241 changeset: 1:d89b0a12d229
242 242 user: test
243 243 date: Thu Jan 01 00:00:02 1970 +0000
244 244 files: b g
245 245 description:
246 246 b
247 247
248 248
249 249 changeset: 0:9161b9aeaf16
250 250 user: test
251 251 date: Thu Jan 01 00:00:01 1970 +0000
252 252 files: a f
253 253 description:
254 254 a
255 255
256 256
257 257
258 258
259 259 log -pf dir/b
260 260
261 261 $ hg up -q 3
262 262 $ hg log -pf dir/b
263 263 changeset: 2:f8954cd4dc1f
264 264 user: test
265 265 date: Thu Jan 01 00:00:03 1970 +0000
266 266 summary: c
267 267
268 268 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
269 269 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
270 270 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
271 271 @@ -0,0 +1,1 @@
272 272 +a
273 273
274 274 changeset: 1:d89b0a12d229
275 275 user: test
276 276 date: Thu Jan 01 00:00:02 1970 +0000
277 277 summary: b
278 278
279 279 diff -r 9161b9aeaf16 -r d89b0a12d229 b
280 280 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
281 281 +++ b/b Thu Jan 01 00:00:02 1970 +0000
282 282 @@ -0,0 +1,1 @@
283 283 +a
284 284
285 285 changeset: 0:9161b9aeaf16
286 286 user: test
287 287 date: Thu Jan 01 00:00:01 1970 +0000
288 288 summary: a
289 289
290 290 diff -r 000000000000 -r 9161b9aeaf16 a
291 291 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
292 292 +++ b/a Thu Jan 01 00:00:01 1970 +0000
293 293 @@ -0,0 +1,1 @@
294 294 +a
295 295
296 296
297 297 log -pf b inside dir
298 298
299 299 $ hg --cwd=dir log -pf b
300 300 changeset: 2:f8954cd4dc1f
301 301 user: test
302 302 date: Thu Jan 01 00:00:03 1970 +0000
303 303 summary: c
304 304
305 305 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
306 306 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
307 307 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
308 308 @@ -0,0 +1,1 @@
309 309 +a
310 310
311 311 changeset: 1:d89b0a12d229
312 312 user: test
313 313 date: Thu Jan 01 00:00:02 1970 +0000
314 314 summary: b
315 315
316 316 diff -r 9161b9aeaf16 -r d89b0a12d229 b
317 317 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
318 318 +++ b/b Thu Jan 01 00:00:02 1970 +0000
319 319 @@ -0,0 +1,1 @@
320 320 +a
321 321
322 322 changeset: 0:9161b9aeaf16
323 323 user: test
324 324 date: Thu Jan 01 00:00:01 1970 +0000
325 325 summary: a
326 326
327 327 diff -r 000000000000 -r 9161b9aeaf16 a
328 328 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
329 329 +++ b/a Thu Jan 01 00:00:01 1970 +0000
330 330 @@ -0,0 +1,1 @@
331 331 +a
332 332
333 333
334 334 log -pf, but no args
335 335
336 336 $ hg log -pf
337 337 changeset: 3:2ca5ba701980
338 338 user: test
339 339 date: Thu Jan 01 00:00:04 1970 +0000
340 340 summary: d
341 341
342 342 diff -r f8954cd4dc1f -r 2ca5ba701980 a
343 343 --- a/a Thu Jan 01 00:00:03 1970 +0000
344 344 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
345 345 @@ -1,1 +0,0 @@
346 346 -a
347 347 diff -r f8954cd4dc1f -r 2ca5ba701980 b
348 348 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
349 349 +++ b/b Thu Jan 01 00:00:04 1970 +0000
350 350 @@ -0,0 +1,1 @@
351 351 +a
352 352 diff -r f8954cd4dc1f -r 2ca5ba701980 d
353 353 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
354 354 +++ b/d Thu Jan 01 00:00:04 1970 +0000
355 355 @@ -0,0 +1,1 @@
356 356 +a
357 357 diff -r f8954cd4dc1f -r 2ca5ba701980 g
358 358 --- a/g Thu Jan 01 00:00:03 1970 +0000
359 359 +++ b/g Thu Jan 01 00:00:04 1970 +0000
360 360 @@ -1,2 +1,2 @@
361 361 f
362 362 -g
363 363 +f
364 364
365 365 changeset: 2:f8954cd4dc1f
366 366 user: test
367 367 date: Thu Jan 01 00:00:03 1970 +0000
368 368 summary: c
369 369
370 370 diff -r d89b0a12d229 -r f8954cd4dc1f b
371 371 --- a/b Thu Jan 01 00:00:02 1970 +0000
372 372 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
373 373 @@ -1,1 +0,0 @@
374 374 -a
375 375 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
376 376 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
377 377 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
378 378 @@ -0,0 +1,1 @@
379 379 +a
380 380 diff -r d89b0a12d229 -r f8954cd4dc1f f
381 381 --- a/f Thu Jan 01 00:00:02 1970 +0000
382 382 +++ b/f Thu Jan 01 00:00:03 1970 +0000
383 383 @@ -1,1 +1,2 @@
384 384 f
385 385 +f
386 386 diff -r d89b0a12d229 -r f8954cd4dc1f g
387 387 --- a/g Thu Jan 01 00:00:02 1970 +0000
388 388 +++ b/g Thu Jan 01 00:00:03 1970 +0000
389 389 @@ -1,1 +1,2 @@
390 390 f
391 391 +g
392 392
393 393 changeset: 1:d89b0a12d229
394 394 user: test
395 395 date: Thu Jan 01 00:00:02 1970 +0000
396 396 summary: b
397 397
398 398 diff -r 9161b9aeaf16 -r d89b0a12d229 b
399 399 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
400 400 +++ b/b Thu Jan 01 00:00:02 1970 +0000
401 401 @@ -0,0 +1,1 @@
402 402 +a
403 403 diff -r 9161b9aeaf16 -r d89b0a12d229 g
404 404 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
405 405 +++ b/g Thu Jan 01 00:00:02 1970 +0000
406 406 @@ -0,0 +1,1 @@
407 407 +f
408 408
409 409 changeset: 0:9161b9aeaf16
410 410 user: test
411 411 date: Thu Jan 01 00:00:01 1970 +0000
412 412 summary: a
413 413
414 414 diff -r 000000000000 -r 9161b9aeaf16 a
415 415 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
416 416 +++ b/a Thu Jan 01 00:00:01 1970 +0000
417 417 @@ -0,0 +1,1 @@
418 418 +a
419 419 diff -r 000000000000 -r 9161b9aeaf16 f
420 420 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
421 421 +++ b/f Thu Jan 01 00:00:01 1970 +0000
422 422 @@ -0,0 +1,1 @@
423 423 +f
424 424
425 425
426 426 log -vf dir/b
427 427
428 428 $ hg log -vf dir/b
429 429 changeset: 2:f8954cd4dc1f
430 430 user: test
431 431 date: Thu Jan 01 00:00:03 1970 +0000
432 432 files: b dir/b f g
433 433 description:
434 434 c
435 435
436 436
437 437 changeset: 1:d89b0a12d229
438 438 user: test
439 439 date: Thu Jan 01 00:00:02 1970 +0000
440 440 files: b g
441 441 description:
442 442 b
443 443
444 444
445 445 changeset: 0:9161b9aeaf16
446 446 user: test
447 447 date: Thu Jan 01 00:00:01 1970 +0000
448 448 files: a f
449 449 description:
450 450 a
451 451
452 452
453 453
454 454
455 455 -f and multiple filelog heads
456 456
457 457 $ hg up -q 2
458 458 $ hg log -f g --template '{rev}\n'
459 459 2
460 460 1
461 461 0
462 462 $ hg up -q tip
463 463 $ hg log -f g --template '{rev}\n'
464 464 3
465 465 2
466 466 0
467 467
468 468 follow files from the specified revisions (issue4959)
469 469
470 470 $ hg log -G -T '{rev} {files},{file_copies % " {source}->{name}"}\n'
471 471 @ 4 dir/b e, dir/b->e
472 472 |
473 473 o 3 a b d g, a->b f->g
474 474 |
475 475 o 2 b dir/b f g, b->dir/b
476 476 |
477 477 o 1 b g, a->b f->g
478 478 |
479 479 o 0 a f,
480 480
481 481
482 482 $ hg log -T '{rev}\n' -fr 4 e
483 483 4
484 484 2
485 485 1
486 486 0
487 487 $ hg log -T '{rev}\n' -fr 2 g
488 488 2
489 489 1
490 490 0
491 491 $ hg log -T '{rev}\n' -fr '2+3' g
492 492 3
493 493 2
494 494 1
495 495 0
496 496
497 497 follow files from the specified revisions with glob patterns (issue5053)
498 498 (BROKEN: should follow copies from e@4)
499 499
500 500 $ hg log -T '{rev}\n' -fr4 e -X '[abcdfg]'
501 501 4
502 502 2 (false !)
503 503 1 (false !)
504 504 0 (false !)
505 505
506 506 follow files from the specified revisions with missing patterns
507 507 (BROKEN: should follow copies from e@4)
508 508
509 509 $ hg log -T '{rev}\n' -fr4 e x
510 510 4
511 511 2 (false !)
512 512 1 (false !)
513 513 0 (false !)
514 514
515 515 follow files from the specified revisions across copies with -p/--patch
516 516
517 517 $ hg log -T '== rev: {rev},{file_copies % " {source}->{name}"} ==\n' -fpr 4 e g
518 518 == rev: 4, dir/b->e ==
519 519 diff -r 2ca5ba701980 -r 7e4639b4691b e
520 520 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
521 521 +++ b/e Thu Jan 01 00:00:05 1970 +0000
522 522 @@ -0,0 +1,1 @@
523 523 +a
524 524
525 525 == rev: 3, a->b f->g ==
526 526 diff -r f8954cd4dc1f -r 2ca5ba701980 g
527 527 --- a/g Thu Jan 01 00:00:03 1970 +0000
528 528 +++ b/g Thu Jan 01 00:00:04 1970 +0000
529 529 @@ -1,2 +1,2 @@
530 530 f
531 531 -g
532 532 +f
533 533
534 534 == rev: 2, b->dir/b ==
535 535 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
536 536 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
537 537 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
538 538 @@ -0,0 +1,1 @@
539 539 +a
540 540 diff -r d89b0a12d229 -r f8954cd4dc1f f
541 541 --- a/f Thu Jan 01 00:00:02 1970 +0000
542 542 +++ b/f Thu Jan 01 00:00:03 1970 +0000
543 543 @@ -1,1 +1,2 @@
544 544 f
545 545 +f
546 546
547 547 == rev: 1, a->b f->g ==
548 548 diff -r 9161b9aeaf16 -r d89b0a12d229 b
549 549 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
550 550 +++ b/b Thu Jan 01 00:00:02 1970 +0000
551 551 @@ -0,0 +1,1 @@
552 552 +a
553 553
554 554 == rev: 0, ==
555 555 diff -r 000000000000 -r 9161b9aeaf16 a
556 556 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
557 557 +++ b/a Thu Jan 01 00:00:01 1970 +0000
558 558 @@ -0,0 +1,1 @@
559 559 +a
560 560 diff -r 000000000000 -r 9161b9aeaf16 f
561 561 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
562 562 +++ b/f Thu Jan 01 00:00:01 1970 +0000
563 563 @@ -0,0 +1,1 @@
564 564 +f
565 565
566 566
567 567 log copies with --copies
568 568
569 569 $ hg log -vC --template '{rev} {file_copies}\n'
570 570 4 e (dir/b)
571 571 3 b (a)g (f)
572 572 2 dir/b (b)
573 573 1 b (a)g (f)
574 574 0
575 575
576 576 log copies switch without --copies, with old filecopy template
577 577
578 578 $ hg log -v --template '{rev} {file_copies_switch%filecopy}\n'
579 579 4
580 580 3
581 581 2
582 582 1
583 583 0
584 584
585 585 log copies switch with --copies
586 586
587 587 $ hg log -vC --template '{rev} {file_copies_switch}\n'
588 588 4 e (dir/b)
589 589 3 b (a)g (f)
590 590 2 dir/b (b)
591 591 1 b (a)g (f)
592 592 0
593 593
594 594
595 595 log copies with hardcoded style and with --style=default
596 596
597 597 $ hg log -vC -r4
598 598 changeset: 4:7e4639b4691b
599 599 tag: tip
600 600 user: test
601 601 date: Thu Jan 01 00:00:05 1970 +0000
602 602 files: dir/b e
603 603 copies: e (dir/b)
604 604 description:
605 605 e
606 606
607 607
608 608 $ hg log -vC -r4 --style=default
609 609 changeset: 4:7e4639b4691b
610 610 tag: tip
611 611 user: test
612 612 date: Thu Jan 01 00:00:05 1970 +0000
613 613 files: dir/b e
614 614 copies: e (dir/b)
615 615 description:
616 616 e
617 617
618 618
619 619 $ hg log -vC -r4 -Tjson
620 620 [
621 621 {
622 622 "bookmarks": [],
623 623 "branch": "default",
624 624 "copies": {"e": "dir/b"},
625 625 "date": [5, 0],
626 626 "desc": "e",
627 627 "files": ["dir/b", "e"],
628 628 "node": "7e4639b4691b9f84b81036a8d4fb218ce3c5e3a3",
629 629 "parents": ["2ca5ba7019804f1f597249caddf22a64d34df0ba"],
630 630 "phase": "draft",
631 631 "rev": 4,
632 632 "tags": ["tip"],
633 633 "user": "test"
634 634 }
635 635 ]
636 636
637 637 log copies, non-linear manifest
638 638
639 639 $ hg up -C 3
640 640 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
641 641 $ hg mv dir/b e
642 642 $ echo foo > foo
643 643 $ hg ci -Ame2 -d '6 0'
644 644 adding foo
645 645 created new head
646 646 $ hg log -v --template '{rev} {file_copies}\n' -r 5
647 647 5 e (dir/b)
648 648
649 649
650 650 log copies, execute bit set
651 651
652 652 #if execbit
653 653 $ chmod +x e
654 654 $ hg ci -me3 -d '7 0'
655 655 $ hg log -v --template '{rev} {file_copies}\n' -r 6
656 656 6
657 657 #endif
658 658
659 659 log copies, empty set
660 660
661 661 $ hg log --copies -r '0 and not 0'
662 662
663 663 log -p d
664 664
665 665 $ hg log -pv d
666 666 changeset: 3:2ca5ba701980
667 667 user: test
668 668 date: Thu Jan 01 00:00:04 1970 +0000
669 669 files: a b d g
670 670 description:
671 671 d
672 672
673 673
674 674 diff -r f8954cd4dc1f -r 2ca5ba701980 d
675 675 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
676 676 +++ b/d Thu Jan 01 00:00:04 1970 +0000
677 677 @@ -0,0 +1,1 @@
678 678 +a
679 679
680 680
681 681
682 682 log --removed file
683 683
684 684 $ hg log --removed -v a
685 685 changeset: 3:2ca5ba701980
686 686 user: test
687 687 date: Thu Jan 01 00:00:04 1970 +0000
688 688 files: a b d g
689 689 description:
690 690 d
691 691
692 692
693 693 changeset: 0:9161b9aeaf16
694 694 user: test
695 695 date: Thu Jan 01 00:00:01 1970 +0000
696 696 files: a f
697 697 description:
698 698 a
699 699
700 700
701 701
702 702 log --removed revrange file
703 703
704 704 $ hg log --removed -v -r0:2 a
705 705 changeset: 0:9161b9aeaf16
706 706 user: test
707 707 date: Thu Jan 01 00:00:01 1970 +0000
708 708 files: a f
709 709 description:
710 710 a
711 711
712 712
713 713 $ cd ..
714 714
715 715 log --follow tests
716 716
717 717 $ hg init follow
718 718 $ cd follow
719 719
720 720 $ echo base > base
721 721 $ hg ci -Ambase -d '1 0'
722 722 adding base
723 723
724 724 $ echo r1 >> base
725 725 $ hg ci -Amr1 -d '1 0'
726 726 $ echo r2 >> base
727 727 $ hg ci -Amr2 -d '1 0'
728 728
729 729 $ hg up -C 1
730 730 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
731 731 $ echo b1 > b1
732 732
733 733 log -r "follow('set:clean()')"
734 734
735 735 $ hg log -r "follow('set:clean()')"
736 736 changeset: 0:67e992f2c4f3
737 737 user: test
738 738 date: Thu Jan 01 00:00:01 1970 +0000
739 739 summary: base
740 740
741 741 changeset: 1:3d5bf5654eda
742 742 user: test
743 743 date: Thu Jan 01 00:00:01 1970 +0000
744 744 summary: r1
745 745
746 746
747 747 $ hg ci -Amb1 -d '1 0'
748 748 adding b1
749 749 created new head
750 750
751 751
752 752 log -f
753 753
754 754 $ hg log -f
755 755 changeset: 3:e62f78d544b4
756 756 tag: tip
757 757 parent: 1:3d5bf5654eda
758 758 user: test
759 759 date: Thu Jan 01 00:00:01 1970 +0000
760 760 summary: b1
761 761
762 762 changeset: 1:3d5bf5654eda
763 763 user: test
764 764 date: Thu Jan 01 00:00:01 1970 +0000
765 765 summary: r1
766 766
767 767 changeset: 0:67e992f2c4f3
768 768 user: test
769 769 date: Thu Jan 01 00:00:01 1970 +0000
770 770 summary: base
771 771
772 772
773 773 log -r follow('glob:b*')
774 774
775 775 $ hg log -r "follow('glob:b*')"
776 776 changeset: 0:67e992f2c4f3
777 777 user: test
778 778 date: Thu Jan 01 00:00:01 1970 +0000
779 779 summary: base
780 780
781 781 changeset: 1:3d5bf5654eda
782 782 user: test
783 783 date: Thu Jan 01 00:00:01 1970 +0000
784 784 summary: r1
785 785
786 786 changeset: 3:e62f78d544b4
787 787 tag: tip
788 788 parent: 1:3d5bf5654eda
789 789 user: test
790 790 date: Thu Jan 01 00:00:01 1970 +0000
791 791 summary: b1
792 792
793 793 log -f -r '1 + 4'
794 794
795 795 $ hg up -C 0
796 796 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
797 797 $ echo b2 > b2
798 798 $ hg ci -Amb2 -d '1 0'
799 799 adding b2
800 800 created new head
801 801 $ hg log -f -r '1 + 4'
802 802 changeset: 4:ddb82e70d1a1
803 803 tag: tip
804 804 parent: 0:67e992f2c4f3
805 805 user: test
806 806 date: Thu Jan 01 00:00:01 1970 +0000
807 807 summary: b2
808 808
809 809 changeset: 1:3d5bf5654eda
810 810 user: test
811 811 date: Thu Jan 01 00:00:01 1970 +0000
812 812 summary: r1
813 813
814 814 changeset: 0:67e992f2c4f3
815 815 user: test
816 816 date: Thu Jan 01 00:00:01 1970 +0000
817 817 summary: base
818 818
819 819
820 820 log -fr with aliases: 'A' should be expanded, but 'reverse()' should have no
821 821 effect
822 822
823 823 $ hg log --config 'revsetalias.reverse(x)=x' --config 'revsetalias.A=1+4' -qfrA
824 824 4:ddb82e70d1a1
825 825 1:3d5bf5654eda
826 826 0:67e992f2c4f3
827 827
828 828 log -r "follow('set:grep(b2)')"
829 829
830 830 $ hg log -r "follow('set:grep(b2)')"
831 831 changeset: 4:ddb82e70d1a1
832 832 tag: tip
833 833 parent: 0:67e992f2c4f3
834 834 user: test
835 835 date: Thu Jan 01 00:00:01 1970 +0000
836 836 summary: b2
837 837
838 838 log -r "follow('set:grep(b2)', 4)"
839 839
840 840 $ hg up -qC 0
841 841 $ hg log -r "follow('set:grep(b2)', 4)"
842 842 changeset: 4:ddb82e70d1a1
843 843 tag: tip
844 844 parent: 0:67e992f2c4f3
845 845 user: test
846 846 date: Thu Jan 01 00:00:01 1970 +0000
847 847 summary: b2
848 848
849 849
850 850 follow files starting from multiple revisions:
851 851
852 852 $ hg log -T '{rev}: {files}\n' -r "follow('glob:b?', startrev=2+3+4)"
853 853 3: b1
854 854 4: b2
855 855
856 856 follow files starting from empty revision:
857 857
858 858 $ hg log -T '{rev}: {files}\n' -r "follow('glob:*', startrev=.-.)"
859 859
860 860 follow starting from revisions:
861 861
862 862 $ hg log -Gq -r "follow(startrev=2+4)"
863 863 o 4:ddb82e70d1a1
864 864 |
865 865 | o 2:60c670bf5b30
866 866 | |
867 867 | o 1:3d5bf5654eda
868 868 |/
869 869 @ 0:67e992f2c4f3
870 870
871 871
872 872 follow the current revision:
873 873
874 874 $ hg log -Gq -r "follow()"
875 875 @ 0:67e992f2c4f3
876 876
877 877
878 878 $ hg up -qC 4
879 879
880 880 log -f -r null
881 881
882 882 $ hg log -f -r null
883 883 changeset: -1:000000000000
884 884 user:
885 885 date: Thu Jan 01 00:00:00 1970 +0000
886 886
887 887 $ hg log -f -r null -G
888 888 o changeset: -1:000000000000
889 889 user:
890 890 date: Thu Jan 01 00:00:00 1970 +0000
891 891
892 892
893 893
894 894 log -f with null parent
895 895
896 896 $ hg up -C null
897 897 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
898 898 $ hg log -f
899 899
900 900
901 901 log -r . with two parents
902 902
903 903 $ hg up -C 3
904 904 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
905 905 $ hg merge tip
906 906 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
907 907 (branch merge, don't forget to commit)
908 908 $ hg log -r .
909 909 changeset: 3:e62f78d544b4
910 910 parent: 1:3d5bf5654eda
911 911 user: test
912 912 date: Thu Jan 01 00:00:01 1970 +0000
913 913 summary: b1
914 914
915 915
916 916
917 917 log -r . with one parent
918 918
919 919 $ hg ci -mm12 -d '1 0'
920 920 $ hg log -r .
921 921 changeset: 5:302e9dd6890d
922 922 tag: tip
923 923 parent: 3:e62f78d544b4
924 924 parent: 4:ddb82e70d1a1
925 925 user: test
926 926 date: Thu Jan 01 00:00:01 1970 +0000
927 927 summary: m12
928 928
929 929
930 930 $ echo postm >> b1
931 931 $ hg ci -Amb1.1 -d'1 0'
932 932
933 933
934 934 log --follow-first
935 935
936 936 $ hg log --follow-first
937 937 changeset: 6:2404bbcab562
938 938 tag: tip
939 939 user: test
940 940 date: Thu Jan 01 00:00:01 1970 +0000
941 941 summary: b1.1
942 942
943 943 changeset: 5:302e9dd6890d
944 944 parent: 3:e62f78d544b4
945 945 parent: 4:ddb82e70d1a1
946 946 user: test
947 947 date: Thu Jan 01 00:00:01 1970 +0000
948 948 summary: m12
949 949
950 950 changeset: 3:e62f78d544b4
951 951 parent: 1:3d5bf5654eda
952 952 user: test
953 953 date: Thu Jan 01 00:00:01 1970 +0000
954 954 summary: b1
955 955
956 956 changeset: 1:3d5bf5654eda
957 957 user: test
958 958 date: Thu Jan 01 00:00:01 1970 +0000
959 959 summary: r1
960 960
961 961 changeset: 0:67e992f2c4f3
962 962 user: test
963 963 date: Thu Jan 01 00:00:01 1970 +0000
964 964 summary: base
965 965
966 966
967 967
968 968 log -P 2
969 969
970 970 $ hg log -P 2
971 971 changeset: 6:2404bbcab562
972 972 tag: tip
973 973 user: test
974 974 date: Thu Jan 01 00:00:01 1970 +0000
975 975 summary: b1.1
976 976
977 977 changeset: 5:302e9dd6890d
978 978 parent: 3:e62f78d544b4
979 979 parent: 4:ddb82e70d1a1
980 980 user: test
981 981 date: Thu Jan 01 00:00:01 1970 +0000
982 982 summary: m12
983 983
984 984 changeset: 4:ddb82e70d1a1
985 985 parent: 0:67e992f2c4f3
986 986 user: test
987 987 date: Thu Jan 01 00:00:01 1970 +0000
988 988 summary: b2
989 989
990 990 changeset: 3:e62f78d544b4
991 991 parent: 1:3d5bf5654eda
992 992 user: test
993 993 date: Thu Jan 01 00:00:01 1970 +0000
994 994 summary: b1
995 995
996 996
997 997
998 998 log -r tip -p --git
999 999
1000 1000 $ hg log -r tip -p --git
1001 1001 changeset: 6:2404bbcab562
1002 1002 tag: tip
1003 1003 user: test
1004 1004 date: Thu Jan 01 00:00:01 1970 +0000
1005 1005 summary: b1.1
1006 1006
1007 1007 diff --git a/b1 b/b1
1008 1008 --- a/b1
1009 1009 +++ b/b1
1010 1010 @@ -1,1 +1,2 @@
1011 1011 b1
1012 1012 +postm
1013 1013
1014 1014
1015 1015
1016 1016 log -r ""
1017 1017
1018 1018 $ hg log -r ''
1019 1019 hg: parse error: empty query
1020 1020 [255]
1021 1021
1022 1022 log -r <some unknown node id>
1023 1023
1024 1024 $ hg log -r 1000000000000000000000000000000000000000
1025 1025 abort: unknown revision '1000000000000000000000000000000000000000'!
1026 1026 [255]
1027 1027
1028 1028 log -k r1
1029 1029
1030 1030 $ hg log -k r1
1031 1031 changeset: 1:3d5bf5654eda
1032 1032 user: test
1033 1033 date: Thu Jan 01 00:00:01 1970 +0000
1034 1034 summary: r1
1035 1035
1036 1036 log -p -l2 --color=always
1037 1037
1038 1038 $ hg --config extensions.color= --config color.mode=ansi \
1039 1039 > log -p -l2 --color=always
1040 1040 \x1b[0;33mchangeset: 6:2404bbcab562\x1b[0m (esc)
1041 1041 tag: tip
1042 1042 user: test
1043 1043 date: Thu Jan 01 00:00:01 1970 +0000
1044 1044 summary: b1.1
1045 1045
1046 1046 \x1b[0;1mdiff -r 302e9dd6890d -r 2404bbcab562 b1\x1b[0m (esc)
1047 1047 \x1b[0;31;1m--- a/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
1048 1048 \x1b[0;32;1m+++ b/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
1049 1049 \x1b[0;35m@@ -1,1 +1,2 @@\x1b[0m (esc)
1050 1050 b1
1051 1051 \x1b[0;32m+postm\x1b[0m (esc)
1052 1052
1053 1053 \x1b[0;33mchangeset: 5:302e9dd6890d\x1b[0m (esc)
1054 1054 parent: 3:e62f78d544b4
1055 1055 parent: 4:ddb82e70d1a1
1056 1056 user: test
1057 1057 date: Thu Jan 01 00:00:01 1970 +0000
1058 1058 summary: m12
1059 1059
1060 1060 \x1b[0;1mdiff -r e62f78d544b4 -r 302e9dd6890d b2\x1b[0m (esc)
1061 1061 \x1b[0;31;1m--- /dev/null Thu Jan 01 00:00:00 1970 +0000\x1b[0m (esc)
1062 1062 \x1b[0;32;1m+++ b/b2 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
1063 1063 \x1b[0;35m@@ -0,0 +1,1 @@\x1b[0m (esc)
1064 1064 \x1b[0;32m+b2\x1b[0m (esc)
1065 1065
1066 1066
1067 1067
1068 1068 log -r tip --stat
1069 1069
1070 1070 $ hg log -r tip --stat
1071 1071 changeset: 6:2404bbcab562
1072 1072 tag: tip
1073 1073 user: test
1074 1074 date: Thu Jan 01 00:00:01 1970 +0000
1075 1075 summary: b1.1
1076 1076
1077 1077 b1 | 1 +
1078 1078 1 files changed, 1 insertions(+), 0 deletions(-)
1079 1079
1080 1080
1081 1081 $ cd ..
1082 1082
1083 1083 log --follow --patch FILE in repository where linkrev isn't trustworthy
1084 1084 (issue5376, issue6124)
1085 1085
1086 1086 $ hg init follow-dup
1087 1087 $ cd follow-dup
1088 1088 $ cat <<EOF >> .hg/hgrc
1089 1089 > [ui]
1090 1090 > logtemplate = '=== {rev}: {desc}\n'
1091 1091 > [diff]
1092 1092 > nodates = True
1093 1093 > EOF
1094 1094 $ echo 0 >> a
1095 1095 $ hg ci -qAm 'a0'
1096 1096 $ echo 1 >> a
1097 1097 $ hg ci -m 'a1'
1098 1098 $ hg up -q 0
1099 1099 $ echo 1 >> a
1100 1100 $ touch b
1101 1101 $ hg ci -qAm 'a1 with b'
1102 1102 $ echo 3 >> a
1103 1103 $ hg ci -m 'a3'
1104 1104
1105 1105 fctx.rev() == 2, but fctx.linkrev() == 1
1106 1106
1107 1107 $ hg log -pf a
1108 1108 === 3: a3
1109 1109 diff -r 4ea02ba94d66 -r e7a6331a34f0 a
1110 1110 --- a/a
1111 1111 +++ b/a
1112 1112 @@ -1,2 +1,3 @@
1113 1113 0
1114 1114 1
1115 1115 +3
1116 1116
1117 1117 === 2: a1 with b
1118 1118 diff -r 49b5e81287e2 -r 4ea02ba94d66 a
1119 1119 --- a/a
1120 1120 +++ b/a
1121 1121 @@ -1,1 +1,2 @@
1122 1122 0
1123 1123 +1
1124 1124
1125 1125 === 0: a0
1126 1126 diff -r 000000000000 -r 49b5e81287e2 a
1127 1127 --- /dev/null
1128 1128 +++ b/a
1129 1129 @@ -0,0 +1,1 @@
1130 1130 +0
1131 1131
1132 1132 $ hg log -pr . a
1133 1133 === 3: a3
1134 1134 diff -r 4ea02ba94d66 -r e7a6331a34f0 a
1135 1135 --- a/a
1136 1136 +++ b/a
1137 1137 @@ -1,2 +1,3 @@
1138 1138 0
1139 1139 1
1140 1140 +3
1141 1141
1142 1142
1143 1143 fctx.introrev() == 2, but fctx.linkrev() == 1
1144 1144
1145 1145 $ hg up -q 2
1146 1146 $ hg log -pf a
1147 1147 === 2: a1 with b
1148 1148 diff -r 49b5e81287e2 -r 4ea02ba94d66 a
1149 1149 --- a/a
1150 1150 +++ b/a
1151 1151 @@ -1,1 +1,2 @@
1152 1152 0
1153 1153 +1
1154 1154
1155 1155 === 0: a0
1156 1156 diff -r 000000000000 -r 49b5e81287e2 a
1157 1157 --- /dev/null
1158 1158 +++ b/a
1159 1159 @@ -0,0 +1,1 @@
1160 1160 +0
1161 1161
1162 1162
1163 1163 BROKEN: should show the same diff as for rev 2 above
1164 1164 $ hg log -pr . a
1165 1165
1166 1166 $ cd ..
1167 1167
1168 1168 Multiple copy sources of a file:
1169 1169
1170 1170 $ hg init follow-multi
1171 1171 $ cd follow-multi
1172 1172 $ echo 0 >> a
1173 1173 $ hg ci -qAm 'a'
1174 1174 $ hg cp a b
1175 1175 $ hg ci -m 'a->b'
1176 1176 $ echo 2 >> a
1177 1177 $ hg ci -m 'a'
1178 1178 $ echo 3 >> b
1179 1179 $ hg ci -m 'b'
1180 1180 $ echo 4 >> a
1181 1181 $ echo 4 >> b
1182 1182 $ hg ci -m 'a,b'
1183 1183 $ echo 5 >> a
1184 1184 $ hg ci -m 'a0'
1185 1185 $ echo 6 >> b
1186 1186 $ hg ci -m 'b0'
1187 1187 $ hg up -q 4
1188 1188 $ echo 7 >> b
1189 1189 $ hg ci -m 'b1'
1190 1190 created new head
1191 1191 $ echo 8 >> a
1192 1192 $ hg ci -m 'a1'
1193 1193 $ hg rm a
1194 1194 $ hg mv b a
1195 1195 $ hg ci -m 'b1->a1'
1196 1196 $ hg merge -qt :local
1197 1197 $ hg ci -m '(a0,b1->a1)->a'
1198 1198
1199 1199 $ hg log -GT '{rev}: {desc}\n'
1200 1200 @ 10: (a0,b1->a1)->a
1201 1201 |\
1202 1202 | o 9: b1->a1
1203 1203 | |
1204 1204 | o 8: a1
1205 1205 | |
1206 1206 | o 7: b1
1207 1207 | |
1208 1208 o | 6: b0
1209 1209 | |
1210 1210 o | 5: a0
1211 1211 |/
1212 1212 o 4: a,b
1213 1213 |
1214 1214 o 3: b
1215 1215 |
1216 1216 o 2: a
1217 1217 |
1218 1218 o 1: a->b
1219 1219 |
1220 1220 o 0: a
1221 1221
1222 1222
1223 1223 since file 'a' has multiple copy sources at the revision 4, ancestors can't
1224 1224 be indexed solely by fctx.linkrev().
1225 1225
1226 1226 $ hg log -T '{rev}: {desc}\n' -f a
1227 1227 10: (a0,b1->a1)->a
1228 1228 9: b1->a1
1229 1229 7: b1
1230 1230 5: a0
1231 1231 4: a,b
1232 1232 3: b
1233 1233 2: a
1234 1234 1: a->b
1235 1235 0: a
1236 1236
1237 1237 $ cd ..
1238 1238
1239 1239 Test that log should respect the order of -rREV even if multiple OR conditions
1240 1240 are specified (issue5100):
1241 1241
1242 1242 $ hg init revorder
1243 1243 $ cd revorder
1244 1244
1245 1245 $ hg branch -q b0
1246 1246 $ echo 0 >> f0
1247 1247 $ hg ci -qAm k0 -u u0
1248 1248 $ hg branch -q b1
1249 1249 $ echo 1 >> f1
1250 1250 $ hg ci -qAm k1 -u u1
1251 1251 $ hg branch -q b2
1252 1252 $ echo 2 >> f2
1253 1253 $ hg ci -qAm k2 -u u2
1254 1254
1255 1255 $ hg update -q b2
1256 1256 $ echo 3 >> f2
1257 1257 $ hg ci -qAm k2 -u u2
1258 1258 $ hg update -q b1
1259 1259 $ echo 4 >> f1
1260 1260 $ hg ci -qAm k1 -u u1
1261 1261 $ hg update -q b0
1262 1262 $ echo 5 >> f0
1263 1263 $ hg ci -qAm k0 -u u0
1264 1264
1265 1265 summary of revisions:
1266 1266
1267 1267 $ hg log -G -T '{rev} {branch} {author} {desc} {files}\n'
1268 1268 @ 5 b0 u0 k0 f0
1269 1269 |
1270 1270 | o 4 b1 u1 k1 f1
1271 1271 | |
1272 1272 | | o 3 b2 u2 k2 f2
1273 1273 | | |
1274 1274 | | o 2 b2 u2 k2 f2
1275 1275 | |/
1276 1276 | o 1 b1 u1 k1 f1
1277 1277 |/
1278 1278 o 0 b0 u0 k0 f0
1279 1279
1280 1280
1281 1281 log -b BRANCH in ascending order:
1282 1282
1283 1283 $ hg log -r0:tip -T '{rev} {branch}\n' -b b0 -b b1
1284 1284 0 b0
1285 1285 1 b1
1286 1286 4 b1
1287 1287 5 b0
1288 1288 $ hg log -r0:tip -T '{rev} {branch}\n' -b b1 -b b0
1289 1289 0 b0
1290 1290 1 b1
1291 1291 4 b1
1292 1292 5 b0
1293 1293
1294 1294 log --only-branch BRANCH in descending order:
1295 1295
1296 1296 $ hg log -rtip:0 -T '{rev} {branch}\n' --only-branch b1 --only-branch b2
1297 1297 4 b1
1298 1298 3 b2
1299 1299 2 b2
1300 1300 1 b1
1301 1301 $ hg log -rtip:0 -T '{rev} {branch}\n' --only-branch b2 --only-branch b1
1302 1302 4 b1
1303 1303 3 b2
1304 1304 2 b2
1305 1305 1 b1
1306 1306
1307 1307 log -u USER in ascending order, against compound set:
1308 1308
1309 1309 $ hg log -r'::head()' -T '{rev} {author}\n' -u u0 -u u2
1310 1310 0 u0
1311 1311 2 u2
1312 1312 3 u2
1313 1313 5 u0
1314 1314 $ hg log -r'::head()' -T '{rev} {author}\n' -u u2 -u u0
1315 1315 0 u0
1316 1316 2 u2
1317 1317 3 u2
1318 1318 5 u0
1319 1319
1320 1320 log -k TEXT in descending order, against compound set:
1321 1321
1322 1322 $ hg log -r'5 + reverse(::3)' -T '{rev} {desc}\n' -k k0 -k k1 -k k2
1323 1323 5 k0
1324 1324 3 k2
1325 1325 2 k2
1326 1326 1 k1
1327 1327 0 k0
1328 1328 $ hg log -r'5 + reverse(::3)' -T '{rev} {desc}\n' -k k2 -k k1 -k k0
1329 1329 5 k0
1330 1330 3 k2
1331 1331 2 k2
1332 1332 1 k1
1333 1333 0 k0
1334 1334
1335 1335 log FILE in ascending order, against dagrange:
1336 1336
1337 1337 $ hg log -r1:: -T '{rev} {files}\n' f1 f2
1338 1338 1 f1
1339 1339 2 f2
1340 1340 3 f2
1341 1341 4 f1
1342 1342 $ hg log -r1:: -T '{rev} {files}\n' f2 f1
1343 1343 1 f1
1344 1344 2 f2
1345 1345 3 f2
1346 1346 4 f1
1347 1347
1348 1348 $ cd ..
1349 1349
1350 1350 User
1351 1351
1352 1352 $ hg init usertest
1353 1353 $ cd usertest
1354 1354
1355 1355 $ echo a > a
1356 1356 $ hg ci -A -m "a" -u "User One <user1@example.org>"
1357 1357 adding a
1358 1358 $ echo b > b
1359 1359 $ hg ci -A -m "b" -u "User Two <user2@example.org>"
1360 1360 adding b
1361 1361
1362 1362 $ hg log -u "User One <user1@example.org>"
1363 1363 changeset: 0:29a4c94f1924
1364 1364 user: User One <user1@example.org>
1365 1365 date: Thu Jan 01 00:00:00 1970 +0000
1366 1366 summary: a
1367 1367
1368 1368 $ hg log -u "user1" -u "user2"
1369 1369 changeset: 1:e834b5e69c0e
1370 1370 tag: tip
1371 1371 user: User Two <user2@example.org>
1372 1372 date: Thu Jan 01 00:00:00 1970 +0000
1373 1373 summary: b
1374 1374
1375 1375 changeset: 0:29a4c94f1924
1376 1376 user: User One <user1@example.org>
1377 1377 date: Thu Jan 01 00:00:00 1970 +0000
1378 1378 summary: a
1379 1379
1380 1380 $ hg log -u "user3"
1381 1381
1382 1382 "-u USER" shouldn't be overridden by "user(USER)" alias
1383 1383
1384 1384 $ hg log --config 'revsetalias.user(x)=branch(x)' -u default
1385 1385 $ hg log --config 'revsetalias.user(x)=branch(x)' -u user1
1386 1386 changeset: 0:29a4c94f1924
1387 1387 user: User One <user1@example.org>
1388 1388 date: Thu Jan 01 00:00:00 1970 +0000
1389 1389 summary: a
1390 1390
1391 1391
1392 1392 $ cd ..
1393 1393
1394 1394 $ hg init branches
1395 1395 $ cd branches
1396 1396
1397 1397 $ echo a > a
1398 1398 $ hg ci -A -m "commit on default"
1399 1399 adding a
1400 1400 $ hg branch test
1401 1401 marked working directory as branch test
1402 1402 (branches are permanent and global, did you want a bookmark?)
1403 1403 $ echo b > b
1404 1404 $ hg ci -A -m "commit on test"
1405 1405 adding b
1406 1406
1407 1407 $ hg up default
1408 1408 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1409 1409 $ echo c > c
1410 1410 $ hg ci -A -m "commit on default"
1411 1411 adding c
1412 1412 $ hg up test
1413 1413 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
1414 1414 $ echo c > c
1415 1415 $ hg ci -A -m "commit on test"
1416 1416 adding c
1417 1417
1418 1418
1419 1419 log -b default
1420 1420
1421 1421 $ hg log -b default
1422 1422 changeset: 2:c3a4f03cc9a7
1423 1423 parent: 0:24427303d56f
1424 1424 user: test
1425 1425 date: Thu Jan 01 00:00:00 1970 +0000
1426 1426 summary: commit on default
1427 1427
1428 1428 changeset: 0:24427303d56f
1429 1429 user: test
1430 1430 date: Thu Jan 01 00:00:00 1970 +0000
1431 1431 summary: commit on default
1432 1432
1433 1433
1434 1434
1435 1435 log -b test
1436 1436
1437 1437 $ hg log -b test
1438 1438 changeset: 3:f5d8de11c2e2
1439 1439 branch: test
1440 1440 tag: tip
1441 1441 parent: 1:d32277701ccb
1442 1442 user: test
1443 1443 date: Thu Jan 01 00:00:00 1970 +0000
1444 1444 summary: commit on test
1445 1445
1446 1446 changeset: 1:d32277701ccb
1447 1447 branch: test
1448 1448 user: test
1449 1449 date: Thu Jan 01 00:00:00 1970 +0000
1450 1450 summary: commit on test
1451 1451
1452 1452
1453 1453
1454 1454 log -b dummy
1455 1455
1456 1456 $ hg log -b dummy
1457 1457 abort: unknown revision 'dummy'!
1458 1458 [255]
1459 1459
1460 1460
1461 1461 log -b .
1462 1462
1463 1463 $ hg log -b .
1464 1464 changeset: 3:f5d8de11c2e2
1465 1465 branch: test
1466 1466 tag: tip
1467 1467 parent: 1:d32277701ccb
1468 1468 user: test
1469 1469 date: Thu Jan 01 00:00:00 1970 +0000
1470 1470 summary: commit on test
1471 1471
1472 1472 changeset: 1:d32277701ccb
1473 1473 branch: test
1474 1474 user: test
1475 1475 date: Thu Jan 01 00:00:00 1970 +0000
1476 1476 summary: commit on test
1477 1477
1478 1478
1479 1479
1480 1480 log -b default -b test
1481 1481
1482 1482 $ hg log -b default -b test
1483 1483 changeset: 3:f5d8de11c2e2
1484 1484 branch: test
1485 1485 tag: tip
1486 1486 parent: 1:d32277701ccb
1487 1487 user: test
1488 1488 date: Thu Jan 01 00:00:00 1970 +0000
1489 1489 summary: commit on test
1490 1490
1491 1491 changeset: 2:c3a4f03cc9a7
1492 1492 parent: 0:24427303d56f
1493 1493 user: test
1494 1494 date: Thu Jan 01 00:00:00 1970 +0000
1495 1495 summary: commit on default
1496 1496
1497 1497 changeset: 1:d32277701ccb
1498 1498 branch: test
1499 1499 user: test
1500 1500 date: Thu Jan 01 00:00:00 1970 +0000
1501 1501 summary: commit on test
1502 1502
1503 1503 changeset: 0:24427303d56f
1504 1504 user: test
1505 1505 date: Thu Jan 01 00:00:00 1970 +0000
1506 1506 summary: commit on default
1507 1507
1508 1508
1509 1509
1510 1510 log -b default -b .
1511 1511
1512 1512 $ hg log -b default -b .
1513 1513 changeset: 3:f5d8de11c2e2
1514 1514 branch: test
1515 1515 tag: tip
1516 1516 parent: 1:d32277701ccb
1517 1517 user: test
1518 1518 date: Thu Jan 01 00:00:00 1970 +0000
1519 1519 summary: commit on test
1520 1520
1521 1521 changeset: 2:c3a4f03cc9a7
1522 1522 parent: 0:24427303d56f
1523 1523 user: test
1524 1524 date: Thu Jan 01 00:00:00 1970 +0000
1525 1525 summary: commit on default
1526 1526
1527 1527 changeset: 1:d32277701ccb
1528 1528 branch: test
1529 1529 user: test
1530 1530 date: Thu Jan 01 00:00:00 1970 +0000
1531 1531 summary: commit on test
1532 1532
1533 1533 changeset: 0:24427303d56f
1534 1534 user: test
1535 1535 date: Thu Jan 01 00:00:00 1970 +0000
1536 1536 summary: commit on default
1537 1537
1538 1538
1539 1539
1540 1540 log -b . -b test
1541 1541
1542 1542 $ hg log -b . -b test
1543 1543 changeset: 3:f5d8de11c2e2
1544 1544 branch: test
1545 1545 tag: tip
1546 1546 parent: 1:d32277701ccb
1547 1547 user: test
1548 1548 date: Thu Jan 01 00:00:00 1970 +0000
1549 1549 summary: commit on test
1550 1550
1551 1551 changeset: 1:d32277701ccb
1552 1552 branch: test
1553 1553 user: test
1554 1554 date: Thu Jan 01 00:00:00 1970 +0000
1555 1555 summary: commit on test
1556 1556
1557 1557
1558 1558
1559 1559 log -b 2
1560 1560
1561 1561 $ hg log -b 2
1562 1562 changeset: 2:c3a4f03cc9a7
1563 1563 parent: 0:24427303d56f
1564 1564 user: test
1565 1565 date: Thu Jan 01 00:00:00 1970 +0000
1566 1566 summary: commit on default
1567 1567
1568 1568 changeset: 0:24427303d56f
1569 1569 user: test
1570 1570 date: Thu Jan 01 00:00:00 1970 +0000
1571 1571 summary: commit on default
1572 1572
1573 1573 #if gettext
1574 1574
1575 1575 Test that all log names are translated (e.g. branches, bookmarks, tags):
1576 1576
1577 1577 $ hg bookmark babar -r tip
1578 1578
1579 1579 $ HGENCODING=UTF-8 LANGUAGE=de hg log -r tip
1580 1580 \xc3\x84nderung: 3:f5d8de11c2e2 (esc)
1581 1581 Zweig: test
1582 1582 Lesezeichen: babar
1583 1583 Marke: tip
1584 1584 Vorg\xc3\xa4nger: 1:d32277701ccb (esc)
1585 1585 Nutzer: test
1586 1586 Datum: Thu Jan 01 00:00:00 1970 +0000
1587 1587 Zusammenfassung: commit on test
1588 1588
1589 1589 $ hg bookmark -d babar
1590 1590
1591 1591 #endif
1592 1592
1593 1593 log -p --cwd dir (in subdir)
1594 1594
1595 1595 $ mkdir dir
1596 1596 $ hg log -p --cwd dir
1597 1597 changeset: 3:f5d8de11c2e2
1598 1598 branch: test
1599 1599 tag: tip
1600 1600 parent: 1:d32277701ccb
1601 1601 user: test
1602 1602 date: Thu Jan 01 00:00:00 1970 +0000
1603 1603 summary: commit on test
1604 1604
1605 1605 diff -r d32277701ccb -r f5d8de11c2e2 c
1606 1606 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1607 1607 +++ b/c Thu Jan 01 00:00:00 1970 +0000
1608 1608 @@ -0,0 +1,1 @@
1609 1609 +c
1610 1610
1611 1611 changeset: 2:c3a4f03cc9a7
1612 1612 parent: 0:24427303d56f
1613 1613 user: test
1614 1614 date: Thu Jan 01 00:00:00 1970 +0000
1615 1615 summary: commit on default
1616 1616
1617 1617 diff -r 24427303d56f -r c3a4f03cc9a7 c
1618 1618 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1619 1619 +++ b/c Thu Jan 01 00:00:00 1970 +0000
1620 1620 @@ -0,0 +1,1 @@
1621 1621 +c
1622 1622
1623 1623 changeset: 1:d32277701ccb
1624 1624 branch: test
1625 1625 user: test
1626 1626 date: Thu Jan 01 00:00:00 1970 +0000
1627 1627 summary: commit on test
1628 1628
1629 1629 diff -r 24427303d56f -r d32277701ccb b
1630 1630 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1631 1631 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1632 1632 @@ -0,0 +1,1 @@
1633 1633 +b
1634 1634
1635 1635 changeset: 0:24427303d56f
1636 1636 user: test
1637 1637 date: Thu Jan 01 00:00:00 1970 +0000
1638 1638 summary: commit on default
1639 1639
1640 1640 diff -r 000000000000 -r 24427303d56f a
1641 1641 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1642 1642 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1643 1643 @@ -0,0 +1,1 @@
1644 1644 +a
1645 1645
1646 1646
1647 1647
1648 1648 log -p -R repo
1649 1649
1650 1650 $ cd dir
1651 1651 $ hg log -p -R .. ../a
1652 1652 changeset: 0:24427303d56f
1653 1653 user: test
1654 1654 date: Thu Jan 01 00:00:00 1970 +0000
1655 1655 summary: commit on default
1656 1656
1657 1657 diff -r 000000000000 -r 24427303d56f a
1658 1658 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1659 1659 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1660 1660 @@ -0,0 +1,1 @@
1661 1661 +a
1662 1662
1663 1663
1664 1664 $ cd ../..
1665 1665
1666 1666 $ hg init follow2
1667 1667 $ cd follow2
1668 1668
1669 1669 # Build the following history:
1670 1670 # tip - o - x - o - x - x
1671 1671 # \ /
1672 1672 # o - o - o - x
1673 1673 # \ /
1674 1674 # o
1675 1675 #
1676 1676 # Where "o" is a revision containing "foo" and
1677 1677 # "x" is a revision without "foo"
1678 1678
1679 1679 $ touch init
1680 1680 $ hg ci -A -m "init, unrelated"
1681 1681 adding init
1682 1682 $ echo 'foo' > init
1683 1683 $ hg ci -m "change, unrelated"
1684 1684 $ echo 'foo' > foo
1685 1685 $ hg ci -A -m "add unrelated old foo"
1686 1686 adding foo
1687 1687 $ hg rm foo
1688 1688 $ hg ci -m "delete foo, unrelated"
1689 1689 $ echo 'related' > foo
1690 1690 $ hg ci -A -m "add foo, related"
1691 1691 adding foo
1692 1692
1693 1693 $ hg up 0
1694 1694 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
1695 1695 $ touch branch
1696 1696 $ hg ci -A -m "first branch, unrelated"
1697 1697 adding branch
1698 1698 created new head
1699 1699 $ touch foo
1700 1700 $ hg ci -A -m "create foo, related"
1701 1701 adding foo
1702 1702 $ echo 'change' > foo
1703 1703 $ hg ci -m "change foo, related"
1704 1704
1705 1705 $ hg up 6
1706 1706 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1707 1707 $ echo 'change foo in branch' > foo
1708 1708 $ hg ci -m "change foo in branch, related"
1709 1709 created new head
1710 1710 $ hg merge 7
1711 1711 merging foo
1712 1712 warning: conflicts while merging foo! (edit, then use 'hg resolve --mark')
1713 1713 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1714 1714 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
1715 1715 [1]
1716 1716 $ echo 'merge 1' > foo
1717 1717 $ hg resolve -m foo
1718 1718 (no more unresolved files)
1719 1719 $ hg ci -m "First merge, related"
1720 1720
1721 1721 $ hg merge 4
1722 1722 merging foo
1723 1723 warning: conflicts while merging foo! (edit, then use 'hg resolve --mark')
1724 1724 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
1725 1725 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
1726 1726 [1]
1727 1727 $ echo 'merge 2' > foo
1728 1728 $ hg resolve -m foo
1729 1729 (no more unresolved files)
1730 1730 $ hg ci -m "Last merge, related"
1731 1731
1732 1732 $ hg log --graph
1733 1733 @ changeset: 10:4dae8563d2c5
1734 1734 |\ tag: tip
1735 1735 | | parent: 9:7b35701b003e
1736 1736 | | parent: 4:88176d361b69
1737 1737 | | user: test
1738 1738 | | date: Thu Jan 01 00:00:00 1970 +0000
1739 1739 | | summary: Last merge, related
1740 1740 | |
1741 1741 | o changeset: 9:7b35701b003e
1742 1742 | |\ parent: 8:e5416ad8a855
1743 1743 | | | parent: 7:87fe3144dcfa
1744 1744 | | | user: test
1745 1745 | | | date: Thu Jan 01 00:00:00 1970 +0000
1746 1746 | | | summary: First merge, related
1747 1747 | | |
1748 1748 | | o changeset: 8:e5416ad8a855
1749 1749 | | | parent: 6:dc6c325fe5ee
1750 1750 | | | user: test
1751 1751 | | | date: Thu Jan 01 00:00:00 1970 +0000
1752 1752 | | | summary: change foo in branch, related
1753 1753 | | |
1754 1754 | o | changeset: 7:87fe3144dcfa
1755 1755 | |/ user: test
1756 1756 | | date: Thu Jan 01 00:00:00 1970 +0000
1757 1757 | | summary: change foo, related
1758 1758 | |
1759 1759 | o changeset: 6:dc6c325fe5ee
1760 1760 | | user: test
1761 1761 | | date: Thu Jan 01 00:00:00 1970 +0000
1762 1762 | | summary: create foo, related
1763 1763 | |
1764 1764 | o changeset: 5:73db34516eb9
1765 1765 | | parent: 0:e87515fd044a
1766 1766 | | user: test
1767 1767 | | date: Thu Jan 01 00:00:00 1970 +0000
1768 1768 | | summary: first branch, unrelated
1769 1769 | |
1770 1770 o | changeset: 4:88176d361b69
1771 1771 | | user: test
1772 1772 | | date: Thu Jan 01 00:00:00 1970 +0000
1773 1773 | | summary: add foo, related
1774 1774 | |
1775 1775 o | changeset: 3:dd78ae4afb56
1776 1776 | | user: test
1777 1777 | | date: Thu Jan 01 00:00:00 1970 +0000
1778 1778 | | summary: delete foo, unrelated
1779 1779 | |
1780 1780 o | changeset: 2:c4c64aedf0f7
1781 1781 | | user: test
1782 1782 | | date: Thu Jan 01 00:00:00 1970 +0000
1783 1783 | | summary: add unrelated old foo
1784 1784 | |
1785 1785 o | changeset: 1:e5faa7440653
1786 1786 |/ user: test
1787 1787 | date: Thu Jan 01 00:00:00 1970 +0000
1788 1788 | summary: change, unrelated
1789 1789 |
1790 1790 o changeset: 0:e87515fd044a
1791 1791 user: test
1792 1792 date: Thu Jan 01 00:00:00 1970 +0000
1793 1793 summary: init, unrelated
1794 1794
1795 1795
1796 1796 $ hg --traceback log -f foo
1797 1797 changeset: 10:4dae8563d2c5
1798 1798 tag: tip
1799 1799 parent: 9:7b35701b003e
1800 1800 parent: 4:88176d361b69
1801 1801 user: test
1802 1802 date: Thu Jan 01 00:00:00 1970 +0000
1803 1803 summary: Last merge, related
1804 1804
1805 1805 changeset: 9:7b35701b003e
1806 1806 parent: 8:e5416ad8a855
1807 1807 parent: 7:87fe3144dcfa
1808 1808 user: test
1809 1809 date: Thu Jan 01 00:00:00 1970 +0000
1810 1810 summary: First merge, related
1811 1811
1812 1812 changeset: 8:e5416ad8a855
1813 1813 parent: 6:dc6c325fe5ee
1814 1814 user: test
1815 1815 date: Thu Jan 01 00:00:00 1970 +0000
1816 1816 summary: change foo in branch, related
1817 1817
1818 1818 changeset: 7:87fe3144dcfa
1819 1819 user: test
1820 1820 date: Thu Jan 01 00:00:00 1970 +0000
1821 1821 summary: change foo, related
1822 1822
1823 1823 changeset: 6:dc6c325fe5ee
1824 1824 user: test
1825 1825 date: Thu Jan 01 00:00:00 1970 +0000
1826 1826 summary: create foo, related
1827 1827
1828 1828 changeset: 4:88176d361b69
1829 1829 user: test
1830 1830 date: Thu Jan 01 00:00:00 1970 +0000
1831 1831 summary: add foo, related
1832 1832
1833 1833
1834 1834 Also check when maxrev < lastrevfilelog
1835 1835
1836 1836 $ hg --traceback log -f -r4 foo
1837 1837 changeset: 4:88176d361b69
1838 1838 user: test
1839 1839 date: Thu Jan 01 00:00:00 1970 +0000
1840 1840 summary: add foo, related
1841 1841
1842 1842 $ cd ..
1843 1843
1844 1844 Issue2383: hg log showing _less_ differences than hg diff
1845 1845
1846 1846 $ hg init issue2383
1847 1847 $ cd issue2383
1848 1848
1849 1849 Create a test repo:
1850 1850
1851 1851 $ echo a > a
1852 1852 $ hg ci -Am0
1853 1853 adding a
1854 1854 $ echo b > b
1855 1855 $ hg ci -Am1
1856 1856 adding b
1857 1857 $ hg co 0
1858 1858 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1859 1859 $ echo b > a
1860 1860 $ hg ci -m2
1861 1861 created new head
1862 1862
1863 1863 Merge:
1864 1864
1865 1865 $ hg merge
1866 1866 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1867 1867 (branch merge, don't forget to commit)
1868 1868
1869 1869 Make sure there's a file listed in the merge to trigger the bug:
1870 1870
1871 1871 $ echo c > a
1872 1872 $ hg ci -m3
1873 1873
1874 1874 Two files shown here in diff:
1875 1875
1876 1876 $ hg diff --rev 2:3
1877 1877 diff -r b09be438c43a -r 8e07aafe1edc a
1878 1878 --- a/a Thu Jan 01 00:00:00 1970 +0000
1879 1879 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1880 1880 @@ -1,1 +1,1 @@
1881 1881 -b
1882 1882 +c
1883 1883 diff -r b09be438c43a -r 8e07aafe1edc b
1884 1884 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1885 1885 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1886 1886 @@ -0,0 +1,1 @@
1887 1887 +b
1888 1888
1889 1889 Diff here should be the same:
1890 1890
1891 1891 $ hg log -vpr 3
1892 1892 changeset: 3:8e07aafe1edc
1893 1893 tag: tip
1894 1894 parent: 2:b09be438c43a
1895 1895 parent: 1:925d80f479bb
1896 1896 user: test
1897 1897 date: Thu Jan 01 00:00:00 1970 +0000
1898 1898 files: a
1899 1899 description:
1900 1900 3
1901 1901
1902 1902
1903 1903 diff -r b09be438c43a -r 8e07aafe1edc a
1904 1904 --- a/a Thu Jan 01 00:00:00 1970 +0000
1905 1905 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1906 1906 @@ -1,1 +1,1 @@
1907 1907 -b
1908 1908 +c
1909 1909 diff -r b09be438c43a -r 8e07aafe1edc b
1910 1910 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1911 1911 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1912 1912 @@ -0,0 +1,1 @@
1913 1913 +b
1914 1914
1915 1915 $ cd ..
1916 1916
1917 1917 'hg log -r rev fn' when last(filelog(fn)) != rev
1918 1918
1919 1919 $ hg init simplelog
1920 1920 $ cd simplelog
1921 1921 $ echo f > a
1922 1922 $ hg ci -Am'a' -d '0 0'
1923 1923 adding a
1924 1924 $ echo f >> a
1925 1925 $ hg ci -Am'a bis' -d '1 0'
1926 1926
1927 1927 $ hg log -r0 a
1928 1928 changeset: 0:9f758d63dcde
1929 1929 user: test
1930 1930 date: Thu Jan 01 00:00:00 1970 +0000
1931 1931 summary: a
1932 1932
1933 1933 enable obsolete to test hidden feature
1934 1934
1935 1935 $ cat >> $HGRCPATH << EOF
1936 1936 > [experimental]
1937 1937 > evolution.createmarkers=True
1938 1938 > EOF
1939 1939
1940 1940 $ hg log --template='{rev}:{node}\n'
1941 1941 1:a765632148dc55d38c35c4f247c618701886cb2f
1942 1942 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1943 1943 $ hg debugobsolete a765632148dc55d38c35c4f247c618701886cb2f
1944 1944 1 new obsolescence markers
1945 1945 obsoleted 1 changesets
1946 1946 $ hg up null -q
1947 1947 $ hg log --template='{rev}:{node}\n'
1948 1948 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1949 1949 $ hg log --template='{rev}:{node}\n' --hidden
1950 1950 1:a765632148dc55d38c35c4f247c618701886cb2f
1951 1951 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1952 1952 $ hg log -r a
1953 1953 abort: hidden revision 'a' is pruned!
1954 1954 (use --hidden to access hidden revisions)
1955 1955 [255]
1956 1956
1957 1957 test that parent prevent a changeset to be hidden
1958 1958
1959 1959 $ hg up 1 -q --hidden
1960 1960 updated to hidden changeset a765632148dc
1961 1961 (hidden revision 'a765632148dc' is pruned)
1962 1962 $ hg log --template='{rev}:{node}\n'
1963 1963 1:a765632148dc55d38c35c4f247c618701886cb2f
1964 1964 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1965 1965
1966 1966 test that second parent prevent a changeset to be hidden too
1967 1967
1968 1968 $ hg debugsetparents 0 1 # nothing suitable to merge here
1969 1969 $ hg log --template='{rev}:{node}\n'
1970 1970 1:a765632148dc55d38c35c4f247c618701886cb2f
1971 1971 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1972 1972 $ hg debugsetparents 1
1973 1973 $ hg up -q null
1974 1974
1975 1975 bookmarks prevent a changeset being hidden
1976 1976
1977 1977 $ hg bookmark --hidden -r 1 X
1978 1978 bookmarking hidden changeset a765632148dc
1979 1979 (hidden revision 'a765632148dc' is pruned)
1980 1980 $ hg log --template '{rev}:{node}\n'
1981 1981 1:a765632148dc55d38c35c4f247c618701886cb2f
1982 1982 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1983 1983 $ hg bookmark -d X
1984 1984
1985 1985 divergent bookmarks are not hidden
1986 1986
1987 1987 $ hg bookmark --hidden -r 1 X@foo
1988 1988 bookmarking hidden changeset a765632148dc
1989 1989 (hidden revision 'a765632148dc' is pruned)
1990 1990 $ hg log --template '{rev}:{node}\n'
1991 1991 1:a765632148dc55d38c35c4f247c618701886cb2f
1992 1992 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1993 1993
1994 1994 test hidden revision 0 (issue5385)
1995 1995
1996 1996 $ hg bookmark -d X@foo
1997 1997 $ hg up null -q
1998 1998 $ hg debugobsolete 9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1999 1999 1 new obsolescence markers
2000 2000 obsoleted 1 changesets
2001 2001 $ echo f > b
2002 2002 $ hg ci -Am'b' -d '2 0'
2003 2003 adding b
2004 2004 $ echo f >> b
2005 2005 $ hg ci -m'b bis' -d '3 0'
2006 2006 $ hg log -T'{rev}:{node}\n'
2007 2007 3:d7d28b288a6b83d5d2cf49f10c5974deed3a1d2e
2008 2008 2:94375ec45bddd2a824535fc04855bd058c926ec0
2009 2009
2010 2010 $ hg log -T'{rev}:{node}\n' -r:
2011 2011 2:94375ec45bddd2a824535fc04855bd058c926ec0
2012 2012 3:d7d28b288a6b83d5d2cf49f10c5974deed3a1d2e
2013 2013 $ hg log -T'{rev}:{node}\n' -r:tip
2014 2014 2:94375ec45bddd2a824535fc04855bd058c926ec0
2015 2015 3:d7d28b288a6b83d5d2cf49f10c5974deed3a1d2e
2016 2016 $ hg log -T'{rev}:{node}\n' -r:0
2017 2017 abort: hidden revision '0' is pruned!
2018 2018 (use --hidden to access hidden revisions)
2019 2019 [255]
2020 2020 $ hg log -T'{rev}:{node}\n' -f
2021 2021 3:d7d28b288a6b83d5d2cf49f10c5974deed3a1d2e
2022 2022 2:94375ec45bddd2a824535fc04855bd058c926ec0
2023 2023
2024 2024 clear extensions configuration
2025 2025 $ echo '[extensions]' >> $HGRCPATH
2026 2026 $ echo "obs=!" >> $HGRCPATH
2027 2027 $ cd ..
2028 2028
2029 2029 test -u/-k for problematic encoding
2030 2030 # unicode: cp932:
2031 2031 # u30A2 0x83 0x41(= 'A')
2032 2032 # u30C2 0x83 0x61(= 'a')
2033 2033
2034 2034 $ hg init problematicencoding
2035 2035 $ cd problematicencoding
2036 2036
2037 2037 >>> with open('setup.sh', 'wb') as f:
2038 2038 ... f.write(u'''
2039 2039 ... echo a > text
2040 2040 ... hg add text
2041 2041 ... hg --encoding utf-8 commit -u '\u30A2' -m none
2042 2042 ... echo b > text
2043 2043 ... hg --encoding utf-8 commit -u '\u30C2' -m none
2044 2044 ... echo c > text
2045 2045 ... hg --encoding utf-8 commit -u none -m '\u30A2'
2046 2046 ... echo d > text
2047 2047 ... hg --encoding utf-8 commit -u none -m '\u30C2'
2048 2048 ... '''.encode('utf-8')) and None
2049 2049 $ sh < setup.sh
2050 2050
2051 2051 test in problematic encoding
2052 2052 >>> with open('test.sh', 'wb') as f:
2053 2053 ... f.write(u'''
2054 2054 ... hg --encoding cp932 log --template '{rev}\\n' -u '\u30A2'
2055 2055 ... echo ====
2056 2056 ... hg --encoding cp932 log --template '{rev}\\n' -u '\u30C2'
2057 2057 ... echo ====
2058 2058 ... hg --encoding cp932 log --template '{rev}\\n' -k '\u30A2'
2059 2059 ... echo ====
2060 2060 ... hg --encoding cp932 log --template '{rev}\\n' -k '\u30C2'
2061 2061 ... '''.encode('cp932')) and None
2062 2062 $ sh < test.sh
2063 2063 0
2064 2064 ====
2065 2065 1
2066 2066 ====
2067 2067 2
2068 2068 0
2069 2069 ====
2070 2070 3
2071 2071 1
2072 2072
2073 2073 $ cd ..
2074 2074
2075 2075 test hg log on non-existent files and on directories
2076 2076 $ hg init issue1340
2077 2077 $ cd issue1340
2078 2078 $ mkdir d1; mkdir D2; mkdir D3.i; mkdir d4.hg; mkdir d5.d; mkdir .d6
2079 2079 $ echo 1 > d1/f1
2080 2080 $ echo 1 > D2/f1
2081 2081 $ echo 1 > D3.i/f1
2082 2082 $ echo 1 > d4.hg/f1
2083 2083 $ echo 1 > d5.d/f1
2084 2084 $ echo 1 > .d6/f1
2085 2085 $ hg -q add .
2086 2086 $ hg commit -m "a bunch of weird directories"
2087 2087 $ hg log -l1 d1/f1 | grep changeset
2088 2088 changeset: 0:65624cd9070a
2089 2089 $ hg log -l1 f1
2090 2090 $ hg log -l1 . | grep changeset
2091 2091 changeset: 0:65624cd9070a
2092 2092 $ hg log -l1 ./ | grep changeset
2093 2093 changeset: 0:65624cd9070a
2094 2094 $ hg log -l1 d1 | grep changeset
2095 2095 changeset: 0:65624cd9070a
2096 2096 $ hg log -l1 D2 | grep changeset
2097 2097 changeset: 0:65624cd9070a
2098 2098 $ hg log -l1 D2/f1 | grep changeset
2099 2099 changeset: 0:65624cd9070a
2100 2100 $ hg log -l1 D3.i | grep changeset
2101 2101 changeset: 0:65624cd9070a
2102 2102 $ hg log -l1 D3.i/f1 | grep changeset
2103 2103 changeset: 0:65624cd9070a
2104 2104 $ hg log -l1 d4.hg | grep changeset
2105 2105 changeset: 0:65624cd9070a
2106 2106 $ hg log -l1 d4.hg/f1 | grep changeset
2107 2107 changeset: 0:65624cd9070a
2108 2108 $ hg log -l1 d5.d | grep changeset
2109 2109 changeset: 0:65624cd9070a
2110 2110 $ hg log -l1 d5.d/f1 | grep changeset
2111 2111 changeset: 0:65624cd9070a
2112 2112 $ hg log -l1 .d6 | grep changeset
2113 2113 changeset: 0:65624cd9070a
2114 2114 $ hg log -l1 .d6/f1 | grep changeset
2115 2115 changeset: 0:65624cd9070a
2116 2116
2117 2117 issue3772: hg log -r :null showing revision 0 as well
2118 2118
2119 2119 $ hg log -r :null
2120 2120 changeset: 0:65624cd9070a
2121 2121 tag: tip
2122 2122 user: test
2123 2123 date: Thu Jan 01 00:00:00 1970 +0000
2124 2124 summary: a bunch of weird directories
2125 2125
2126 2126 changeset: -1:000000000000
2127 2127 user:
2128 2128 date: Thu Jan 01 00:00:00 1970 +0000
2129 2129
2130 2130 $ hg log -r null:null
2131 2131 changeset: -1:000000000000
2132 2132 user:
2133 2133 date: Thu Jan 01 00:00:00 1970 +0000
2134 2134
2135 2135 working-directory revision requires special treatment
2136 2136
2137 2137 clean:
2138 2138
2139 2139 $ hg log -r 'wdir()' --debug
2140 2140 changeset: 2147483647:ffffffffffffffffffffffffffffffffffffffff
2141 2141 phase: draft
2142 2142 parent: 0:65624cd9070a035fa7191a54f2b8af39f16b0c08
2143 2143 parent: -1:0000000000000000000000000000000000000000
2144 2144 manifest: 2147483647:ffffffffffffffffffffffffffffffffffffffff
2145 2145 user: test
2146 2146 date: [A-Za-z0-9:+ ]+ (re)
2147 2147 extra: branch=default
2148 2148
2149 2149 $ hg log -r 'wdir()' -p --stat
2150 2150 changeset: 2147483647:ffffffffffff
2151 2151 parent: 0:65624cd9070a
2152 2152 user: test
2153 2153 date: [A-Za-z0-9:+ ]+ (re)
2154 2154
2155 2155
2156 2156
2157 2157
2158 2158 dirty:
2159 2159
2160 2160 $ echo 2 >> d1/f1
2161 2161 $ echo 2 > d1/f2
2162 2162 $ hg add d1/f2
2163 2163 $ hg remove .d6/f1
2164 2164 $ hg status
2165 2165 M d1/f1
2166 2166 A d1/f2
2167 2167 R .d6/f1
2168 2168
2169 2169 $ hg log -r 'wdir()'
2170 2170 changeset: 2147483647:ffffffffffff
2171 2171 parent: 0:65624cd9070a
2172 2172 user: test
2173 2173 date: [A-Za-z0-9:+ ]+ (re)
2174 2174
2175 2175 $ hg log -r 'wdir()' -q
2176 2176 2147483647:ffffffffffff
2177 2177
2178 2178 $ hg log -r 'wdir()' --debug
2179 2179 changeset: 2147483647:ffffffffffffffffffffffffffffffffffffffff
2180 2180 phase: draft
2181 2181 parent: 0:65624cd9070a035fa7191a54f2b8af39f16b0c08
2182 2182 parent: -1:0000000000000000000000000000000000000000
2183 2183 manifest: 2147483647:ffffffffffffffffffffffffffffffffffffffff
2184 2184 user: test
2185 2185 date: [A-Za-z0-9:+ ]+ (re)
2186 2186 files: d1/f1
2187 2187 files+: d1/f2
2188 2188 files-: .d6/f1
2189 2189 extra: branch=default
2190 2190
2191 2191 $ hg log -r 'wdir()' -p --stat --git
2192 2192 changeset: 2147483647:ffffffffffff
2193 2193 parent: 0:65624cd9070a
2194 2194 user: test
2195 2195 date: [A-Za-z0-9:+ ]+ (re)
2196 2196
2197 2197 .d6/f1 | 1 -
2198 2198 d1/f1 | 1 +
2199 2199 d1/f2 | 1 +
2200 2200 3 files changed, 2 insertions(+), 1 deletions(-)
2201 2201
2202 2202 diff --git a/.d6/f1 b/.d6/f1
2203 2203 deleted file mode 100644
2204 2204 --- a/.d6/f1
2205 2205 +++ /dev/null
2206 2206 @@ -1,1 +0,0 @@
2207 2207 -1
2208 2208 diff --git a/d1/f1 b/d1/f1
2209 2209 --- a/d1/f1
2210 2210 +++ b/d1/f1
2211 2211 @@ -1,1 +1,2 @@
2212 2212 1
2213 2213 +2
2214 2214 diff --git a/d1/f2 b/d1/f2
2215 2215 new file mode 100644
2216 2216 --- /dev/null
2217 2217 +++ b/d1/f2
2218 2218 @@ -0,0 +1,1 @@
2219 2219 +2
2220 2220
2221 2221 $ hg log -r 'wdir()' -Tjson
2222 2222 [
2223 2223 {
2224 2224 "bookmarks": [],
2225 2225 "branch": "default",
2226 2226 "date": [*, 0], (glob)
2227 2227 "desc": "",
2228 2228 "node": "ffffffffffffffffffffffffffffffffffffffff",
2229 2229 "parents": ["65624cd9070a035fa7191a54f2b8af39f16b0c08"],
2230 2230 "phase": "draft",
2231 2231 "rev": 2147483647,
2232 2232 "tags": [],
2233 2233 "user": "test"
2234 2234 }
2235 2235 ]
2236 2236
2237 2237 $ hg log -r 'wdir()' -Tjson -q
2238 2238 [
2239 2239 {
2240 2240 "node": "ffffffffffffffffffffffffffffffffffffffff",
2241 2241 "rev": 2147483647
2242 2242 }
2243 2243 ]
2244 2244
2245 2245 $ hg log -r 'wdir()' -Tjson --debug
2246 2246 [
2247 2247 {
2248 2248 "added": ["d1/f2"],
2249 2249 "bookmarks": [],
2250 2250 "branch": "default",
2251 2251 "date": [*, 0], (glob)
2252 2252 "desc": "",
2253 2253 "extra": {"branch": "default"},
2254 2254 "manifest": "ffffffffffffffffffffffffffffffffffffffff",
2255 2255 "modified": ["d1/f1"],
2256 2256 "node": "ffffffffffffffffffffffffffffffffffffffff",
2257 2257 "parents": ["65624cd9070a035fa7191a54f2b8af39f16b0c08"],
2258 2258 "phase": "draft",
2259 2259 "removed": [".d6/f1"],
2260 2260 "rev": 2147483647,
2261 2261 "tags": [],
2262 2262 "user": "test"
2263 2263 }
2264 2264 ]
2265 2265
2266 2266 $ hg revert -aqC
2267 2267
2268 2268 Check that adding an arbitrary name shows up in log automatically
2269 2269
2270 2270 $ cat > ../names.py <<EOF
2271 2271 > """A small extension to test adding arbitrary names to a repo"""
2272 2272 > from __future__ import absolute_import
2273 2273 > from mercurial import namespaces
2274 2274 >
2275 2275 > def reposetup(ui, repo):
2276 2276 > if not repo.local():
2277 2277 > return
2278 2278 > foo = {b'foo': repo[0].node()}
2279 2279 > names = lambda r: foo.keys()
2280 2280 > namemap = lambda r, name: foo.get(name)
2281 2281 > nodemap = lambda r, node: [name for name, n in foo.items()
2282 2282 > if n == node]
2283 2283 > ns = namespaces.namespace(
2284 2284 > b"bars", templatename=b"bar", logname=b"barlog",
2285 2285 > colorname=b"barcolor", listnames=names, namemap=namemap,
2286 2286 > nodemap=nodemap)
2287 2287 >
2288 2288 > repo.names.addnamespace(ns)
2289 2289 > EOF
2290 2290
2291 2291 $ hg --config extensions.names=../names.py log -r 0
2292 2292 changeset: 0:65624cd9070a
2293 2293 tag: tip
2294 2294 barlog: foo
2295 2295 user: test
2296 2296 date: Thu Jan 01 00:00:00 1970 +0000
2297 2297 summary: a bunch of weird directories
2298 2298
2299 2299 $ hg --config extensions.names=../names.py \
2300 2300 > --config extensions.color= --config color.log.barcolor=red \
2301 2301 > --color=always log -r 0
2302 2302 \x1b[0;33mchangeset: 0:65624cd9070a\x1b[0m (esc)
2303 2303 tag: tip
2304 2304 \x1b[0;31mbarlog: foo\x1b[0m (esc)
2305 2305 user: test
2306 2306 date: Thu Jan 01 00:00:00 1970 +0000
2307 2307 summary: a bunch of weird directories
2308 2308
2309 2309 $ hg --config extensions.names=../names.py log -r 0 --template '{bars}\n'
2310 2310 foo
2311 2311
2312 2312 Templater parse errors:
2313 2313
2314 2314 simple error
2315 2315 $ hg log -r . -T '{shortest(node}'
2316 2316 hg: parse error at 14: unexpected token: end
2317 2317 ({shortest(node}
2318 2318 ^ here)
2319 2319 [255]
2320 2320
2321 2321 multi-line template with error
2322 2322 $ hg log -r . -T 'line 1
2323 2323 > line2
2324 2324 > {shortest(node}
2325 2325 > line4\nline5'
2326 2326 hg: parse error at 27: unexpected token: end
2327 2327 (line 1\nline2\n{shortest(node}\nline4\nline5
2328 2328 ^ here)
2329 2329 [255]
2330 2330
2331 2331 $ cd ..
2332 2332
2333 2333 New namespace is registered per repo instance, but the template keyword
2334 2334 is global. So we shouldn't expect the namespace always exists. Using
2335 2335 ssh:// makes sure a bundle repository is created from scratch. (issue6301)
2336 2336
2337 $ hg clone -e "'$PYTHON' '$TESTDIR/dummyssh'" \
2337 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" \
2338 2338 > -qr0 "ssh://user@dummy/`pwd`/a" a-clone
2339 2339 $ hg incoming --config extensions.names=names.py -R a-clone \
2340 > -e "'$PYTHON' '$TESTDIR/dummyssh'" -T '{bars}\n' -l1
2340 > -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" -T '{bars}\n' -l1
2341 2341 comparing with ssh://user@dummy/$TESTTMP/a
2342 2342 searching for changes
2343 2343
2344 2344
2345 2345 hg log -f dir across branches
2346 2346
2347 2347 $ hg init acrossbranches
2348 2348 $ cd acrossbranches
2349 2349 $ mkdir d
2350 2350 $ echo a > d/a && hg ci -Aqm a
2351 2351 $ echo b > d/a && hg ci -Aqm b
2352 2352 $ hg up -q 0
2353 2353 $ echo b > d/a && hg ci -Aqm c
2354 2354 $ hg log -f d -T '{desc}' -G
2355 2355 @ c
2356 2356 |
2357 2357 o a
2358 2358
2359 2359 Ensure that largefiles doesn't interfere with following a normal file
2360 2360 $ hg --config extensions.largefiles= log -f d -T '{desc}' -G
2361 2361 The fsmonitor extension is incompatible with the largefiles extension and has been disabled. (fsmonitor !)
2362 2362 @ c
2363 2363 |
2364 2364 o a
2365 2365
2366 2366 $ hg log -f d/a -T '{desc}' -G
2367 2367 @ c
2368 2368 |
2369 2369 o a
2370 2370
2371 2371 $ cd ..
2372 2372
2373 2373 hg log -f with linkrev pointing to another branch
2374 2374 -------------------------------------------------
2375 2375
2376 2376 create history with a filerev whose linkrev points to another branch
2377 2377
2378 2378 $ hg init branchedlinkrev
2379 2379 $ cd branchedlinkrev
2380 2380 $ echo 1 > a
2381 2381 $ hg commit -Am 'content1'
2382 2382 adding a
2383 2383 $ echo 2 > a
2384 2384 $ hg commit -m 'content2'
2385 2385 $ hg up --rev 'desc(content1)'
2386 2386 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
2387 2387 $ echo unrelated > unrelated
2388 2388 $ hg commit -Am 'unrelated'
2389 2389 adding unrelated
2390 2390 created new head
2391 2391 $ hg graft -r 'desc(content2)'
2392 2392 grafting 1:2294ae80ad84 "content2"
2393 2393 $ echo 3 > a
2394 2394 $ hg commit -m 'content3'
2395 2395 $ hg log -G
2396 2396 @ changeset: 4:50b9b36e9c5d
2397 2397 | tag: tip
2398 2398 | user: test
2399 2399 | date: Thu Jan 01 00:00:00 1970 +0000
2400 2400 | summary: content3
2401 2401 |
2402 2402 o changeset: 3:15b2327059e5
2403 2403 | user: test
2404 2404 | date: Thu Jan 01 00:00:00 1970 +0000
2405 2405 | summary: content2
2406 2406 |
2407 2407 o changeset: 2:2029acd1168c
2408 2408 | parent: 0:ae0a3c9f9e95
2409 2409 | user: test
2410 2410 | date: Thu Jan 01 00:00:00 1970 +0000
2411 2411 | summary: unrelated
2412 2412 |
2413 2413 | o changeset: 1:2294ae80ad84
2414 2414 |/ user: test
2415 2415 | date: Thu Jan 01 00:00:00 1970 +0000
2416 2416 | summary: content2
2417 2417 |
2418 2418 o changeset: 0:ae0a3c9f9e95
2419 2419 user: test
2420 2420 date: Thu Jan 01 00:00:00 1970 +0000
2421 2421 summary: content1
2422 2422
2423 2423
2424 2424 log -f on the file should list the graft result.
2425 2425
2426 2426 $ hg log -Gf a
2427 2427 @ changeset: 4:50b9b36e9c5d
2428 2428 | tag: tip
2429 2429 | user: test
2430 2430 | date: Thu Jan 01 00:00:00 1970 +0000
2431 2431 | summary: content3
2432 2432 |
2433 2433 o changeset: 3:15b2327059e5
2434 2434 : user: test
2435 2435 : date: Thu Jan 01 00:00:00 1970 +0000
2436 2436 : summary: content2
2437 2437 :
2438 2438 o changeset: 0:ae0a3c9f9e95
2439 2439 user: test
2440 2440 date: Thu Jan 01 00:00:00 1970 +0000
2441 2441 summary: content1
2442 2442
2443 2443
2444 2444 plain log lists the original version
2445 2445 (XXX we should probably list both)
2446 2446
2447 2447 $ hg log -G a
2448 2448 @ changeset: 4:50b9b36e9c5d
2449 2449 : tag: tip
2450 2450 : user: test
2451 2451 : date: Thu Jan 01 00:00:00 1970 +0000
2452 2452 : summary: content3
2453 2453 :
2454 2454 : o changeset: 1:2294ae80ad84
2455 2455 :/ user: test
2456 2456 : date: Thu Jan 01 00:00:00 1970 +0000
2457 2457 : summary: content2
2458 2458 :
2459 2459 o changeset: 0:ae0a3c9f9e95
2460 2460 user: test
2461 2461 date: Thu Jan 01 00:00:00 1970 +0000
2462 2462 summary: content1
2463 2463
2464 2464
2465 2465 hg log -f from the grafted changeset
2466 2466 (The bootstrap should properly take the topology in account)
2467 2467
2468 2468 $ hg up 'desc(content3)^'
2469 2469 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
2470 2470 $ hg log -Gf a
2471 2471 @ changeset: 3:15b2327059e5
2472 2472 : user: test
2473 2473 : date: Thu Jan 01 00:00:00 1970 +0000
2474 2474 : summary: content2
2475 2475 :
2476 2476 o changeset: 0:ae0a3c9f9e95
2477 2477 user: test
2478 2478 date: Thu Jan 01 00:00:00 1970 +0000
2479 2479 summary: content1
2480 2480
2481 2481
2482 2482 Test that we use the first non-hidden changeset in that case.
2483 2483
2484 2484 (hide the changeset)
2485 2485
2486 2486 $ hg log -T '{node}\n' -r 1
2487 2487 2294ae80ad8447bc78383182eeac50cb049df623
2488 2488 $ hg debugobsolete 2294ae80ad8447bc78383182eeac50cb049df623
2489 2489 1 new obsolescence markers
2490 2490 obsoleted 1 changesets
2491 2491 $ hg log -G
2492 2492 o changeset: 4:50b9b36e9c5d
2493 2493 | tag: tip
2494 2494 | user: test
2495 2495 | date: Thu Jan 01 00:00:00 1970 +0000
2496 2496 | summary: content3
2497 2497 |
2498 2498 @ changeset: 3:15b2327059e5
2499 2499 | user: test
2500 2500 | date: Thu Jan 01 00:00:00 1970 +0000
2501 2501 | summary: content2
2502 2502 |
2503 2503 o changeset: 2:2029acd1168c
2504 2504 | parent: 0:ae0a3c9f9e95
2505 2505 | user: test
2506 2506 | date: Thu Jan 01 00:00:00 1970 +0000
2507 2507 | summary: unrelated
2508 2508 |
2509 2509 o changeset: 0:ae0a3c9f9e95
2510 2510 user: test
2511 2511 date: Thu Jan 01 00:00:00 1970 +0000
2512 2512 summary: content1
2513 2513
2514 2514
2515 2515 Check that log on the file does not drop the file revision.
2516 2516
2517 2517 $ hg log -G a
2518 2518 o changeset: 4:50b9b36e9c5d
2519 2519 | tag: tip
2520 2520 | user: test
2521 2521 | date: Thu Jan 01 00:00:00 1970 +0000
2522 2522 | summary: content3
2523 2523 |
2524 2524 @ changeset: 3:15b2327059e5
2525 2525 : user: test
2526 2526 : date: Thu Jan 01 00:00:00 1970 +0000
2527 2527 : summary: content2
2528 2528 :
2529 2529 o changeset: 0:ae0a3c9f9e95
2530 2530 user: test
2531 2531 date: Thu Jan 01 00:00:00 1970 +0000
2532 2532 summary: content1
2533 2533
2534 2534
2535 2535 Even when a head revision is linkrev-shadowed.
2536 2536
2537 2537 $ hg log -T '{node}\n' -r 4
2538 2538 50b9b36e9c5df2c6fc6dcefa8ad0da929e84aed2
2539 2539 $ hg debugobsolete 50b9b36e9c5df2c6fc6dcefa8ad0da929e84aed2
2540 2540 1 new obsolescence markers
2541 2541 obsoleted 1 changesets
2542 2542 $ hg log -G a
2543 2543 @ changeset: 3:15b2327059e5
2544 2544 : tag: tip
2545 2545 : user: test
2546 2546 : date: Thu Jan 01 00:00:00 1970 +0000
2547 2547 : summary: content2
2548 2548 :
2549 2549 o changeset: 0:ae0a3c9f9e95
2550 2550 user: test
2551 2551 date: Thu Jan 01 00:00:00 1970 +0000
2552 2552 summary: content1
2553 2553
2554 2554
2555 2555 $ cd ..
2556 2556
2557 2557 Even when the file revision is missing from some head:
2558 2558
2559 2559 $ hg init issue4490
2560 2560 $ cd issue4490
2561 2561 $ echo '[experimental]' >> .hg/hgrc
2562 2562 $ echo 'evolution.createmarkers=True' >> .hg/hgrc
2563 2563 $ echo a > a
2564 2564 $ hg ci -Am0
2565 2565 adding a
2566 2566 $ echo b > b
2567 2567 $ hg ci -Am1
2568 2568 adding b
2569 2569 $ echo B > b
2570 2570 $ hg ci --amend -m 1
2571 2571 $ hg up 0
2572 2572 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
2573 2573 $ echo c > c
2574 2574 $ hg ci -Am2
2575 2575 adding c
2576 2576 created new head
2577 2577 $ hg up 'head() and not .'
2578 2578 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
2579 2579 $ hg log -G
2580 2580 o changeset: 3:db815d6d32e6
2581 2581 | tag: tip
2582 2582 | parent: 0:f7b1eb17ad24
2583 2583 | user: test
2584 2584 | date: Thu Jan 01 00:00:00 1970 +0000
2585 2585 | summary: 2
2586 2586 |
2587 2587 | @ changeset: 2:9bc8ce7f9356
2588 2588 |/ parent: 0:f7b1eb17ad24
2589 2589 | user: test
2590 2590 | date: Thu Jan 01 00:00:00 1970 +0000
2591 2591 | summary: 1
2592 2592 |
2593 2593 o changeset: 0:f7b1eb17ad24
2594 2594 user: test
2595 2595 date: Thu Jan 01 00:00:00 1970 +0000
2596 2596 summary: 0
2597 2597
2598 2598 $ hg log -f -G b
2599 2599 @ changeset: 2:9bc8ce7f9356
2600 2600 | parent: 0:f7b1eb17ad24
2601 2601 ~ user: test
2602 2602 date: Thu Jan 01 00:00:00 1970 +0000
2603 2603 summary: 1
2604 2604
2605 2605 $ hg log -G b
2606 2606 @ changeset: 2:9bc8ce7f9356
2607 2607 | parent: 0:f7b1eb17ad24
2608 2608 ~ user: test
2609 2609 date: Thu Jan 01 00:00:00 1970 +0000
2610 2610 summary: 1
2611 2611
2612 2612 $ cd ..
2613 2613
2614 2614 Check proper report when the manifest changes but not the file issue4499
2615 2615 ------------------------------------------------------------------------
2616 2616
2617 2617 $ hg init issue4499
2618 2618 $ cd issue4499
2619 2619 $ for f in A B C D F E G H I J K L M N O P Q R S T U; do
2620 2620 > echo 1 > $f;
2621 2621 > hg add $f;
2622 2622 > done
2623 2623 $ hg commit -m 'A1B1C1'
2624 2624 $ echo 2 > A
2625 2625 $ echo 2 > B
2626 2626 $ echo 2 > C
2627 2627 $ hg commit -m 'A2B2C2'
2628 2628 $ hg up 0
2629 2629 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
2630 2630 $ echo 3 > A
2631 2631 $ echo 2 > B
2632 2632 $ echo 2 > C
2633 2633 $ hg commit -m 'A3B2C2'
2634 2634 created new head
2635 2635
2636 2636 $ hg log -G
2637 2637 @ changeset: 2:fe5fc3d0eb17
2638 2638 | tag: tip
2639 2639 | parent: 0:abf4f0e38563
2640 2640 | user: test
2641 2641 | date: Thu Jan 01 00:00:00 1970 +0000
2642 2642 | summary: A3B2C2
2643 2643 |
2644 2644 | o changeset: 1:07dcc6b312c0
2645 2645 |/ user: test
2646 2646 | date: Thu Jan 01 00:00:00 1970 +0000
2647 2647 | summary: A2B2C2
2648 2648 |
2649 2649 o changeset: 0:abf4f0e38563
2650 2650 user: test
2651 2651 date: Thu Jan 01 00:00:00 1970 +0000
2652 2652 summary: A1B1C1
2653 2653
2654 2654
2655 2655 Log -f on B should reports current changesets
2656 2656
2657 2657 $ hg log -fG B
2658 2658 @ changeset: 2:fe5fc3d0eb17
2659 2659 | tag: tip
2660 2660 | parent: 0:abf4f0e38563
2661 2661 | user: test
2662 2662 | date: Thu Jan 01 00:00:00 1970 +0000
2663 2663 | summary: A3B2C2
2664 2664 |
2665 2665 o changeset: 0:abf4f0e38563
2666 2666 user: test
2667 2667 date: Thu Jan 01 00:00:00 1970 +0000
2668 2668 summary: A1B1C1
2669 2669
2670 2670 $ cd ..
2671 2671
2672 2672 --- going to test line wrap fix on using both --stat and -G (issue5800)
2673 2673 $ hg init issue5800
2674 2674 $ cd issue5800
2675 2675 $ touch a
2676 2676 $ hg ci -Am 'add a'
2677 2677 adding a
2678 2678 ---- now we are going to add 300 lines to a
2679 2679 $ for i in `$TESTDIR/seq.py 1 300`; do echo $i >> a; done
2680 2680 $ hg ci -m 'modify a'
2681 2681 $ hg log
2682 2682 changeset: 1:a98683e6a834
2683 2683 tag: tip
2684 2684 user: test
2685 2685 date: Thu Jan 01 00:00:00 1970 +0000
2686 2686 summary: modify a
2687 2687
2688 2688 changeset: 0:ac82d8b1f7c4
2689 2689 user: test
2690 2690 date: Thu Jan 01 00:00:00 1970 +0000
2691 2691 summary: add a
2692 2692
2693 2693 ---- now visualise the changes we made without template
2694 2694 $ hg log -l1 -r a98683e6a834 --stat -G
2695 2695 @ changeset: 1:a98683e6a834
2696 2696 | tag: tip
2697 2697 ~ user: test
2698 2698 date: Thu Jan 01 00:00:00 1970 +0000
2699 2699 summary: modify a
2700 2700
2701 2701 a | 300 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2702 2702 1 files changed, 300 insertions(+), 0 deletions(-)
2703 2703
2704 2704 ---- with template
2705 2705 $ hg log -l1 -r a98683e6a834 --stat -G -T bisect
2706 2706 @ changeset: 1:a98683e6a834
2707 2707 | bisect:
2708 2708 ~ tag: tip
2709 2709 user: test
2710 2710 date: Thu Jan 01 00:00:00 1970 +0000
2711 2711 summary: modify a
2712 2712
2713 2713 a | 300 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2714 2714 1 files changed, 300 insertions(+), 0 deletions(-)
2715 2715
2716 2716 $ hg log -l1 -r a98683e6a834 --stat -G -T changelog
2717 2717 1970-01-01 test <test>
2718 2718
2719 2719 @ * a:
2720 2720 | modify a
2721 2721 ~ [a98683e6a834] [tip]
2722 2722
2723 2723 a | 300 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2724 2724 1 files changed, 300 insertions(+), 0 deletions(-)
2725 2725
2726 2726 $ hg log -l1 -r a98683e6a834 --stat -G -T compact
2727 2727 @ 1[tip] a98683e6a834 1970-01-01 00:00 +0000 test
2728 2728 | modify a
2729 2729 ~
2730 2730 a | 300 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2731 2731 1 files changed, 300 insertions(+), 0 deletions(-)
2732 2732
2733 2733 $ hg log -l1 -r a98683e6a834 --stat -G -T default
2734 2734 @ changeset: 1:a98683e6a834
2735 2735 | tag: tip
2736 2736 ~ user: test
2737 2737 date: Thu Jan 01 00:00:00 1970 +0000
2738 2738 summary: modify a
2739 2739
2740 2740 a | 300 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2741 2741 1 files changed, 300 insertions(+), 0 deletions(-)
2742 2742
2743 2743 $ hg log -l1 -r a98683e6a834 --stat -G -T phases
2744 2744 @ changeset: 1:a98683e6a834
2745 2745 | tag: tip
2746 2746 ~ phase: draft
2747 2747 user: test
2748 2748 date: Thu Jan 01 00:00:00 1970 +0000
2749 2749 summary: modify a
2750 2750
2751 2751 a | 300 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2752 2752 1 files changed, 300 insertions(+), 0 deletions(-)
2753 2753
2754 2754 $ hg log -l1 -r a98683e6a834 --stat -G -T show
2755 2755 @ changeset: 1:a98683e6a834
2756 2756 | tag: tip
2757 2757 ~ user: test
2758 2758 date: Thu Jan 01 00:00:00 1970 +0000
2759 2759 summary: modify a
2760 2760
2761 2761 a | 300 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2762 2762 1 files changed, 300 insertions(+), 0 deletions(-)
2763 2763
2764 2764 $ hg log -l1 -r a98683e6a834 --stat -G -T status
2765 2765 @ changeset: 1:a98683e6a834
2766 2766 | tag: tip
2767 2767 ~ user: test
2768 2768 date: Thu Jan 01 00:00:00 1970 +0000
2769 2769 summary: modify a
2770 2770 files:
2771 2771 M a
2772 2772
2773 2773 a | 300 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2774 2774 1 files changed, 300 insertions(+), 0 deletions(-)
2775 2775
2776 2776 $ hg log -l1 -r a98683e6a834 --stat -G -T xml
2777 2777 <?xml version="1.0"?>
2778 2778 <log>
2779 2779 @ <logentry revision="1" node="a98683e6a8340830a7683909768b62871e84bc9d">
2780 2780 | <tag>tip</tag>
2781 2781 ~ <author email="test">test</author>
2782 2782 <date>1970-01-01T00:00:00+00:00</date>
2783 2783 <msg xml:space="preserve">modify a</msg>
2784 2784 </logentry>
2785 2785 a | 300 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2786 2786 1 files changed, 300 insertions(+), 0 deletions(-)
2787 2787
2788 2788 </log>
2789 2789
2790 2790 $ cd ..
@@ -1,674 +1,682 b''
1 1 $ hg init repo1
2 2 $ cd repo1
3 3 $ mkdir a b a/1 b/1 b/2
4 4 $ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
5 5
6 6 hg status in repo root:
7 7
8 8 $ hg status
9 9 ? a/1/in_a_1
10 10 ? a/in_a
11 11 ? b/1/in_b_1
12 12 ? b/2/in_b_2
13 13 ? b/in_b
14 14 ? in_root
15 15
16 16 hg status . in repo root:
17 17
18 18 $ hg status .
19 19 ? a/1/in_a_1
20 20 ? a/in_a
21 21 ? b/1/in_b_1
22 22 ? b/2/in_b_2
23 23 ? b/in_b
24 24 ? in_root
25 25
26 26 $ hg status --cwd a
27 27 ? a/1/in_a_1
28 28 ? a/in_a
29 29 ? b/1/in_b_1
30 30 ? b/2/in_b_2
31 31 ? b/in_b
32 32 ? in_root
33 33 $ hg status --cwd a .
34 34 ? 1/in_a_1
35 35 ? in_a
36 36 $ hg status --cwd a ..
37 37 ? 1/in_a_1
38 38 ? in_a
39 39 ? ../b/1/in_b_1
40 40 ? ../b/2/in_b_2
41 41 ? ../b/in_b
42 42 ? ../in_root
43 43
44 44 $ hg status --cwd b
45 45 ? a/1/in_a_1
46 46 ? a/in_a
47 47 ? b/1/in_b_1
48 48 ? b/2/in_b_2
49 49 ? b/in_b
50 50 ? in_root
51 51 $ hg status --cwd b .
52 52 ? 1/in_b_1
53 53 ? 2/in_b_2
54 54 ? in_b
55 55 $ hg status --cwd b ..
56 56 ? ../a/1/in_a_1
57 57 ? ../a/in_a
58 58 ? 1/in_b_1
59 59 ? 2/in_b_2
60 60 ? in_b
61 61 ? ../in_root
62 62
63 63 $ hg status --cwd a/1
64 64 ? a/1/in_a_1
65 65 ? a/in_a
66 66 ? b/1/in_b_1
67 67 ? b/2/in_b_2
68 68 ? b/in_b
69 69 ? in_root
70 70 $ hg status --cwd a/1 .
71 71 ? in_a_1
72 72 $ hg status --cwd a/1 ..
73 73 ? in_a_1
74 74 ? ../in_a
75 75
76 76 $ hg status --cwd b/1
77 77 ? a/1/in_a_1
78 78 ? a/in_a
79 79 ? b/1/in_b_1
80 80 ? b/2/in_b_2
81 81 ? b/in_b
82 82 ? in_root
83 83 $ hg status --cwd b/1 .
84 84 ? in_b_1
85 85 $ hg status --cwd b/1 ..
86 86 ? in_b_1
87 87 ? ../2/in_b_2
88 88 ? ../in_b
89 89
90 90 $ hg status --cwd b/2
91 91 ? a/1/in_a_1
92 92 ? a/in_a
93 93 ? b/1/in_b_1
94 94 ? b/2/in_b_2
95 95 ? b/in_b
96 96 ? in_root
97 97 $ hg status --cwd b/2 .
98 98 ? in_b_2
99 99 $ hg status --cwd b/2 ..
100 100 ? ../1/in_b_1
101 101 ? in_b_2
102 102 ? ../in_b
103 103
104 104 combining patterns with root and patterns without a root works
105 105
106 106 $ hg st a/in_a re:.*b$
107 107 ? a/in_a
108 108 ? b/in_b
109 109
110 110 tweaking defaults works
111 111 $ hg status --cwd a --config ui.tweakdefaults=yes
112 112 ? 1/in_a_1
113 113 ? in_a
114 114 ? ../b/1/in_b_1
115 115 ? ../b/2/in_b_2
116 116 ? ../b/in_b
117 117 ? ../in_root
118 118 $ HGPLAIN=1 hg status --cwd a --config ui.tweakdefaults=yes
119 119 ? a/1/in_a_1 (glob)
120 120 ? a/in_a (glob)
121 121 ? b/1/in_b_1 (glob)
122 122 ? b/2/in_b_2 (glob)
123 123 ? b/in_b (glob)
124 124 ? in_root
125 125 $ HGPLAINEXCEPT=tweakdefaults hg status --cwd a --config ui.tweakdefaults=yes
126 126 ? 1/in_a_1
127 127 ? in_a
128 128 ? ../b/1/in_b_1
129 129 ? ../b/2/in_b_2
130 130 ? ../b/in_b
131 131 ? ../in_root (glob)
132 132
133 133 relative paths can be requested
134 134
135 135 $ hg status --cwd a --config ui.relative-paths=yes
136 136 ? 1/in_a_1
137 137 ? in_a
138 138 ? ../b/1/in_b_1
139 139 ? ../b/2/in_b_2
140 140 ? ../b/in_b
141 141 ? ../in_root
142 142
143 143 $ hg status --cwd a . --config ui.relative-paths=legacy
144 144 ? 1/in_a_1
145 145 ? in_a
146 146 $ hg status --cwd a . --config ui.relative-paths=no
147 147 ? a/1/in_a_1
148 148 ? a/in_a
149 149
150 150 commands.status.relative overrides ui.relative-paths
151 151
152 152 $ cat >> $HGRCPATH <<EOF
153 153 > [ui]
154 154 > relative-paths = False
155 155 > [commands]
156 156 > status.relative = True
157 157 > EOF
158 158 $ hg status --cwd a
159 159 ? 1/in_a_1
160 160 ? in_a
161 161 ? ../b/1/in_b_1
162 162 ? ../b/2/in_b_2
163 163 ? ../b/in_b
164 164 ? ../in_root
165 165 $ HGPLAIN=1 hg status --cwd a
166 166 ? a/1/in_a_1 (glob)
167 167 ? a/in_a (glob)
168 168 ? b/1/in_b_1 (glob)
169 169 ? b/2/in_b_2 (glob)
170 170 ? b/in_b (glob)
171 171 ? in_root
172 172
173 173 if relative paths are explicitly off, tweakdefaults doesn't change it
174 174 $ cat >> $HGRCPATH <<EOF
175 175 > [commands]
176 176 > status.relative = False
177 177 > EOF
178 178 $ hg status --cwd a --config ui.tweakdefaults=yes
179 179 ? a/1/in_a_1
180 180 ? a/in_a
181 181 ? b/1/in_b_1
182 182 ? b/2/in_b_2
183 183 ? b/in_b
184 184 ? in_root
185 185
186 186 $ cd ..
187 187
188 188 $ hg init repo2
189 189 $ cd repo2
190 190 $ touch modified removed deleted ignored
191 191 $ echo "^ignored$" > .hgignore
192 192 $ hg ci -A -m 'initial checkin'
193 193 adding .hgignore
194 194 adding deleted
195 195 adding modified
196 196 adding removed
197 197 $ touch modified added unknown ignored
198 198 $ hg add added
199 199 $ hg remove removed
200 200 $ rm deleted
201 201
202 202 hg status:
203 203
204 204 $ hg status
205 205 A added
206 206 R removed
207 207 ! deleted
208 208 ? unknown
209 209
210 210 hg status modified added removed deleted unknown never-existed ignored:
211 211
212 212 $ hg status modified added removed deleted unknown never-existed ignored
213 213 never-existed: * (glob)
214 214 A added
215 215 R removed
216 216 ! deleted
217 217 ? unknown
218 218
219 219 $ hg copy modified copied
220 220
221 221 hg status -C:
222 222
223 223 $ hg status -C
224 224 A added
225 225 A copied
226 226 modified
227 227 R removed
228 228 ! deleted
229 229 ? unknown
230 230
231 231 hg status -A:
232 232
233 233 $ hg status -A
234 234 A added
235 235 A copied
236 236 modified
237 237 R removed
238 238 ! deleted
239 239 ? unknown
240 240 I ignored
241 241 C .hgignore
242 242 C modified
243 243
244 244 $ hg status -A -T '{status} {path} {node|shortest}\n'
245 245 A added ffff
246 246 A copied ffff
247 247 R removed ffff
248 248 ! deleted ffff
249 249 ? unknown ffff
250 250 I ignored ffff
251 251 C .hgignore ffff
252 252 C modified ffff
253 253
254 254 $ hg status -A -Tjson
255 255 [
256 256 {
257 257 "itemtype": "file",
258 258 "path": "added",
259 259 "status": "A"
260 260 },
261 261 {
262 262 "itemtype": "file",
263 263 "path": "copied",
264 264 "source": "modified",
265 265 "status": "A"
266 266 },
267 267 {
268 268 "itemtype": "file",
269 269 "path": "removed",
270 270 "status": "R"
271 271 },
272 272 {
273 273 "itemtype": "file",
274 274 "path": "deleted",
275 275 "status": "!"
276 276 },
277 277 {
278 278 "itemtype": "file",
279 279 "path": "unknown",
280 280 "status": "?"
281 281 },
282 282 {
283 283 "itemtype": "file",
284 284 "path": "ignored",
285 285 "status": "I"
286 286 },
287 287 {
288 288 "itemtype": "file",
289 289 "path": ".hgignore",
290 290 "status": "C"
291 291 },
292 292 {
293 293 "itemtype": "file",
294 294 "path": "modified",
295 295 "status": "C"
296 296 }
297 297 ]
298 298
299 299 $ hg status -A -Tpickle > pickle
300 300 >>> from __future__ import print_function
301 301 >>> from mercurial import util
302 302 >>> pickle = util.pickle
303 303 >>> data = sorted((x[b'status'].decode(), x[b'path'].decode()) for x in pickle.load(open("pickle", r"rb")))
304 304 >>> for s, p in data: print("%s %s" % (s, p))
305 305 ! deleted
306 306 ? pickle
307 307 ? unknown
308 308 A added
309 309 A copied
310 310 C .hgignore
311 311 C modified
312 312 I ignored
313 313 R removed
314 314 $ rm pickle
315 315
316 316 $ echo "^ignoreddir$" > .hgignore
317 317 $ mkdir ignoreddir
318 318 $ touch ignoreddir/file
319 319
320 320 Test templater support:
321 321
322 322 $ hg status -AT "[{status}]\t{if(source, '{source} -> ')}{path}\n"
323 323 [M] .hgignore
324 324 [A] added
325 325 [A] modified -> copied
326 326 [R] removed
327 327 [!] deleted
328 328 [?] ignored
329 329 [?] unknown
330 330 [I] ignoreddir/file
331 331 [C] modified
332 332 $ hg status -AT default
333 333 M .hgignore
334 334 A added
335 335 A copied
336 336 modified
337 337 R removed
338 338 ! deleted
339 339 ? ignored
340 340 ? unknown
341 341 I ignoreddir/file
342 342 C modified
343 343 $ hg status -T compact
344 344 abort: "status" not in template map
345 345 [255]
346 346
347 347 hg status ignoreddir/file:
348 348
349 349 $ hg status ignoreddir/file
350 350
351 351 hg status -i ignoreddir/file:
352 352
353 353 $ hg status -i ignoreddir/file
354 354 I ignoreddir/file
355 355 $ cd ..
356 356
357 357 Check 'status -q' and some combinations
358 358
359 359 $ hg init repo3
360 360 $ cd repo3
361 361 $ touch modified removed deleted ignored
362 362 $ echo "^ignored$" > .hgignore
363 363 $ hg commit -A -m 'initial checkin'
364 364 adding .hgignore
365 365 adding deleted
366 366 adding modified
367 367 adding removed
368 368 $ touch added unknown ignored
369 369 $ hg add added
370 370 $ echo "test" >> modified
371 371 $ hg remove removed
372 372 $ rm deleted
373 373 $ hg copy modified copied
374 374
375 375 Specify working directory revision explicitly, that should be the same as
376 376 "hg status"
377 377
378 378 $ hg status --change "wdir()"
379 379 M modified
380 380 A added
381 381 A copied
382 382 R removed
383 383 ! deleted
384 384 ? unknown
385 385
386 386 Run status with 2 different flags.
387 387 Check if result is the same or different.
388 388 If result is not as expected, raise error
389 389
390 390 $ assert() {
391 391 > hg status $1 > ../a
392 392 > hg status $2 > ../b
393 393 > if diff ../a ../b > /dev/null; then
394 394 > out=0
395 395 > else
396 396 > out=1
397 397 > fi
398 398 > if [ $3 -eq 0 ]; then
399 399 > df="same"
400 400 > else
401 401 > df="different"
402 402 > fi
403 403 > if [ $out -ne $3 ]; then
404 404 > echo "Error on $1 and $2, should be $df."
405 405 > fi
406 406 > }
407 407
408 408 Assert flag1 flag2 [0-same | 1-different]
409 409
410 410 $ assert "-q" "-mard" 0
411 411 $ assert "-A" "-marduicC" 0
412 412 $ assert "-qA" "-mardcC" 0
413 413 $ assert "-qAui" "-A" 0
414 414 $ assert "-qAu" "-marducC" 0
415 415 $ assert "-qAi" "-mardicC" 0
416 416 $ assert "-qu" "-u" 0
417 417 $ assert "-q" "-u" 1
418 418 $ assert "-m" "-a" 1
419 419 $ assert "-r" "-d" 1
420 420 $ cd ..
421 421
422 422 $ hg init repo4
423 423 $ cd repo4
424 424 $ touch modified removed deleted
425 425 $ hg ci -q -A -m 'initial checkin'
426 426 $ touch added unknown
427 427 $ hg add added
428 428 $ hg remove removed
429 429 $ rm deleted
430 430 $ echo x > modified
431 431 $ hg copy modified copied
432 432 $ hg ci -m 'test checkin' -d "1000001 0"
433 433 $ rm *
434 434 $ touch unrelated
435 435 $ hg ci -q -A -m 'unrelated checkin' -d "1000002 0"
436 436
437 437 hg status --change 1:
438 438
439 439 $ hg status --change 1
440 440 M modified
441 441 A added
442 442 A copied
443 443 R removed
444 444
445 445 hg status --change 1 unrelated:
446 446
447 447 $ hg status --change 1 unrelated
448 448
449 449 hg status -C --change 1 added modified copied removed deleted:
450 450
451 451 $ hg status -C --change 1 added modified copied removed deleted
452 452 M modified
453 453 A added
454 454 A copied
455 455 modified
456 456 R removed
457 457
458 458 hg status -A --change 1 and revset:
459 459
460 460 $ hg status -A --change '1|1'
461 461 M modified
462 462 A added
463 463 A copied
464 464 modified
465 465 R removed
466 466 C deleted
467 467
468 468 $ cd ..
469 469
470 470 hg status with --rev and reverted changes:
471 471
472 472 $ hg init reverted-changes-repo
473 473 $ cd reverted-changes-repo
474 474 $ echo a > file
475 475 $ hg add file
476 476 $ hg ci -m a
477 477 $ echo b > file
478 478 $ hg ci -m b
479 479
480 480 reverted file should appear clean
481 481
482 482 $ hg revert -r 0 .
483 483 reverting file
484 484 $ hg status -A --rev 0
485 485 C file
486 486
487 487 #if execbit
488 488 reverted file with changed flag should appear modified
489 489
490 490 $ chmod +x file
491 491 $ hg status -A --rev 0
492 492 M file
493 493
494 494 $ hg revert -r 0 .
495 495 reverting file
496 496
497 497 reverted and committed file with changed flag should appear modified
498 498
499 499 $ hg co -C .
500 500 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
501 501 $ chmod +x file
502 502 $ hg ci -m 'change flag'
503 503 $ hg status -A --rev 1 --rev 2
504 504 M file
505 505 $ hg diff -r 1 -r 2
506 506
507 507 #endif
508 508
509 509 $ cd ..
510 510
511 511 hg status of binary file starting with '\1\n', a separator for metadata:
512 512
513 513 $ hg init repo5
514 514 $ cd repo5
515 515 >>> open("010a", r"wb").write(b"\1\nfoo") and None
516 516 $ hg ci -q -A -m 'initial checkin'
517 517 $ hg status -A
518 518 C 010a
519 519
520 520 >>> open("010a", r"wb").write(b"\1\nbar") and None
521 521 $ hg status -A
522 522 M 010a
523 523 $ hg ci -q -m 'modify 010a'
524 524 $ hg status -A --rev 0:1
525 525 M 010a
526 526
527 527 $ touch empty
528 528 $ hg ci -q -A -m 'add another file'
529 529 $ hg status -A --rev 1:2 010a
530 530 C 010a
531 531
532 532 $ cd ..
533 533
534 534 test "hg status" with "directory pattern" which matches against files
535 535 only known on target revision.
536 536
537 537 $ hg init repo6
538 538 $ cd repo6
539 539
540 540 $ echo a > a.txt
541 541 $ hg add a.txt
542 542 $ hg commit -m '#0'
543 543 $ mkdir -p 1/2/3/4/5
544 544 $ echo b > 1/2/3/4/5/b.txt
545 545 $ hg add 1/2/3/4/5/b.txt
546 546 $ hg commit -m '#1'
547 547
548 548 $ hg update -C 0 > /dev/null
549 549 $ hg status -A
550 550 C a.txt
551 551
552 552 the directory matching against specified pattern should be removed,
553 553 because directory existence prevents 'dirstate.walk()' from showing
554 554 warning message about such pattern.
555 555
556 556 $ test ! -d 1
557 557 $ hg status -A --rev 1 1/2/3/4/5/b.txt
558 558 R 1/2/3/4/5/b.txt
559 559 $ hg status -A --rev 1 1/2/3/4/5
560 560 R 1/2/3/4/5/b.txt
561 561 $ hg status -A --rev 1 1/2/3
562 562 R 1/2/3/4/5/b.txt
563 563 $ hg status -A --rev 1 1
564 564 R 1/2/3/4/5/b.txt
565 565
566 566 $ hg status --config ui.formatdebug=True --rev 1 1
567 567 status = [
568 568 {
569 569 'itemtype': 'file',
570 570 'path': '1/2/3/4/5/b.txt',
571 571 'status': 'R'
572 572 },
573 573 ]
574 574
575 575 #if windows
576 576 $ hg --config ui.slash=false status -A --rev 1 1
577 577 R 1\2\3\4\5\b.txt
578 578 #endif
579 579
580 580 $ cd ..
581 581
582 582 Status after move overwriting a file (issue4458)
583 583 =================================================
584 584
585 585
586 586 $ hg init issue4458
587 587 $ cd issue4458
588 588 $ echo a > a
589 589 $ echo b > b
590 590 $ hg commit -Am base
591 591 adding a
592 592 adding b
593 593
594 594
595 595 with --force
596 596
597 597 $ hg mv b --force a
598 598 $ hg st --copies
599 599 M a
600 600 b
601 601 R b
602 602 $ hg revert --all
603 603 reverting a
604 604 undeleting b
605 605 $ rm *.orig
606 606
607 607 without force
608 608
609 609 $ hg rm a
610 610 $ hg st --copies
611 611 R a
612 612 $ hg mv b a
613 613 $ hg st --copies
614 614 M a
615 615 b
616 616 R b
617 617
618 618 using ui.statuscopies setting
619 619 $ hg st --config ui.statuscopies=true
620 620 M a
621 621 b
622 622 R b
623 623 $ hg st --config ui.statuscopies=false
624 624 M a
625 625 R b
626 626 $ hg st --config ui.tweakdefaults=yes
627 627 M a
628 628 b
629 629 R b
630 630
631 631 using log status template (issue5155)
632 632 $ hg log -Tstatus -r 'wdir()' -C
633 633 changeset: 2147483647:ffffffffffff
634 634 parent: 0:8c55c58b4c0e
635 635 user: test
636 636 date: * (glob)
637 637 files:
638 638 M a
639 639 b
640 640 R b
641 641
642 642 $ hg log -GTstatus -r 'wdir()' -C
643 643 o changeset: 2147483647:ffffffffffff
644 644 | parent: 0:8c55c58b4c0e
645 645 ~ user: test
646 646 date: * (glob)
647 647 files:
648 648 M a
649 649 b
650 650 R b
651 651
652 652
653 653 Other "bug" highlight, the revision status does not report the copy information.
654 654 This is buggy behavior.
655 655
656 656 $ hg commit -m 'blah'
657 657 $ hg st --copies --change .
658 658 M a
659 659 R b
660 660
661 661 using log status template, the copy information is displayed correctly.
662 662 $ hg log -Tstatus -r. -C
663 663 changeset: 1:6685fde43d21
664 664 tag: tip
665 665 user: test
666 666 date: * (glob)
667 667 summary: blah
668 668 files:
669 669 M a
670 670 b
671 671 R b
672 672
673 673
674 674 $ cd ..
675
676 Make sure .hg doesn't show up even as a symlink
677
678 $ hg init repo0
679 $ mkdir symlink-repo0
680 $ cd symlink-repo0
681 $ ln -s ../repo0/.hg
682 $ hg status
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now