##// END OF EJS Templates
merge with stable
Pulkit Goyal -
r45152:e147748f merge default
parent child Browse files
Show More
@@ -1,195 +1,196 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,208 +1,209 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 8fca7e8449a847e3cf1054f2c07b51237699fad3 5.3.2
@@ -1,2644 +1,2644 b''
1 1 # histedit.py - interactive history editing for mercurial
2 2 #
3 3 # Copyright 2009 Augie Fackler <raf@durin42.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 """interactive history editing
8 8
9 9 With this extension installed, Mercurial gains one new command: histedit. Usage
10 10 is as follows, assuming the following history::
11 11
12 12 @ 3[tip] 7c2fd3b9020c 2009-04-27 18:04 -0500 durin42
13 13 | Add delta
14 14 |
15 15 o 2 030b686bedc4 2009-04-27 18:04 -0500 durin42
16 16 | Add gamma
17 17 |
18 18 o 1 c561b4e977df 2009-04-27 18:04 -0500 durin42
19 19 | Add beta
20 20 |
21 21 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
22 22 Add alpha
23 23
24 24 If you were to run ``hg histedit c561b4e977df``, you would see the following
25 25 file open in your editor::
26 26
27 27 pick c561b4e977df Add beta
28 28 pick 030b686bedc4 Add gamma
29 29 pick 7c2fd3b9020c Add delta
30 30
31 31 # Edit history between c561b4e977df and 7c2fd3b9020c
32 32 #
33 33 # Commits are listed from least to most recent
34 34 #
35 35 # Commands:
36 36 # p, pick = use commit
37 37 # e, edit = use commit, but stop for amending
38 38 # f, fold = use commit, but combine it with the one above
39 39 # r, roll = like fold, but discard this commit's description and date
40 40 # d, drop = remove commit from history
41 41 # m, mess = edit commit message without changing commit content
42 42 # b, base = checkout changeset and apply further changesets from there
43 43 #
44 44
45 45 In this file, lines beginning with ``#`` are ignored. You must specify a rule
46 46 for each revision in your history. For example, if you had meant to add gamma
47 47 before beta, and then wanted to add delta in the same revision as beta, you
48 48 would reorganize the file to look like this::
49 49
50 50 pick 030b686bedc4 Add gamma
51 51 pick c561b4e977df Add beta
52 52 fold 7c2fd3b9020c Add delta
53 53
54 54 # Edit history between c561b4e977df and 7c2fd3b9020c
55 55 #
56 56 # Commits are listed from least to most recent
57 57 #
58 58 # Commands:
59 59 # p, pick = use commit
60 60 # e, edit = use commit, but stop for amending
61 61 # f, fold = use commit, but combine it with the one above
62 62 # r, roll = like fold, but discard this commit's description and date
63 63 # d, drop = remove commit from history
64 64 # m, mess = edit commit message without changing commit content
65 65 # b, base = checkout changeset and apply further changesets from there
66 66 #
67 67
68 68 At which point you close the editor and ``histedit`` starts working. When you
69 69 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
70 70 those revisions together, offering you a chance to clean up the commit message::
71 71
72 72 Add beta
73 73 ***
74 74 Add delta
75 75
76 76 Edit the commit message to your liking, then close the editor. The date used
77 77 for the commit will be the later of the two commits' dates. For this example,
78 78 let's assume that the commit message was changed to ``Add beta and delta.``
79 79 After histedit has run and had a chance to remove any old or temporary
80 80 revisions it needed, the history looks like this::
81 81
82 82 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
83 83 | Add beta and delta.
84 84 |
85 85 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
86 86 | Add gamma
87 87 |
88 88 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
89 89 Add alpha
90 90
91 91 Note that ``histedit`` does *not* remove any revisions (even its own temporary
92 92 ones) until after it has completed all the editing operations, so it will
93 93 probably perform several strip operations when it's done. For the above example,
94 94 it had to run strip twice. Strip can be slow depending on a variety of factors,
95 95 so you might need to be a little patient. You can choose to keep the original
96 96 revisions by passing the ``--keep`` flag.
97 97
98 98 The ``edit`` operation will drop you back to a command prompt,
99 99 allowing you to edit files freely, or even use ``hg record`` to commit
100 100 some changes as a separate commit. When you're done, any remaining
101 101 uncommitted changes will be committed as well. When done, run ``hg
102 102 histedit --continue`` to finish this step. If there are uncommitted
103 103 changes, you'll be prompted for a new commit message, but the default
104 104 commit message will be the original message for the ``edit`` ed
105 105 revision, and the date of the original commit will be preserved.
106 106
107 107 The ``message`` operation will give you a chance to revise a commit
108 108 message without changing the contents. It's a shortcut for doing
109 109 ``edit`` immediately followed by `hg histedit --continue``.
110 110
111 111 If ``histedit`` encounters a conflict when moving a revision (while
112 112 handling ``pick`` or ``fold``), it'll stop in a similar manner to
113 113 ``edit`` with the difference that it won't prompt you for a commit
114 114 message when done. If you decide at this point that you don't like how
115 115 much work it will be to rearrange history, or that you made a mistake,
116 116 you can use ``hg histedit --abort`` to abandon the new changes you
117 117 have made and return to the state before you attempted to edit your
118 118 history.
119 119
120 120 If we clone the histedit-ed example repository above and add four more
121 121 changes, such that we have the following history::
122 122
123 123 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
124 124 | Add theta
125 125 |
126 126 o 5 140988835471 2009-04-27 18:04 -0500 stefan
127 127 | Add eta
128 128 |
129 129 o 4 122930637314 2009-04-27 18:04 -0500 stefan
130 130 | Add zeta
131 131 |
132 132 o 3 836302820282 2009-04-27 18:04 -0500 stefan
133 133 | Add epsilon
134 134 |
135 135 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
136 136 | Add beta and delta.
137 137 |
138 138 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
139 139 | Add gamma
140 140 |
141 141 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
142 142 Add alpha
143 143
144 144 If you run ``hg histedit --outgoing`` on the clone then it is the same
145 145 as running ``hg histedit 836302820282``. If you need plan to push to a
146 146 repository that Mercurial does not detect to be related to the source
147 147 repo, you can add a ``--force`` option.
148 148
149 149 Config
150 150 ------
151 151
152 152 Histedit rule lines are truncated to 80 characters by default. You
153 153 can customize this behavior by setting a different length in your
154 154 configuration file::
155 155
156 156 [histedit]
157 157 linelen = 120 # truncate rule lines at 120 characters
158 158
159 159 The summary of a change can be customized as well::
160 160
161 161 [histedit]
162 162 summary-template = '{rev} {bookmarks} {desc|firstline}'
163 163
164 164 The customized summary should be kept short enough that rule lines
165 165 will fit in the configured line length. See above if that requires
166 166 customization.
167 167
168 168 ``hg histedit`` attempts to automatically choose an appropriate base
169 169 revision to use. To change which base revision is used, define a
170 170 revset in your configuration file::
171 171
172 172 [histedit]
173 173 defaultrev = only(.) & draft()
174 174
175 175 By default each edited revision needs to be present in histedit commands.
176 176 To remove revision you need to use ``drop`` operation. You can configure
177 177 the drop to be implicit for missing commits by adding::
178 178
179 179 [histedit]
180 180 dropmissing = True
181 181
182 182 By default, histedit will close the transaction after each action. For
183 183 performance purposes, you can configure histedit to use a single transaction
184 184 across the entire histedit. WARNING: This setting introduces a significant risk
185 185 of losing the work you've done in a histedit if the histedit aborts
186 186 unexpectedly::
187 187
188 188 [histedit]
189 189 singletransaction = True
190 190
191 191 """
192 192
193 193 from __future__ import absolute_import
194 194
195 195 # chistedit dependencies that are not available everywhere
196 196 try:
197 197 import fcntl
198 198 import termios
199 199 except ImportError:
200 200 fcntl = None
201 201 termios = None
202 202
203 203 import functools
204 204 import locale
205 205 import os
206 206 import struct
207 207
208 208 from mercurial.i18n import _
209 209 from mercurial.pycompat import (
210 210 getattr,
211 211 open,
212 212 )
213 213 from mercurial import (
214 214 bundle2,
215 215 cmdutil,
216 216 context,
217 217 copies,
218 218 destutil,
219 219 discovery,
220 220 encoding,
221 221 error,
222 222 exchange,
223 223 extensions,
224 224 hg,
225 225 logcmdutil,
226 226 merge as mergemod,
227 227 mergeutil,
228 228 node,
229 229 obsolete,
230 230 pycompat,
231 231 registrar,
232 232 repair,
233 233 rewriteutil,
234 234 scmutil,
235 235 state as statemod,
236 236 util,
237 237 )
238 238 from mercurial.utils import (
239 239 dateutil,
240 240 stringutil,
241 241 )
242 242
243 243 pickle = util.pickle
244 244 cmdtable = {}
245 245 command = registrar.command(cmdtable)
246 246
247 247 configtable = {}
248 248 configitem = registrar.configitem(configtable)
249 249 configitem(
250 250 b'experimental', b'histedit.autoverb', default=False,
251 251 )
252 252 configitem(
253 253 b'histedit', b'defaultrev', default=None,
254 254 )
255 255 configitem(
256 256 b'histedit', b'dropmissing', default=False,
257 257 )
258 258 configitem(
259 259 b'histedit', b'linelen', default=80,
260 260 )
261 261 configitem(
262 262 b'histedit', b'singletransaction', default=False,
263 263 )
264 264 configitem(
265 265 b'ui', b'interface.histedit', default=None,
266 266 )
267 267 configitem(b'histedit', b'summary-template', default=b'{rev} {desc|firstline}')
268 268
269 269 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
270 270 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
271 271 # be specifying the version(s) of Mercurial they are tested with, or
272 272 # leave the attribute unspecified.
273 273 testedwith = b'ships-with-hg-core'
274 274
275 275 actiontable = {}
276 276 primaryactions = set()
277 277 secondaryactions = set()
278 278 tertiaryactions = set()
279 279 internalactions = set()
280 280
281 281
282 282 def geteditcomment(ui, first, last):
283 283 """ construct the editor comment
284 284 The comment includes::
285 285 - an intro
286 286 - sorted primary commands
287 287 - sorted short commands
288 288 - sorted long commands
289 289 - additional hints
290 290
291 291 Commands are only included once.
292 292 """
293 293 intro = _(
294 """Edit history between %s and %s
294 b"""Edit history between %s and %s
295 295
296 296 Commits are listed from least to most recent
297 297
298 298 You can reorder changesets by reordering the lines
299 299
300 300 Commands:
301 301 """
302 302 )
303 303 actions = []
304 304
305 305 def addverb(v):
306 306 a = actiontable[v]
307 307 lines = a.message.split(b"\n")
308 308 if len(a.verbs):
309 309 v = b', '.join(sorted(a.verbs, key=lambda v: len(v)))
310 310 actions.append(b" %s = %s" % (v, lines[0]))
311 311 actions.extend([b' %s'] * (len(lines) - 1))
312 312
313 313 for v in (
314 314 sorted(primaryactions)
315 315 + sorted(secondaryactions)
316 316 + sorted(tertiaryactions)
317 317 ):
318 318 addverb(v)
319 319 actions.append(b'')
320 320
321 321 hints = []
322 322 if ui.configbool(b'histedit', b'dropmissing'):
323 323 hints.append(
324 324 b"Deleting a changeset from the list "
325 325 b"will DISCARD it from the edited history!"
326 326 )
327 327
328 328 lines = (intro % (first, last)).split(b'\n') + actions + hints
329 329
330 330 return b''.join([b'# %s\n' % l if l else b'#\n' for l in lines])
331 331
332 332
333 333 class histeditstate(object):
334 334 def __init__(self, repo):
335 335 self.repo = repo
336 336 self.actions = None
337 337 self.keep = None
338 338 self.topmost = None
339 339 self.parentctxnode = None
340 340 self.lock = None
341 341 self.wlock = None
342 342 self.backupfile = None
343 343 self.stateobj = statemod.cmdstate(repo, b'histedit-state')
344 344 self.replacements = []
345 345
346 346 def read(self):
347 347 """Load histedit state from disk and set fields appropriately."""
348 348 if not self.stateobj.exists():
349 349 cmdutil.wrongtooltocontinue(self.repo, _(b'histedit'))
350 350
351 351 data = self._read()
352 352
353 353 self.parentctxnode = data[b'parentctxnode']
354 354 actions = parserules(data[b'rules'], self)
355 355 self.actions = actions
356 356 self.keep = data[b'keep']
357 357 self.topmost = data[b'topmost']
358 358 self.replacements = data[b'replacements']
359 359 self.backupfile = data[b'backupfile']
360 360
361 361 def _read(self):
362 362 fp = self.repo.vfs.read(b'histedit-state')
363 363 if fp.startswith(b'v1\n'):
364 364 data = self._load()
365 365 parentctxnode, rules, keep, topmost, replacements, backupfile = data
366 366 else:
367 367 data = pickle.loads(fp)
368 368 parentctxnode, rules, keep, topmost, replacements = data
369 369 backupfile = None
370 370 rules = b"\n".join([b"%s %s" % (verb, rest) for [verb, rest] in rules])
371 371
372 372 return {
373 373 b'parentctxnode': parentctxnode,
374 374 b"rules": rules,
375 375 b"keep": keep,
376 376 b"topmost": topmost,
377 377 b"replacements": replacements,
378 378 b"backupfile": backupfile,
379 379 }
380 380
381 381 def write(self, tr=None):
382 382 if tr:
383 383 tr.addfilegenerator(
384 384 b'histedit-state',
385 385 (b'histedit-state',),
386 386 self._write,
387 387 location=b'plain',
388 388 )
389 389 else:
390 390 with self.repo.vfs(b"histedit-state", b"w") as f:
391 391 self._write(f)
392 392
393 393 def _write(self, fp):
394 394 fp.write(b'v1\n')
395 395 fp.write(b'%s\n' % node.hex(self.parentctxnode))
396 396 fp.write(b'%s\n' % node.hex(self.topmost))
397 397 fp.write(b'%s\n' % (b'True' if self.keep else b'False'))
398 398 fp.write(b'%d\n' % len(self.actions))
399 399 for action in self.actions:
400 400 fp.write(b'%s\n' % action.tostate())
401 401 fp.write(b'%d\n' % len(self.replacements))
402 402 for replacement in self.replacements:
403 403 fp.write(
404 404 b'%s%s\n'
405 405 % (
406 406 node.hex(replacement[0]),
407 407 b''.join(node.hex(r) for r in replacement[1]),
408 408 )
409 409 )
410 410 backupfile = self.backupfile
411 411 if not backupfile:
412 412 backupfile = b''
413 413 fp.write(b'%s\n' % backupfile)
414 414
415 415 def _load(self):
416 416 fp = self.repo.vfs(b'histedit-state', b'r')
417 417 lines = [l[:-1] for l in fp.readlines()]
418 418
419 419 index = 0
420 420 lines[index] # version number
421 421 index += 1
422 422
423 423 parentctxnode = node.bin(lines[index])
424 424 index += 1
425 425
426 426 topmost = node.bin(lines[index])
427 427 index += 1
428 428
429 429 keep = lines[index] == b'True'
430 430 index += 1
431 431
432 432 # Rules
433 433 rules = []
434 434 rulelen = int(lines[index])
435 435 index += 1
436 436 for i in pycompat.xrange(rulelen):
437 437 ruleaction = lines[index]
438 438 index += 1
439 439 rule = lines[index]
440 440 index += 1
441 441 rules.append((ruleaction, rule))
442 442
443 443 # Replacements
444 444 replacements = []
445 445 replacementlen = int(lines[index])
446 446 index += 1
447 447 for i in pycompat.xrange(replacementlen):
448 448 replacement = lines[index]
449 449 original = node.bin(replacement[:40])
450 450 succ = [
451 451 node.bin(replacement[i : i + 40])
452 452 for i in range(40, len(replacement), 40)
453 453 ]
454 454 replacements.append((original, succ))
455 455 index += 1
456 456
457 457 backupfile = lines[index]
458 458 index += 1
459 459
460 460 fp.close()
461 461
462 462 return parentctxnode, rules, keep, topmost, replacements, backupfile
463 463
464 464 def clear(self):
465 465 if self.inprogress():
466 466 self.repo.vfs.unlink(b'histedit-state')
467 467
468 468 def inprogress(self):
469 469 return self.repo.vfs.exists(b'histedit-state')
470 470
471 471
472 472 class histeditaction(object):
473 473 def __init__(self, state, node):
474 474 self.state = state
475 475 self.repo = state.repo
476 476 self.node = node
477 477
478 478 @classmethod
479 479 def fromrule(cls, state, rule):
480 480 """Parses the given rule, returning an instance of the histeditaction.
481 481 """
482 482 ruleid = rule.strip().split(b' ', 1)[0]
483 483 # ruleid can be anything from rev numbers, hashes, "bookmarks" etc
484 484 # Check for validation of rule ids and get the rulehash
485 485 try:
486 486 rev = node.bin(ruleid)
487 487 except TypeError:
488 488 try:
489 489 _ctx = scmutil.revsingle(state.repo, ruleid)
490 490 rulehash = _ctx.hex()
491 491 rev = node.bin(rulehash)
492 492 except error.RepoLookupError:
493 493 raise error.ParseError(_(b"invalid changeset %s") % ruleid)
494 494 return cls(state, rev)
495 495
496 496 def verify(self, prev, expected, seen):
497 497 """ Verifies semantic correctness of the rule"""
498 498 repo = self.repo
499 499 ha = node.hex(self.node)
500 500 self.node = scmutil.resolvehexnodeidprefix(repo, ha)
501 501 if self.node is None:
502 502 raise error.ParseError(_(b'unknown changeset %s listed') % ha[:12])
503 503 self._verifynodeconstraints(prev, expected, seen)
504 504
505 505 def _verifynodeconstraints(self, prev, expected, seen):
506 506 # by default command need a node in the edited list
507 507 if self.node not in expected:
508 508 raise error.ParseError(
509 509 _(b'%s "%s" changeset was not a candidate')
510 510 % (self.verb, node.short(self.node)),
511 511 hint=_(b'only use listed changesets'),
512 512 )
513 513 # and only one command per node
514 514 if self.node in seen:
515 515 raise error.ParseError(
516 516 _(b'duplicated command for changeset %s')
517 517 % node.short(self.node)
518 518 )
519 519
520 520 def torule(self):
521 521 """build a histedit rule line for an action
522 522
523 523 by default lines are in the form:
524 524 <hash> <rev> <summary>
525 525 """
526 526 ctx = self.repo[self.node]
527 527 ui = self.repo.ui
528 528 summary = (
529 529 cmdutil.rendertemplate(
530 530 ctx, ui.config(b'histedit', b'summary-template')
531 531 )
532 532 or b''
533 533 )
534 534 summary = summary.splitlines()[0]
535 535 line = b'%s %s %s' % (self.verb, ctx, summary)
536 536 # trim to 75 columns by default so it's not stupidly wide in my editor
537 537 # (the 5 more are left for verb)
538 538 maxlen = self.repo.ui.configint(b'histedit', b'linelen')
539 539 maxlen = max(maxlen, 22) # avoid truncating hash
540 540 return stringutil.ellipsis(line, maxlen)
541 541
542 542 def tostate(self):
543 543 """Print an action in format used by histedit state files
544 544 (the first line is a verb, the remainder is the second)
545 545 """
546 546 return b"%s\n%s" % (self.verb, node.hex(self.node))
547 547
548 548 def run(self):
549 549 """Runs the action. The default behavior is simply apply the action's
550 550 rulectx onto the current parentctx."""
551 551 self.applychange()
552 552 self.continuedirty()
553 553 return self.continueclean()
554 554
555 555 def applychange(self):
556 556 """Applies the changes from this action's rulectx onto the current
557 557 parentctx, but does not commit them."""
558 558 repo = self.repo
559 559 rulectx = repo[self.node]
560 560 repo.ui.pushbuffer(error=True, labeled=True)
561 561 hg.update(repo, self.state.parentctxnode, quietempty=True)
562 562 repo.ui.popbuffer()
563 563 stats = applychanges(repo.ui, repo, rulectx, {})
564 564 repo.dirstate.setbranch(rulectx.branch())
565 565 if stats.unresolvedcount:
566 566 raise error.InterventionRequired(
567 567 _(b'Fix up the change (%s %s)')
568 568 % (self.verb, node.short(self.node)),
569 569 hint=_(b'hg histedit --continue to resume'),
570 570 )
571 571
572 572 def continuedirty(self):
573 573 """Continues the action when changes have been applied to the working
574 574 copy. The default behavior is to commit the dirty changes."""
575 575 repo = self.repo
576 576 rulectx = repo[self.node]
577 577
578 578 editor = self.commiteditor()
579 579 commit = commitfuncfor(repo, rulectx)
580 580 if repo.ui.configbool(b'rewrite', b'update-timestamp'):
581 581 date = dateutil.makedate()
582 582 else:
583 583 date = rulectx.date()
584 584 commit(
585 585 text=rulectx.description(),
586 586 user=rulectx.user(),
587 587 date=date,
588 588 extra=rulectx.extra(),
589 589 editor=editor,
590 590 )
591 591
592 592 def commiteditor(self):
593 593 """The editor to be used to edit the commit message."""
594 594 return False
595 595
596 596 def continueclean(self):
597 597 """Continues the action when the working copy is clean. The default
598 598 behavior is to accept the current commit as the new version of the
599 599 rulectx."""
600 600 ctx = self.repo[b'.']
601 601 if ctx.node() == self.state.parentctxnode:
602 602 self.repo.ui.warn(
603 603 _(b'%s: skipping changeset (no changes)\n')
604 604 % node.short(self.node)
605 605 )
606 606 return ctx, [(self.node, tuple())]
607 607 if ctx.node() == self.node:
608 608 # Nothing changed
609 609 return ctx, []
610 610 return ctx, [(self.node, (ctx.node(),))]
611 611
612 612
613 613 def commitfuncfor(repo, src):
614 614 """Build a commit function for the replacement of <src>
615 615
616 616 This function ensure we apply the same treatment to all changesets.
617 617
618 618 - Add a 'histedit_source' entry in extra.
619 619
620 620 Note that fold has its own separated logic because its handling is a bit
621 621 different and not easily factored out of the fold method.
622 622 """
623 623 phasemin = src.phase()
624 624
625 625 def commitfunc(**kwargs):
626 626 overrides = {(b'phases', b'new-commit'): phasemin}
627 627 with repo.ui.configoverride(overrides, b'histedit'):
628 628 extra = kwargs.get('extra', {}).copy()
629 629 extra[b'histedit_source'] = src.hex()
630 630 kwargs['extra'] = extra
631 631 return repo.commit(**kwargs)
632 632
633 633 return commitfunc
634 634
635 635
636 636 def applychanges(ui, repo, ctx, opts):
637 637 """Merge changeset from ctx (only) in the current working directory"""
638 638 wcpar = repo.dirstate.p1()
639 639 if ctx.p1().node() == wcpar:
640 640 # edits are "in place" we do not need to make any merge,
641 641 # just applies changes on parent for editing
642 642 ui.pushbuffer()
643 643 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
644 644 stats = mergemod.updateresult(0, 0, 0, 0)
645 645 ui.popbuffer()
646 646 else:
647 647 try:
648 648 # ui.forcemerge is an internal variable, do not document
649 649 repo.ui.setconfig(
650 650 b'ui', b'forcemerge', opts.get(b'tool', b''), b'histedit'
651 651 )
652 652 stats = mergemod.graft(repo, ctx, labels=[b'local', b'histedit'])
653 653 finally:
654 654 repo.ui.setconfig(b'ui', b'forcemerge', b'', b'histedit')
655 655 return stats
656 656
657 657
658 658 def collapse(repo, firstctx, lastctx, commitopts, skipprompt=False):
659 659 """collapse the set of revisions from first to last as new one.
660 660
661 661 Expected commit options are:
662 662 - message
663 663 - date
664 664 - username
665 665 Commit message is edited in all cases.
666 666
667 667 This function works in memory."""
668 668 ctxs = list(repo.set(b'%d::%d', firstctx.rev(), lastctx.rev()))
669 669 if not ctxs:
670 670 return None
671 671 for c in ctxs:
672 672 if not c.mutable():
673 673 raise error.ParseError(
674 674 _(b"cannot fold into public change %s") % node.short(c.node())
675 675 )
676 676 base = firstctx.p1()
677 677
678 678 # commit a new version of the old changeset, including the update
679 679 # collect all files which might be affected
680 680 files = set()
681 681 for ctx in ctxs:
682 682 files.update(ctx.files())
683 683
684 684 # Recompute copies (avoid recording a -> b -> a)
685 685 copied = copies.pathcopies(base, lastctx)
686 686
687 687 # prune files which were reverted by the updates
688 688 files = [f for f in files if not cmdutil.samefile(f, lastctx, base)]
689 689 # commit version of these files as defined by head
690 690 headmf = lastctx.manifest()
691 691
692 692 def filectxfn(repo, ctx, path):
693 693 if path in headmf:
694 694 fctx = lastctx[path]
695 695 flags = fctx.flags()
696 696 mctx = context.memfilectx(
697 697 repo,
698 698 ctx,
699 699 fctx.path(),
700 700 fctx.data(),
701 701 islink=b'l' in flags,
702 702 isexec=b'x' in flags,
703 703 copysource=copied.get(path),
704 704 )
705 705 return mctx
706 706 return None
707 707
708 708 if commitopts.get(b'message'):
709 709 message = commitopts[b'message']
710 710 else:
711 711 message = firstctx.description()
712 712 user = commitopts.get(b'user')
713 713 date = commitopts.get(b'date')
714 714 extra = commitopts.get(b'extra')
715 715
716 716 parents = (firstctx.p1().node(), firstctx.p2().node())
717 717 editor = None
718 718 if not skipprompt:
719 719 editor = cmdutil.getcommiteditor(edit=True, editform=b'histedit.fold')
720 720 new = context.memctx(
721 721 repo,
722 722 parents=parents,
723 723 text=message,
724 724 files=files,
725 725 filectxfn=filectxfn,
726 726 user=user,
727 727 date=date,
728 728 extra=extra,
729 729 editor=editor,
730 730 )
731 731 return repo.commitctx(new)
732 732
733 733
734 734 def _isdirtywc(repo):
735 735 return repo[None].dirty(missing=True)
736 736
737 737
738 738 def abortdirty():
739 739 raise error.Abort(
740 740 _(b'working copy has pending changes'),
741 741 hint=_(
742 742 b'amend, commit, or revert them and run histedit '
743 743 b'--continue, or abort with histedit --abort'
744 744 ),
745 745 )
746 746
747 747
748 748 def action(verbs, message, priority=False, internal=False):
749 749 def wrap(cls):
750 750 assert not priority or not internal
751 751 verb = verbs[0]
752 752 if priority:
753 753 primaryactions.add(verb)
754 754 elif internal:
755 755 internalactions.add(verb)
756 756 elif len(verbs) > 1:
757 757 secondaryactions.add(verb)
758 758 else:
759 759 tertiaryactions.add(verb)
760 760
761 761 cls.verb = verb
762 762 cls.verbs = verbs
763 763 cls.message = message
764 764 for verb in verbs:
765 765 actiontable[verb] = cls
766 766 return cls
767 767
768 768 return wrap
769 769
770 770
771 771 @action([b'pick', b'p'], _(b'use commit'), priority=True)
772 772 class pick(histeditaction):
773 773 def run(self):
774 774 rulectx = self.repo[self.node]
775 775 if rulectx.p1().node() == self.state.parentctxnode:
776 776 self.repo.ui.debug(b'node %s unchanged\n' % node.short(self.node))
777 777 return rulectx, []
778 778
779 779 return super(pick, self).run()
780 780
781 781
782 782 @action([b'edit', b'e'], _(b'use commit, but stop for amending'), priority=True)
783 783 class edit(histeditaction):
784 784 def run(self):
785 785 repo = self.repo
786 786 rulectx = repo[self.node]
787 787 hg.update(repo, self.state.parentctxnode, quietempty=True)
788 788 applychanges(repo.ui, repo, rulectx, {})
789 789 raise error.InterventionRequired(
790 790 _(b'Editing (%s), you may commit or record as needed now.')
791 791 % node.short(self.node),
792 792 hint=_(b'hg histedit --continue to resume'),
793 793 )
794 794
795 795 def commiteditor(self):
796 796 return cmdutil.getcommiteditor(edit=True, editform=b'histedit.edit')
797 797
798 798
799 799 @action([b'fold', b'f'], _(b'use commit, but combine it with the one above'))
800 800 class fold(histeditaction):
801 801 def verify(self, prev, expected, seen):
802 802 """ Verifies semantic correctness of the fold rule"""
803 803 super(fold, self).verify(prev, expected, seen)
804 804 repo = self.repo
805 805 if not prev:
806 806 c = repo[self.node].p1()
807 807 elif not prev.verb in (b'pick', b'base'):
808 808 return
809 809 else:
810 810 c = repo[prev.node]
811 811 if not c.mutable():
812 812 raise error.ParseError(
813 813 _(b"cannot fold into public change %s") % node.short(c.node())
814 814 )
815 815
816 816 def continuedirty(self):
817 817 repo = self.repo
818 818 rulectx = repo[self.node]
819 819
820 820 commit = commitfuncfor(repo, rulectx)
821 821 commit(
822 822 text=b'fold-temp-revision %s' % node.short(self.node),
823 823 user=rulectx.user(),
824 824 date=rulectx.date(),
825 825 extra=rulectx.extra(),
826 826 )
827 827
828 828 def continueclean(self):
829 829 repo = self.repo
830 830 ctx = repo[b'.']
831 831 rulectx = repo[self.node]
832 832 parentctxnode = self.state.parentctxnode
833 833 if ctx.node() == parentctxnode:
834 834 repo.ui.warn(_(b'%s: empty changeset\n') % node.short(self.node))
835 835 return ctx, [(self.node, (parentctxnode,))]
836 836
837 837 parentctx = repo[parentctxnode]
838 838 newcommits = {
839 839 c.node()
840 840 for c in repo.set(b'(%d::. - %d)', parentctx.rev(), parentctx.rev())
841 841 }
842 842 if not newcommits:
843 843 repo.ui.warn(
844 844 _(
845 845 b'%s: cannot fold - working copy is not a '
846 846 b'descendant of previous commit %s\n'
847 847 )
848 848 % (node.short(self.node), node.short(parentctxnode))
849 849 )
850 850 return ctx, [(self.node, (ctx.node(),))]
851 851
852 852 middlecommits = newcommits.copy()
853 853 middlecommits.discard(ctx.node())
854 854
855 855 return self.finishfold(
856 856 repo.ui, repo, parentctx, rulectx, ctx.node(), middlecommits
857 857 )
858 858
859 859 def skipprompt(self):
860 860 """Returns true if the rule should skip the message editor.
861 861
862 862 For example, 'fold' wants to show an editor, but 'rollup'
863 863 doesn't want to.
864 864 """
865 865 return False
866 866
867 867 def mergedescs(self):
868 868 """Returns true if the rule should merge messages of multiple changes.
869 869
870 870 This exists mainly so that 'rollup' rules can be a subclass of
871 871 'fold'.
872 872 """
873 873 return True
874 874
875 875 def firstdate(self):
876 876 """Returns true if the rule should preserve the date of the first
877 877 change.
878 878
879 879 This exists mainly so that 'rollup' rules can be a subclass of
880 880 'fold'.
881 881 """
882 882 return False
883 883
884 884 def finishfold(self, ui, repo, ctx, oldctx, newnode, internalchanges):
885 885 parent = ctx.p1().node()
886 886 hg.updaterepo(repo, parent, overwrite=False)
887 887 ### prepare new commit data
888 888 commitopts = {}
889 889 commitopts[b'user'] = ctx.user()
890 890 # commit message
891 891 if not self.mergedescs():
892 892 newmessage = ctx.description()
893 893 else:
894 894 newmessage = (
895 895 b'\n***\n'.join(
896 896 [ctx.description()]
897 897 + [repo[r].description() for r in internalchanges]
898 898 + [oldctx.description()]
899 899 )
900 900 + b'\n'
901 901 )
902 902 commitopts[b'message'] = newmessage
903 903 # date
904 904 if self.firstdate():
905 905 commitopts[b'date'] = ctx.date()
906 906 else:
907 907 commitopts[b'date'] = max(ctx.date(), oldctx.date())
908 908 # if date is to be updated to current
909 909 if ui.configbool(b'rewrite', b'update-timestamp'):
910 910 commitopts[b'date'] = dateutil.makedate()
911 911
912 912 extra = ctx.extra().copy()
913 913 # histedit_source
914 914 # note: ctx is likely a temporary commit but that the best we can do
915 915 # here. This is sufficient to solve issue3681 anyway.
916 916 extra[b'histedit_source'] = b'%s,%s' % (ctx.hex(), oldctx.hex())
917 917 commitopts[b'extra'] = extra
918 918 phasemin = max(ctx.phase(), oldctx.phase())
919 919 overrides = {(b'phases', b'new-commit'): phasemin}
920 920 with repo.ui.configoverride(overrides, b'histedit'):
921 921 n = collapse(
922 922 repo,
923 923 ctx,
924 924 repo[newnode],
925 925 commitopts,
926 926 skipprompt=self.skipprompt(),
927 927 )
928 928 if n is None:
929 929 return ctx, []
930 930 hg.updaterepo(repo, n, overwrite=False)
931 931 replacements = [
932 932 (oldctx.node(), (newnode,)),
933 933 (ctx.node(), (n,)),
934 934 (newnode, (n,)),
935 935 ]
936 936 for ich in internalchanges:
937 937 replacements.append((ich, (n,)))
938 938 return repo[n], replacements
939 939
940 940
941 941 @action(
942 942 [b'base', b'b'],
943 943 _(b'checkout changeset and apply further changesets from there'),
944 944 )
945 945 class base(histeditaction):
946 946 def run(self):
947 947 if self.repo[b'.'].node() != self.node:
948 948 mergemod.clean_update(self.repo[self.node])
949 949 return self.continueclean()
950 950
951 951 def continuedirty(self):
952 952 abortdirty()
953 953
954 954 def continueclean(self):
955 955 basectx = self.repo[b'.']
956 956 return basectx, []
957 957
958 958 def _verifynodeconstraints(self, prev, expected, seen):
959 959 # base can only be use with a node not in the edited set
960 960 if self.node in expected:
961 961 msg = _(b'%s "%s" changeset was an edited list candidate')
962 962 raise error.ParseError(
963 963 msg % (self.verb, node.short(self.node)),
964 964 hint=_(b'base must only use unlisted changesets'),
965 965 )
966 966
967 967
968 968 @action(
969 969 [b'_multifold'],
970 970 _(
971 971 """fold subclass used for when multiple folds happen in a row
972 972
973 973 We only want to fire the editor for the folded message once when
974 974 (say) four changes are folded down into a single change. This is
975 975 similar to rollup, but we should preserve both messages so that
976 976 when the last fold operation runs we can show the user all the
977 977 commit messages in their editor.
978 978 """
979 979 ),
980 980 internal=True,
981 981 )
982 982 class _multifold(fold):
983 983 def skipprompt(self):
984 984 return True
985 985
986 986
987 987 @action(
988 988 [b"roll", b"r"],
989 989 _(b"like fold, but discard this commit's description and date"),
990 990 )
991 991 class rollup(fold):
992 992 def mergedescs(self):
993 993 return False
994 994
995 995 def skipprompt(self):
996 996 return True
997 997
998 998 def firstdate(self):
999 999 return True
1000 1000
1001 1001
1002 1002 @action([b"drop", b"d"], _(b'remove commit from history'))
1003 1003 class drop(histeditaction):
1004 1004 def run(self):
1005 1005 parentctx = self.repo[self.state.parentctxnode]
1006 1006 return parentctx, [(self.node, tuple())]
1007 1007
1008 1008
1009 1009 @action(
1010 1010 [b"mess", b"m"],
1011 1011 _(b'edit commit message without changing commit content'),
1012 1012 priority=True,
1013 1013 )
1014 1014 class message(histeditaction):
1015 1015 def commiteditor(self):
1016 1016 return cmdutil.getcommiteditor(edit=True, editform=b'histedit.mess')
1017 1017
1018 1018
1019 1019 def findoutgoing(ui, repo, remote=None, force=False, opts=None):
1020 1020 """utility function to find the first outgoing changeset
1021 1021
1022 1022 Used by initialization code"""
1023 1023 if opts is None:
1024 1024 opts = {}
1025 1025 dest = ui.expandpath(remote or b'default-push', remote or b'default')
1026 1026 dest, branches = hg.parseurl(dest, None)[:2]
1027 1027 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
1028 1028
1029 1029 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
1030 1030 other = hg.peer(repo, opts, dest)
1031 1031
1032 1032 if revs:
1033 1033 revs = [repo.lookup(rev) for rev in revs]
1034 1034
1035 1035 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
1036 1036 if not outgoing.missing:
1037 1037 raise error.Abort(_(b'no outgoing ancestors'))
1038 1038 roots = list(repo.revs(b"roots(%ln)", outgoing.missing))
1039 1039 if len(roots) > 1:
1040 1040 msg = _(b'there are ambiguous outgoing revisions')
1041 1041 hint = _(b"see 'hg help histedit' for more detail")
1042 1042 raise error.Abort(msg, hint=hint)
1043 1043 return repo[roots[0]].node()
1044 1044
1045 1045
1046 1046 # Curses Support
1047 1047 try:
1048 1048 import curses
1049 1049 except ImportError:
1050 1050 curses = None
1051 1051
1052 1052 KEY_LIST = [b'pick', b'edit', b'fold', b'drop', b'mess', b'roll']
1053 1053 ACTION_LABELS = {
1054 1054 b'fold': b'^fold',
1055 1055 b'roll': b'^roll',
1056 1056 }
1057 1057
1058 1058 COLOR_HELP, COLOR_SELECTED, COLOR_OK, COLOR_WARN, COLOR_CURRENT = 1, 2, 3, 4, 5
1059 1059 COLOR_DIFF_ADD_LINE, COLOR_DIFF_DEL_LINE, COLOR_DIFF_OFFSET = 6, 7, 8
1060 1060 COLOR_ROLL, COLOR_ROLL_CURRENT, COLOR_ROLL_SELECTED = 9, 10, 11
1061 1061
1062 1062 E_QUIT, E_HISTEDIT = 1, 2
1063 1063 E_PAGEDOWN, E_PAGEUP, E_LINEUP, E_LINEDOWN, E_RESIZE = 3, 4, 5, 6, 7
1064 1064 MODE_INIT, MODE_PATCH, MODE_RULES, MODE_HELP = 0, 1, 2, 3
1065 1065
1066 1066 KEYTABLE = {
1067 1067 b'global': {
1068 1068 b'h': b'next-action',
1069 1069 b'KEY_RIGHT': b'next-action',
1070 1070 b'l': b'prev-action',
1071 1071 b'KEY_LEFT': b'prev-action',
1072 1072 b'q': b'quit',
1073 1073 b'c': b'histedit',
1074 1074 b'C': b'histedit',
1075 1075 b'v': b'showpatch',
1076 1076 b'?': b'help',
1077 1077 },
1078 1078 MODE_RULES: {
1079 1079 b'd': b'action-drop',
1080 1080 b'e': b'action-edit',
1081 1081 b'f': b'action-fold',
1082 1082 b'm': b'action-mess',
1083 1083 b'p': b'action-pick',
1084 1084 b'r': b'action-roll',
1085 1085 b' ': b'select',
1086 1086 b'j': b'down',
1087 1087 b'k': b'up',
1088 1088 b'KEY_DOWN': b'down',
1089 1089 b'KEY_UP': b'up',
1090 1090 b'J': b'move-down',
1091 1091 b'K': b'move-up',
1092 1092 b'KEY_NPAGE': b'move-down',
1093 1093 b'KEY_PPAGE': b'move-up',
1094 1094 b'0': b'goto', # Used for 0..9
1095 1095 },
1096 1096 MODE_PATCH: {
1097 1097 b' ': b'page-down',
1098 1098 b'KEY_NPAGE': b'page-down',
1099 1099 b'KEY_PPAGE': b'page-up',
1100 1100 b'j': b'line-down',
1101 1101 b'k': b'line-up',
1102 1102 b'KEY_DOWN': b'line-down',
1103 1103 b'KEY_UP': b'line-up',
1104 1104 b'J': b'down',
1105 1105 b'K': b'up',
1106 1106 },
1107 1107 MODE_HELP: {},
1108 1108 }
1109 1109
1110 1110
1111 1111 def screen_size():
1112 1112 return struct.unpack(b'hh', fcntl.ioctl(1, termios.TIOCGWINSZ, b' '))
1113 1113
1114 1114
1115 1115 class histeditrule(object):
1116 1116 def __init__(self, ui, ctx, pos, action=b'pick'):
1117 1117 self.ui = ui
1118 1118 self.ctx = ctx
1119 1119 self.action = action
1120 1120 self.origpos = pos
1121 1121 self.pos = pos
1122 1122 self.conflicts = []
1123 1123
1124 1124 def __bytes__(self):
1125 1125 # Example display of several histeditrules:
1126 1126 #
1127 1127 # #10 pick 316392:06a16c25c053 add option to skip tests
1128 1128 # #11 ^roll 316393:71313c964cc5 <RED>oops a fixup commit</RED>
1129 1129 # #12 pick 316394:ab31f3973b0d include mfbt for mozilla-config.h
1130 1130 # #13 ^fold 316395:14ce5803f4c3 fix warnings
1131 1131 #
1132 1132 # The carets point to the changeset being folded into ("roll this
1133 1133 # changeset into the changeset above").
1134 1134 return b'%s%s' % (self.prefix, self.desc)
1135 1135
1136 1136 __str__ = encoding.strmethod(__bytes__)
1137 1137
1138 1138 @property
1139 1139 def prefix(self):
1140 1140 # Some actions ('fold' and 'roll') combine a patch with a
1141 1141 # previous one. Add a marker showing which patch they apply
1142 1142 # to.
1143 1143 action = ACTION_LABELS.get(self.action, self.action)
1144 1144
1145 1145 h = self.ctx.hex()[0:12]
1146 1146 r = self.ctx.rev()
1147 1147
1148 1148 return b"#%s %s %d:%s " % (
1149 1149 (b'%d' % self.origpos).ljust(2),
1150 1150 action.ljust(6),
1151 1151 r,
1152 1152 h,
1153 1153 )
1154 1154
1155 1155 @property
1156 1156 def desc(self):
1157 1157 summary = (
1158 1158 cmdutil.rendertemplate(
1159 1159 self.ctx, self.ui.config(b'histedit', b'summary-template')
1160 1160 )
1161 1161 or b''
1162 1162 )
1163 1163 if summary:
1164 1164 return summary
1165 1165 # This is split off from the prefix property so that we can
1166 1166 # separately make the description for 'roll' red (since it
1167 1167 # will get discarded).
1168 1168 return self.ctx.description().splitlines()[0].strip()
1169 1169
1170 1170 def checkconflicts(self, other):
1171 1171 if other.pos > self.pos and other.origpos <= self.origpos:
1172 1172 if set(other.ctx.files()) & set(self.ctx.files()) != set():
1173 1173 self.conflicts.append(other)
1174 1174 return self.conflicts
1175 1175
1176 1176 if other in self.conflicts:
1177 1177 self.conflicts.remove(other)
1178 1178 return self.conflicts
1179 1179
1180 1180
1181 1181 # ============ EVENTS ===============
1182 1182 def movecursor(state, oldpos, newpos):
1183 1183 '''Change the rule/changeset that the cursor is pointing to, regardless of
1184 1184 current mode (you can switch between patches from the view patch window).'''
1185 1185 state[b'pos'] = newpos
1186 1186
1187 1187 mode, _ = state[b'mode']
1188 1188 if mode == MODE_RULES:
1189 1189 # Scroll through the list by updating the view for MODE_RULES, so that
1190 1190 # even if we are not currently viewing the rules, switching back will
1191 1191 # result in the cursor's rule being visible.
1192 1192 modestate = state[b'modes'][MODE_RULES]
1193 1193 if newpos < modestate[b'line_offset']:
1194 1194 modestate[b'line_offset'] = newpos
1195 1195 elif newpos > modestate[b'line_offset'] + state[b'page_height'] - 1:
1196 1196 modestate[b'line_offset'] = newpos - state[b'page_height'] + 1
1197 1197
1198 1198 # Reset the patch view region to the top of the new patch.
1199 1199 state[b'modes'][MODE_PATCH][b'line_offset'] = 0
1200 1200
1201 1201
1202 1202 def changemode(state, mode):
1203 1203 curmode, _ = state[b'mode']
1204 1204 state[b'mode'] = (mode, curmode)
1205 1205 if mode == MODE_PATCH:
1206 1206 state[b'modes'][MODE_PATCH][b'patchcontents'] = patchcontents(state)
1207 1207
1208 1208
1209 1209 def makeselection(state, pos):
1210 1210 state[b'selected'] = pos
1211 1211
1212 1212
1213 1213 def swap(state, oldpos, newpos):
1214 1214 """Swap two positions and calculate necessary conflicts in
1215 1215 O(|newpos-oldpos|) time"""
1216 1216
1217 1217 rules = state[b'rules']
1218 1218 assert 0 <= oldpos < len(rules) and 0 <= newpos < len(rules)
1219 1219
1220 1220 rules[oldpos], rules[newpos] = rules[newpos], rules[oldpos]
1221 1221
1222 1222 # TODO: swap should not know about histeditrule's internals
1223 1223 rules[newpos].pos = newpos
1224 1224 rules[oldpos].pos = oldpos
1225 1225
1226 1226 start = min(oldpos, newpos)
1227 1227 end = max(oldpos, newpos)
1228 1228 for r in pycompat.xrange(start, end + 1):
1229 1229 rules[newpos].checkconflicts(rules[r])
1230 1230 rules[oldpos].checkconflicts(rules[r])
1231 1231
1232 1232 if state[b'selected']:
1233 1233 makeselection(state, newpos)
1234 1234
1235 1235
1236 1236 def changeaction(state, pos, action):
1237 1237 """Change the action state on the given position to the new action"""
1238 1238 rules = state[b'rules']
1239 1239 assert 0 <= pos < len(rules)
1240 1240 rules[pos].action = action
1241 1241
1242 1242
1243 1243 def cycleaction(state, pos, next=False):
1244 1244 """Changes the action state the next or the previous action from
1245 1245 the action list"""
1246 1246 rules = state[b'rules']
1247 1247 assert 0 <= pos < len(rules)
1248 1248 current = rules[pos].action
1249 1249
1250 1250 assert current in KEY_LIST
1251 1251
1252 1252 index = KEY_LIST.index(current)
1253 1253 if next:
1254 1254 index += 1
1255 1255 else:
1256 1256 index -= 1
1257 1257 changeaction(state, pos, KEY_LIST[index % len(KEY_LIST)])
1258 1258
1259 1259
1260 1260 def changeview(state, delta, unit):
1261 1261 '''Change the region of whatever is being viewed (a patch or the list of
1262 1262 changesets). 'delta' is an amount (+/- 1) and 'unit' is 'page' or 'line'.'''
1263 1263 mode, _ = state[b'mode']
1264 1264 if mode != MODE_PATCH:
1265 1265 return
1266 1266 mode_state = state[b'modes'][mode]
1267 1267 num_lines = len(mode_state[b'patchcontents'])
1268 1268 page_height = state[b'page_height']
1269 1269 unit = page_height if unit == b'page' else 1
1270 1270 num_pages = 1 + (num_lines - 1) // page_height
1271 1271 max_offset = (num_pages - 1) * page_height
1272 1272 newline = mode_state[b'line_offset'] + delta * unit
1273 1273 mode_state[b'line_offset'] = max(0, min(max_offset, newline))
1274 1274
1275 1275
1276 1276 def event(state, ch):
1277 1277 """Change state based on the current character input
1278 1278
1279 1279 This takes the current state and based on the current character input from
1280 1280 the user we change the state.
1281 1281 """
1282 1282 selected = state[b'selected']
1283 1283 oldpos = state[b'pos']
1284 1284 rules = state[b'rules']
1285 1285
1286 1286 if ch in (curses.KEY_RESIZE, b"KEY_RESIZE"):
1287 1287 return E_RESIZE
1288 1288
1289 1289 lookup_ch = ch
1290 1290 if ch is not None and b'0' <= ch <= b'9':
1291 1291 lookup_ch = b'0'
1292 1292
1293 1293 curmode, prevmode = state[b'mode']
1294 1294 action = KEYTABLE[curmode].get(
1295 1295 lookup_ch, KEYTABLE[b'global'].get(lookup_ch)
1296 1296 )
1297 1297 if action is None:
1298 1298 return
1299 1299 if action in (b'down', b'move-down'):
1300 1300 newpos = min(oldpos + 1, len(rules) - 1)
1301 1301 movecursor(state, oldpos, newpos)
1302 1302 if selected is not None or action == b'move-down':
1303 1303 swap(state, oldpos, newpos)
1304 1304 elif action in (b'up', b'move-up'):
1305 1305 newpos = max(0, oldpos - 1)
1306 1306 movecursor(state, oldpos, newpos)
1307 1307 if selected is not None or action == b'move-up':
1308 1308 swap(state, oldpos, newpos)
1309 1309 elif action == b'next-action':
1310 1310 cycleaction(state, oldpos, next=True)
1311 1311 elif action == b'prev-action':
1312 1312 cycleaction(state, oldpos, next=False)
1313 1313 elif action == b'select':
1314 1314 selected = oldpos if selected is None else None
1315 1315 makeselection(state, selected)
1316 1316 elif action == b'goto' and int(ch) < len(rules) and len(rules) <= 10:
1317 1317 newrule = next((r for r in rules if r.origpos == int(ch)))
1318 1318 movecursor(state, oldpos, newrule.pos)
1319 1319 if selected is not None:
1320 1320 swap(state, oldpos, newrule.pos)
1321 1321 elif action.startswith(b'action-'):
1322 1322 changeaction(state, oldpos, action[7:])
1323 1323 elif action == b'showpatch':
1324 1324 changemode(state, MODE_PATCH if curmode != MODE_PATCH else prevmode)
1325 1325 elif action == b'help':
1326 1326 changemode(state, MODE_HELP if curmode != MODE_HELP else prevmode)
1327 1327 elif action == b'quit':
1328 1328 return E_QUIT
1329 1329 elif action == b'histedit':
1330 1330 return E_HISTEDIT
1331 1331 elif action == b'page-down':
1332 1332 return E_PAGEDOWN
1333 1333 elif action == b'page-up':
1334 1334 return E_PAGEUP
1335 1335 elif action == b'line-down':
1336 1336 return E_LINEDOWN
1337 1337 elif action == b'line-up':
1338 1338 return E_LINEUP
1339 1339
1340 1340
1341 1341 def makecommands(rules):
1342 1342 """Returns a list of commands consumable by histedit --commands based on
1343 1343 our list of rules"""
1344 1344 commands = []
1345 1345 for rules in rules:
1346 1346 commands.append(b'%s %s\n' % (rules.action, rules.ctx))
1347 1347 return commands
1348 1348
1349 1349
1350 1350 def addln(win, y, x, line, color=None):
1351 1351 """Add a line to the given window left padding but 100% filled with
1352 1352 whitespace characters, so that the color appears on the whole line"""
1353 1353 maxy, maxx = win.getmaxyx()
1354 1354 length = maxx - 1 - x
1355 1355 line = bytes(line).ljust(length)[:length]
1356 1356 if y < 0:
1357 1357 y = maxy + y
1358 1358 if x < 0:
1359 1359 x = maxx + x
1360 1360 if color:
1361 1361 win.addstr(y, x, line, color)
1362 1362 else:
1363 1363 win.addstr(y, x, line)
1364 1364
1365 1365
1366 1366 def _trunc_head(line, n):
1367 1367 if len(line) <= n:
1368 1368 return line
1369 1369 return b'> ' + line[-(n - 2) :]
1370 1370
1371 1371
1372 1372 def _trunc_tail(line, n):
1373 1373 if len(line) <= n:
1374 1374 return line
1375 1375 return line[: n - 2] + b' >'
1376 1376
1377 1377
1378 1378 def patchcontents(state):
1379 1379 repo = state[b'repo']
1380 1380 rule = state[b'rules'][state[b'pos']]
1381 1381 displayer = logcmdutil.changesetdisplayer(
1382 1382 repo.ui, repo, {b"patch": True, b"template": b"status"}, buffered=True
1383 1383 )
1384 1384 overrides = {(b'ui', b'verbose'): True}
1385 1385 with repo.ui.configoverride(overrides, source=b'histedit'):
1386 1386 displayer.show(rule.ctx)
1387 1387 displayer.close()
1388 1388 return displayer.hunk[rule.ctx.rev()].splitlines()
1389 1389
1390 1390
1391 1391 def _chisteditmain(repo, rules, stdscr):
1392 1392 try:
1393 1393 curses.use_default_colors()
1394 1394 except curses.error:
1395 1395 pass
1396 1396
1397 1397 # initialize color pattern
1398 1398 curses.init_pair(COLOR_HELP, curses.COLOR_WHITE, curses.COLOR_BLUE)
1399 1399 curses.init_pair(COLOR_SELECTED, curses.COLOR_BLACK, curses.COLOR_WHITE)
1400 1400 curses.init_pair(COLOR_WARN, curses.COLOR_BLACK, curses.COLOR_YELLOW)
1401 1401 curses.init_pair(COLOR_OK, curses.COLOR_BLACK, curses.COLOR_GREEN)
1402 1402 curses.init_pair(COLOR_CURRENT, curses.COLOR_WHITE, curses.COLOR_MAGENTA)
1403 1403 curses.init_pair(COLOR_DIFF_ADD_LINE, curses.COLOR_GREEN, -1)
1404 1404 curses.init_pair(COLOR_DIFF_DEL_LINE, curses.COLOR_RED, -1)
1405 1405 curses.init_pair(COLOR_DIFF_OFFSET, curses.COLOR_MAGENTA, -1)
1406 1406 curses.init_pair(COLOR_ROLL, curses.COLOR_RED, -1)
1407 1407 curses.init_pair(
1408 1408 COLOR_ROLL_CURRENT, curses.COLOR_BLACK, curses.COLOR_MAGENTA
1409 1409 )
1410 1410 curses.init_pair(COLOR_ROLL_SELECTED, curses.COLOR_RED, curses.COLOR_WHITE)
1411 1411
1412 1412 # don't display the cursor
1413 1413 try:
1414 1414 curses.curs_set(0)
1415 1415 except curses.error:
1416 1416 pass
1417 1417
1418 1418 def rendercommit(win, state):
1419 1419 """Renders the commit window that shows the log of the current selected
1420 1420 commit"""
1421 1421 pos = state[b'pos']
1422 1422 rules = state[b'rules']
1423 1423 rule = rules[pos]
1424 1424
1425 1425 ctx = rule.ctx
1426 1426 win.box()
1427 1427
1428 1428 maxy, maxx = win.getmaxyx()
1429 1429 length = maxx - 3
1430 1430
1431 1431 line = b"changeset: %d:%s" % (ctx.rev(), ctx.hex()[:12])
1432 1432 win.addstr(1, 1, line[:length])
1433 1433
1434 1434 line = b"user: %s" % ctx.user()
1435 1435 win.addstr(2, 1, line[:length])
1436 1436
1437 1437 bms = repo.nodebookmarks(ctx.node())
1438 1438 line = b"bookmark: %s" % b' '.join(bms)
1439 1439 win.addstr(3, 1, line[:length])
1440 1440
1441 1441 line = b"summary: %s" % (ctx.description().splitlines()[0])
1442 1442 win.addstr(4, 1, line[:length])
1443 1443
1444 1444 line = b"files: "
1445 1445 win.addstr(5, 1, line)
1446 1446 fnx = 1 + len(line)
1447 1447 fnmaxx = length - fnx + 1
1448 1448 y = 5
1449 1449 fnmaxn = maxy - (1 + y) - 1
1450 1450 files = ctx.files()
1451 1451 for i, line1 in enumerate(files):
1452 1452 if len(files) > fnmaxn and i == fnmaxn - 1:
1453 1453 win.addstr(y, fnx, _trunc_tail(b','.join(files[i:]), fnmaxx))
1454 1454 y = y + 1
1455 1455 break
1456 1456 win.addstr(y, fnx, _trunc_head(line1, fnmaxx))
1457 1457 y = y + 1
1458 1458
1459 1459 conflicts = rule.conflicts
1460 1460 if len(conflicts) > 0:
1461 1461 conflictstr = b','.join(map(lambda r: r.ctx.hex()[:12], conflicts))
1462 1462 conflictstr = b"changed files overlap with %s" % conflictstr
1463 1463 else:
1464 1464 conflictstr = b'no overlap'
1465 1465
1466 1466 win.addstr(y, 1, conflictstr[:length])
1467 1467 win.noutrefresh()
1468 1468
1469 1469 def helplines(mode):
1470 1470 if mode == MODE_PATCH:
1471 1471 help = b"""\
1472 1472 ?: help, k/up: line up, j/down: line down, v: stop viewing patch
1473 1473 pgup: prev page, space/pgdn: next page, c: commit, q: abort
1474 1474 """
1475 1475 else:
1476 1476 help = b"""\
1477 1477 ?: help, k/up: move up, j/down: move down, space: select, v: view patch
1478 1478 d: drop, e: edit, f: fold, m: mess, p: pick, r: roll
1479 1479 pgup/K: move patch up, pgdn/J: move patch down, c: commit, q: abort
1480 1480 """
1481 1481 return help.splitlines()
1482 1482
1483 1483 def renderhelp(win, state):
1484 1484 maxy, maxx = win.getmaxyx()
1485 1485 mode, _ = state[b'mode']
1486 1486 for y, line in enumerate(helplines(mode)):
1487 1487 if y >= maxy:
1488 1488 break
1489 1489 addln(win, y, 0, line, curses.color_pair(COLOR_HELP))
1490 1490 win.noutrefresh()
1491 1491
1492 1492 def renderrules(rulesscr, state):
1493 1493 rules = state[b'rules']
1494 1494 pos = state[b'pos']
1495 1495 selected = state[b'selected']
1496 1496 start = state[b'modes'][MODE_RULES][b'line_offset']
1497 1497
1498 1498 conflicts = [r.ctx for r in rules if r.conflicts]
1499 1499 if len(conflicts) > 0:
1500 1500 line = b"potential conflict in %s" % b','.join(
1501 1501 map(pycompat.bytestr, conflicts)
1502 1502 )
1503 1503 addln(rulesscr, -1, 0, line, curses.color_pair(COLOR_WARN))
1504 1504
1505 1505 for y, rule in enumerate(rules[start:]):
1506 1506 if y >= state[b'page_height']:
1507 1507 break
1508 1508 if len(rule.conflicts) > 0:
1509 1509 rulesscr.addstr(y, 0, b" ", curses.color_pair(COLOR_WARN))
1510 1510 else:
1511 1511 rulesscr.addstr(y, 0, b" ", curses.COLOR_BLACK)
1512 1512
1513 1513 if y + start == selected:
1514 1514 rollcolor = COLOR_ROLL_SELECTED
1515 1515 addln(rulesscr, y, 2, rule, curses.color_pair(COLOR_SELECTED))
1516 1516 elif y + start == pos:
1517 1517 rollcolor = COLOR_ROLL_CURRENT
1518 1518 addln(
1519 1519 rulesscr,
1520 1520 y,
1521 1521 2,
1522 1522 rule,
1523 1523 curses.color_pair(COLOR_CURRENT) | curses.A_BOLD,
1524 1524 )
1525 1525 else:
1526 1526 rollcolor = COLOR_ROLL
1527 1527 addln(rulesscr, y, 2, rule)
1528 1528
1529 1529 if rule.action == b'roll':
1530 1530 rulesscr.addstr(
1531 1531 y,
1532 1532 2 + len(rule.prefix),
1533 1533 rule.desc,
1534 1534 curses.color_pair(rollcolor),
1535 1535 )
1536 1536
1537 1537 rulesscr.noutrefresh()
1538 1538
1539 1539 def renderstring(win, state, output, diffcolors=False):
1540 1540 maxy, maxx = win.getmaxyx()
1541 1541 length = min(maxy - 1, len(output))
1542 1542 for y in range(0, length):
1543 1543 line = output[y]
1544 1544 if diffcolors:
1545 1545 if line and line[0] == b'+':
1546 1546 win.addstr(
1547 1547 y, 0, line, curses.color_pair(COLOR_DIFF_ADD_LINE)
1548 1548 )
1549 1549 elif line and line[0] == b'-':
1550 1550 win.addstr(
1551 1551 y, 0, line, curses.color_pair(COLOR_DIFF_DEL_LINE)
1552 1552 )
1553 1553 elif line.startswith(b'@@ '):
1554 1554 win.addstr(y, 0, line, curses.color_pair(COLOR_DIFF_OFFSET))
1555 1555 else:
1556 1556 win.addstr(y, 0, line)
1557 1557 else:
1558 1558 win.addstr(y, 0, line)
1559 1559 win.noutrefresh()
1560 1560
1561 1561 def renderpatch(win, state):
1562 1562 start = state[b'modes'][MODE_PATCH][b'line_offset']
1563 1563 content = state[b'modes'][MODE_PATCH][b'patchcontents']
1564 1564 renderstring(win, state, content[start:], diffcolors=True)
1565 1565
1566 1566 def layout(mode):
1567 1567 maxy, maxx = stdscr.getmaxyx()
1568 1568 helplen = len(helplines(mode))
1569 1569 return {
1570 1570 b'commit': (12, maxx),
1571 1571 b'help': (helplen, maxx),
1572 1572 b'main': (maxy - helplen - 12, maxx),
1573 1573 }
1574 1574
1575 1575 def drawvertwin(size, y, x):
1576 1576 win = curses.newwin(size[0], size[1], y, x)
1577 1577 y += size[0]
1578 1578 return win, y, x
1579 1579
1580 1580 state = {
1581 1581 b'pos': 0,
1582 1582 b'rules': rules,
1583 1583 b'selected': None,
1584 1584 b'mode': (MODE_INIT, MODE_INIT),
1585 1585 b'page_height': None,
1586 1586 b'modes': {
1587 1587 MODE_RULES: {b'line_offset': 0,},
1588 1588 MODE_PATCH: {b'line_offset': 0,},
1589 1589 },
1590 1590 b'repo': repo,
1591 1591 }
1592 1592
1593 1593 # eventloop
1594 1594 ch = None
1595 1595 stdscr.clear()
1596 1596 stdscr.refresh()
1597 1597 while True:
1598 1598 try:
1599 1599 oldmode, _ = state[b'mode']
1600 1600 if oldmode == MODE_INIT:
1601 1601 changemode(state, MODE_RULES)
1602 1602 e = event(state, ch)
1603 1603
1604 1604 if e == E_QUIT:
1605 1605 return False
1606 1606 if e == E_HISTEDIT:
1607 1607 return state[b'rules']
1608 1608 else:
1609 1609 if e == E_RESIZE:
1610 1610 size = screen_size()
1611 1611 if size != stdscr.getmaxyx():
1612 1612 curses.resizeterm(*size)
1613 1613
1614 1614 curmode, _ = state[b'mode']
1615 1615 sizes = layout(curmode)
1616 1616 if curmode != oldmode:
1617 1617 state[b'page_height'] = sizes[b'main'][0]
1618 1618 # Adjust the view to fit the current screen size.
1619 1619 movecursor(state, state[b'pos'], state[b'pos'])
1620 1620
1621 1621 # Pack the windows against the top, each pane spread across the
1622 1622 # full width of the screen.
1623 1623 y, x = (0, 0)
1624 1624 helpwin, y, x = drawvertwin(sizes[b'help'], y, x)
1625 1625 mainwin, y, x = drawvertwin(sizes[b'main'], y, x)
1626 1626 commitwin, y, x = drawvertwin(sizes[b'commit'], y, x)
1627 1627
1628 1628 if e in (E_PAGEDOWN, E_PAGEUP, E_LINEDOWN, E_LINEUP):
1629 1629 if e == E_PAGEDOWN:
1630 1630 changeview(state, +1, b'page')
1631 1631 elif e == E_PAGEUP:
1632 1632 changeview(state, -1, b'page')
1633 1633 elif e == E_LINEDOWN:
1634 1634 changeview(state, +1, b'line')
1635 1635 elif e == E_LINEUP:
1636 1636 changeview(state, -1, b'line')
1637 1637
1638 1638 # start rendering
1639 1639 commitwin.erase()
1640 1640 helpwin.erase()
1641 1641 mainwin.erase()
1642 1642 if curmode == MODE_PATCH:
1643 1643 renderpatch(mainwin, state)
1644 1644 elif curmode == MODE_HELP:
1645 1645 renderstring(mainwin, state, __doc__.strip().splitlines())
1646 1646 else:
1647 1647 renderrules(mainwin, state)
1648 1648 rendercommit(commitwin, state)
1649 1649 renderhelp(helpwin, state)
1650 1650 curses.doupdate()
1651 1651 # done rendering
1652 1652 ch = encoding.strtolocal(stdscr.getkey())
1653 1653 except curses.error:
1654 1654 pass
1655 1655
1656 1656
1657 1657 def _chistedit(ui, repo, freeargs, opts):
1658 1658 """interactively edit changeset history via a curses interface
1659 1659
1660 1660 Provides a ncurses interface to histedit. Press ? in chistedit mode
1661 1661 to see an extensive help. Requires python-curses to be installed."""
1662 1662
1663 1663 if curses is None:
1664 1664 raise error.Abort(_(b"Python curses library required"))
1665 1665
1666 1666 # disable color
1667 1667 ui._colormode = None
1668 1668
1669 1669 try:
1670 1670 keep = opts.get(b'keep')
1671 1671 revs = opts.get(b'rev', [])[:]
1672 1672 cmdutil.checkunfinished(repo)
1673 1673 cmdutil.bailifchanged(repo)
1674 1674
1675 1675 if os.path.exists(os.path.join(repo.path, b'histedit-state')):
1676 1676 raise error.Abort(
1677 1677 _(
1678 1678 b'history edit already in progress, try '
1679 1679 b'--continue or --abort'
1680 1680 )
1681 1681 )
1682 1682 revs.extend(freeargs)
1683 1683 if not revs:
1684 1684 defaultrev = destutil.desthistedit(ui, repo)
1685 1685 if defaultrev is not None:
1686 1686 revs.append(defaultrev)
1687 1687 if len(revs) != 1:
1688 1688 raise error.Abort(
1689 1689 _(b'histedit requires exactly one ancestor revision')
1690 1690 )
1691 1691
1692 1692 rr = list(repo.set(b'roots(%ld)', scmutil.revrange(repo, revs)))
1693 1693 if len(rr) != 1:
1694 1694 raise error.Abort(
1695 1695 _(
1696 1696 b'The specified revisions must have '
1697 1697 b'exactly one common root'
1698 1698 )
1699 1699 )
1700 1700 root = rr[0].node()
1701 1701
1702 1702 topmost = repo.dirstate.p1()
1703 1703 revs = between(repo, root, topmost, keep)
1704 1704 if not revs:
1705 1705 raise error.Abort(
1706 1706 _(b'%s is not an ancestor of working directory')
1707 1707 % node.short(root)
1708 1708 )
1709 1709
1710 1710 ctxs = []
1711 1711 for i, r in enumerate(revs):
1712 1712 ctxs.append(histeditrule(ui, repo[r], i))
1713 1713 # Curses requires setting the locale or it will default to the C
1714 1714 # locale. This sets the locale to the user's default system
1715 1715 # locale.
1716 1716 locale.setlocale(locale.LC_ALL, '')
1717 1717 rc = curses.wrapper(functools.partial(_chisteditmain, repo, ctxs))
1718 1718 curses.echo()
1719 1719 curses.endwin()
1720 1720 if rc is False:
1721 1721 ui.write(_(b"histedit aborted\n"))
1722 1722 return 0
1723 1723 if type(rc) is list:
1724 1724 ui.status(_(b"performing changes\n"))
1725 1725 rules = makecommands(rc)
1726 1726 with repo.vfs(b'chistedit', b'w+') as fp:
1727 1727 for r in rules:
1728 1728 fp.write(r)
1729 1729 opts[b'commands'] = fp.name
1730 1730 return _texthistedit(ui, repo, freeargs, opts)
1731 1731 except KeyboardInterrupt:
1732 1732 pass
1733 1733 return -1
1734 1734
1735 1735
1736 1736 @command(
1737 1737 b'histedit',
1738 1738 [
1739 1739 (
1740 1740 b'',
1741 1741 b'commands',
1742 1742 b'',
1743 1743 _(b'read history edits from the specified file'),
1744 1744 _(b'FILE'),
1745 1745 ),
1746 1746 (b'c', b'continue', False, _(b'continue an edit already in progress')),
1747 1747 (b'', b'edit-plan', False, _(b'edit remaining actions list')),
1748 1748 (
1749 1749 b'k',
1750 1750 b'keep',
1751 1751 False,
1752 1752 _(b"don't strip old nodes after edit is complete"),
1753 1753 ),
1754 1754 (b'', b'abort', False, _(b'abort an edit in progress')),
1755 1755 (b'o', b'outgoing', False, _(b'changesets not found in destination')),
1756 1756 (
1757 1757 b'f',
1758 1758 b'force',
1759 1759 False,
1760 1760 _(b'force outgoing even for unrelated repositories'),
1761 1761 ),
1762 1762 (b'r', b'rev', [], _(b'first revision to be edited'), _(b'REV')),
1763 1763 ]
1764 1764 + cmdutil.formatteropts,
1765 1765 _(b"[OPTIONS] ([ANCESTOR] | --outgoing [URL])"),
1766 1766 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
1767 1767 )
1768 1768 def histedit(ui, repo, *freeargs, **opts):
1769 1769 """interactively edit changeset history
1770 1770
1771 1771 This command lets you edit a linear series of changesets (up to
1772 1772 and including the working directory, which should be clean).
1773 1773 You can:
1774 1774
1775 1775 - `pick` to [re]order a changeset
1776 1776
1777 1777 - `drop` to omit changeset
1778 1778
1779 1779 - `mess` to reword the changeset commit message
1780 1780
1781 1781 - `fold` to combine it with the preceding changeset (using the later date)
1782 1782
1783 1783 - `roll` like fold, but discarding this commit's description and date
1784 1784
1785 1785 - `edit` to edit this changeset (preserving date)
1786 1786
1787 1787 - `base` to checkout changeset and apply further changesets from there
1788 1788
1789 1789 There are a number of ways to select the root changeset:
1790 1790
1791 1791 - Specify ANCESTOR directly
1792 1792
1793 1793 - Use --outgoing -- it will be the first linear changeset not
1794 1794 included in destination. (See :hg:`help config.paths.default-push`)
1795 1795
1796 1796 - Otherwise, the value from the "histedit.defaultrev" config option
1797 1797 is used as a revset to select the base revision when ANCESTOR is not
1798 1798 specified. The first revision returned by the revset is used. By
1799 1799 default, this selects the editable history that is unique to the
1800 1800 ancestry of the working directory.
1801 1801
1802 1802 .. container:: verbose
1803 1803
1804 1804 If you use --outgoing, this command will abort if there are ambiguous
1805 1805 outgoing revisions. For example, if there are multiple branches
1806 1806 containing outgoing revisions.
1807 1807
1808 1808 Use "min(outgoing() and ::.)" or similar revset specification
1809 1809 instead of --outgoing to specify edit target revision exactly in
1810 1810 such ambiguous situation. See :hg:`help revsets` for detail about
1811 1811 selecting revisions.
1812 1812
1813 1813 .. container:: verbose
1814 1814
1815 1815 Examples:
1816 1816
1817 1817 - A number of changes have been made.
1818 1818 Revision 3 is no longer needed.
1819 1819
1820 1820 Start history editing from revision 3::
1821 1821
1822 1822 hg histedit -r 3
1823 1823
1824 1824 An editor opens, containing the list of revisions,
1825 1825 with specific actions specified::
1826 1826
1827 1827 pick 5339bf82f0ca 3 Zworgle the foobar
1828 1828 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1829 1829 pick 0a9639fcda9d 5 Morgify the cromulancy
1830 1830
1831 1831 Additional information about the possible actions
1832 1832 to take appears below the list of revisions.
1833 1833
1834 1834 To remove revision 3 from the history,
1835 1835 its action (at the beginning of the relevant line)
1836 1836 is changed to 'drop'::
1837 1837
1838 1838 drop 5339bf82f0ca 3 Zworgle the foobar
1839 1839 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1840 1840 pick 0a9639fcda9d 5 Morgify the cromulancy
1841 1841
1842 1842 - A number of changes have been made.
1843 1843 Revision 2 and 4 need to be swapped.
1844 1844
1845 1845 Start history editing from revision 2::
1846 1846
1847 1847 hg histedit -r 2
1848 1848
1849 1849 An editor opens, containing the list of revisions,
1850 1850 with specific actions specified::
1851 1851
1852 1852 pick 252a1af424ad 2 Blorb a morgwazzle
1853 1853 pick 5339bf82f0ca 3 Zworgle the foobar
1854 1854 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1855 1855
1856 1856 To swap revision 2 and 4, its lines are swapped
1857 1857 in the editor::
1858 1858
1859 1859 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1860 1860 pick 5339bf82f0ca 3 Zworgle the foobar
1861 1861 pick 252a1af424ad 2 Blorb a morgwazzle
1862 1862
1863 1863 Returns 0 on success, 1 if user intervention is required (not only
1864 1864 for intentional "edit" command, but also for resolving unexpected
1865 1865 conflicts).
1866 1866 """
1867 1867 opts = pycompat.byteskwargs(opts)
1868 1868
1869 1869 # kludge: _chistedit only works for starting an edit, not aborting
1870 1870 # or continuing, so fall back to regular _texthistedit for those
1871 1871 # operations.
1872 1872 if ui.interface(b'histedit') == b'curses' and _getgoal(opts) == goalnew:
1873 1873 return _chistedit(ui, repo, freeargs, opts)
1874 1874 return _texthistedit(ui, repo, freeargs, opts)
1875 1875
1876 1876
1877 1877 def _texthistedit(ui, repo, freeargs, opts):
1878 1878 state = histeditstate(repo)
1879 1879 with repo.wlock() as wlock, repo.lock() as lock:
1880 1880 state.wlock = wlock
1881 1881 state.lock = lock
1882 1882 _histedit(ui, repo, state, freeargs, opts)
1883 1883
1884 1884
1885 1885 goalcontinue = b'continue'
1886 1886 goalabort = b'abort'
1887 1887 goaleditplan = b'edit-plan'
1888 1888 goalnew = b'new'
1889 1889
1890 1890
1891 1891 def _getgoal(opts):
1892 1892 if opts.get(b'continue'):
1893 1893 return goalcontinue
1894 1894 if opts.get(b'abort'):
1895 1895 return goalabort
1896 1896 if opts.get(b'edit_plan'):
1897 1897 return goaleditplan
1898 1898 return goalnew
1899 1899
1900 1900
1901 1901 def _readfile(ui, path):
1902 1902 if path == b'-':
1903 1903 with ui.timeblockedsection(b'histedit'):
1904 1904 return ui.fin.read()
1905 1905 else:
1906 1906 with open(path, b'rb') as f:
1907 1907 return f.read()
1908 1908
1909 1909
1910 1910 def _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs):
1911 1911 # TODO only abort if we try to histedit mq patches, not just
1912 1912 # blanket if mq patches are applied somewhere
1913 1913 mq = getattr(repo, 'mq', None)
1914 1914 if mq and mq.applied:
1915 1915 raise error.Abort(_(b'source has mq patches applied'))
1916 1916
1917 1917 # basic argument incompatibility processing
1918 1918 outg = opts.get(b'outgoing')
1919 1919 editplan = opts.get(b'edit_plan')
1920 1920 abort = opts.get(b'abort')
1921 1921 force = opts.get(b'force')
1922 1922 if force and not outg:
1923 1923 raise error.Abort(_(b'--force only allowed with --outgoing'))
1924 1924 if goal == b'continue':
1925 1925 if any((outg, abort, revs, freeargs, rules, editplan)):
1926 1926 raise error.Abort(_(b'no arguments allowed with --continue'))
1927 1927 elif goal == b'abort':
1928 1928 if any((outg, revs, freeargs, rules, editplan)):
1929 1929 raise error.Abort(_(b'no arguments allowed with --abort'))
1930 1930 elif goal == b'edit-plan':
1931 1931 if any((outg, revs, freeargs)):
1932 1932 raise error.Abort(
1933 1933 _(b'only --commands argument allowed with --edit-plan')
1934 1934 )
1935 1935 else:
1936 1936 if state.inprogress():
1937 1937 raise error.Abort(
1938 1938 _(
1939 1939 b'history edit already in progress, try '
1940 1940 b'--continue or --abort'
1941 1941 )
1942 1942 )
1943 1943 if outg:
1944 1944 if revs:
1945 1945 raise error.Abort(_(b'no revisions allowed with --outgoing'))
1946 1946 if len(freeargs) > 1:
1947 1947 raise error.Abort(
1948 1948 _(b'only one repo argument allowed with --outgoing')
1949 1949 )
1950 1950 else:
1951 1951 revs.extend(freeargs)
1952 1952 if len(revs) == 0:
1953 1953 defaultrev = destutil.desthistedit(ui, repo)
1954 1954 if defaultrev is not None:
1955 1955 revs.append(defaultrev)
1956 1956
1957 1957 if len(revs) != 1:
1958 1958 raise error.Abort(
1959 1959 _(b'histedit requires exactly one ancestor revision')
1960 1960 )
1961 1961
1962 1962
1963 1963 def _histedit(ui, repo, state, freeargs, opts):
1964 1964 fm = ui.formatter(b'histedit', opts)
1965 1965 fm.startitem()
1966 1966 goal = _getgoal(opts)
1967 1967 revs = opts.get(b'rev', [])
1968 1968 nobackup = not ui.configbool(b'rewrite', b'backup-bundle')
1969 1969 rules = opts.get(b'commands', b'')
1970 1970 state.keep = opts.get(b'keep', False)
1971 1971
1972 1972 _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs)
1973 1973
1974 1974 hastags = False
1975 1975 if revs:
1976 1976 revs = scmutil.revrange(repo, revs)
1977 1977 ctxs = [repo[rev] for rev in revs]
1978 1978 for ctx in ctxs:
1979 1979 tags = [tag for tag in ctx.tags() if tag != b'tip']
1980 1980 if not hastags:
1981 1981 hastags = len(tags)
1982 1982 if hastags:
1983 1983 if ui.promptchoice(
1984 1984 _(
1985 1985 b'warning: tags associated with the given'
1986 1986 b' changeset will be lost after histedit.\n'
1987 1987 b'do you want to continue (yN)? $$ &Yes $$ &No'
1988 1988 ),
1989 1989 default=1,
1990 1990 ):
1991 1991 raise error.Abort(_(b'histedit cancelled\n'))
1992 1992 # rebuild state
1993 1993 if goal == goalcontinue:
1994 1994 state.read()
1995 1995 state = bootstrapcontinue(ui, state, opts)
1996 1996 elif goal == goaleditplan:
1997 1997 _edithisteditplan(ui, repo, state, rules)
1998 1998 return
1999 1999 elif goal == goalabort:
2000 2000 _aborthistedit(ui, repo, state, nobackup=nobackup)
2001 2001 return
2002 2002 else:
2003 2003 # goal == goalnew
2004 2004 _newhistedit(ui, repo, state, revs, freeargs, opts)
2005 2005
2006 2006 _continuehistedit(ui, repo, state)
2007 2007 _finishhistedit(ui, repo, state, fm)
2008 2008 fm.end()
2009 2009
2010 2010
2011 2011 def _continuehistedit(ui, repo, state):
2012 2012 """This function runs after either:
2013 2013 - bootstrapcontinue (if the goal is 'continue')
2014 2014 - _newhistedit (if the goal is 'new')
2015 2015 """
2016 2016 # preprocess rules so that we can hide inner folds from the user
2017 2017 # and only show one editor
2018 2018 actions = state.actions[:]
2019 2019 for idx, (action, nextact) in enumerate(zip(actions, actions[1:] + [None])):
2020 2020 if action.verb == b'fold' and nextact and nextact.verb == b'fold':
2021 2021 state.actions[idx].__class__ = _multifold
2022 2022
2023 2023 # Force an initial state file write, so the user can run --abort/continue
2024 2024 # even if there's an exception before the first transaction serialize.
2025 2025 state.write()
2026 2026
2027 2027 tr = None
2028 2028 # Don't use singletransaction by default since it rolls the entire
2029 2029 # transaction back if an unexpected exception happens (like a
2030 2030 # pretxncommit hook throws, or the user aborts the commit msg editor).
2031 2031 if ui.configbool(b"histedit", b"singletransaction"):
2032 2032 # Don't use a 'with' for the transaction, since actions may close
2033 2033 # and reopen a transaction. For example, if the action executes an
2034 2034 # external process it may choose to commit the transaction first.
2035 2035 tr = repo.transaction(b'histedit')
2036 2036 progress = ui.makeprogress(
2037 2037 _(b"editing"), unit=_(b'changes'), total=len(state.actions)
2038 2038 )
2039 2039 with progress, util.acceptintervention(tr):
2040 2040 while state.actions:
2041 2041 state.write(tr=tr)
2042 2042 actobj = state.actions[0]
2043 2043 progress.increment(item=actobj.torule())
2044 2044 ui.debug(
2045 2045 b'histedit: processing %s %s\n' % (actobj.verb, actobj.torule())
2046 2046 )
2047 2047 parentctx, replacement_ = actobj.run()
2048 2048 state.parentctxnode = parentctx.node()
2049 2049 state.replacements.extend(replacement_)
2050 2050 state.actions.pop(0)
2051 2051
2052 2052 state.write()
2053 2053
2054 2054
2055 2055 def _finishhistedit(ui, repo, state, fm):
2056 2056 """This action runs when histedit is finishing its session"""
2057 2057 hg.updaterepo(repo, state.parentctxnode, overwrite=False)
2058 2058
2059 2059 mapping, tmpnodes, created, ntm = processreplacement(state)
2060 2060 if mapping:
2061 2061 for prec, succs in pycompat.iteritems(mapping):
2062 2062 if not succs:
2063 2063 ui.debug(b'histedit: %s is dropped\n' % node.short(prec))
2064 2064 else:
2065 2065 ui.debug(
2066 2066 b'histedit: %s is replaced by %s\n'
2067 2067 % (node.short(prec), node.short(succs[0]))
2068 2068 )
2069 2069 if len(succs) > 1:
2070 2070 m = b'histedit: %s'
2071 2071 for n in succs[1:]:
2072 2072 ui.debug(m % node.short(n))
2073 2073
2074 2074 if not state.keep:
2075 2075 if mapping:
2076 2076 movetopmostbookmarks(repo, state.topmost, ntm)
2077 2077 # TODO update mq state
2078 2078 else:
2079 2079 mapping = {}
2080 2080
2081 2081 for n in tmpnodes:
2082 2082 if n in repo:
2083 2083 mapping[n] = ()
2084 2084
2085 2085 # remove entries about unknown nodes
2086 2086 has_node = repo.unfiltered().changelog.index.has_node
2087 2087 mapping = {
2088 2088 k: v
2089 2089 for k, v in mapping.items()
2090 2090 if has_node(k) and all(has_node(n) for n in v)
2091 2091 }
2092 2092 scmutil.cleanupnodes(repo, mapping, b'histedit')
2093 2093 hf = fm.hexfunc
2094 2094 fl = fm.formatlist
2095 2095 fd = fm.formatdict
2096 2096 nodechanges = fd(
2097 2097 {
2098 2098 hf(oldn): fl([hf(n) for n in newn], name=b'node')
2099 2099 for oldn, newn in pycompat.iteritems(mapping)
2100 2100 },
2101 2101 key=b"oldnode",
2102 2102 value=b"newnodes",
2103 2103 )
2104 2104 fm.data(nodechanges=nodechanges)
2105 2105
2106 2106 state.clear()
2107 2107 if os.path.exists(repo.sjoin(b'undo')):
2108 2108 os.unlink(repo.sjoin(b'undo'))
2109 2109 if repo.vfs.exists(b'histedit-last-edit.txt'):
2110 2110 repo.vfs.unlink(b'histedit-last-edit.txt')
2111 2111
2112 2112
2113 2113 def _aborthistedit(ui, repo, state, nobackup=False):
2114 2114 try:
2115 2115 state.read()
2116 2116 __, leafs, tmpnodes, __ = processreplacement(state)
2117 2117 ui.debug(b'restore wc to old parent %s\n' % node.short(state.topmost))
2118 2118
2119 2119 # Recover our old commits if necessary
2120 2120 if not state.topmost in repo and state.backupfile:
2121 2121 backupfile = repo.vfs.join(state.backupfile)
2122 2122 f = hg.openpath(ui, backupfile)
2123 2123 gen = exchange.readbundle(ui, f, backupfile)
2124 2124 with repo.transaction(b'histedit.abort') as tr:
2125 2125 bundle2.applybundle(
2126 2126 repo,
2127 2127 gen,
2128 2128 tr,
2129 2129 source=b'histedit',
2130 2130 url=b'bundle:' + backupfile,
2131 2131 )
2132 2132
2133 2133 os.remove(backupfile)
2134 2134
2135 2135 # check whether we should update away
2136 2136 if repo.unfiltered().revs(
2137 2137 b'parents() and (%n or %ln::)',
2138 2138 state.parentctxnode,
2139 2139 leafs | tmpnodes,
2140 2140 ):
2141 2141 hg.clean(repo, state.topmost, show_stats=True, quietempty=True)
2142 2142 cleanupnode(ui, repo, tmpnodes, nobackup=nobackup)
2143 2143 cleanupnode(ui, repo, leafs, nobackup=nobackup)
2144 2144 except Exception:
2145 2145 if state.inprogress():
2146 2146 ui.warn(
2147 2147 _(
2148 2148 b'warning: encountered an exception during histedit '
2149 2149 b'--abort; the repository may not have been completely '
2150 2150 b'cleaned up\n'
2151 2151 )
2152 2152 )
2153 2153 raise
2154 2154 finally:
2155 2155 state.clear()
2156 2156
2157 2157
2158 2158 def hgaborthistedit(ui, repo):
2159 2159 state = histeditstate(repo)
2160 2160 nobackup = not ui.configbool(b'rewrite', b'backup-bundle')
2161 2161 with repo.wlock() as wlock, repo.lock() as lock:
2162 2162 state.wlock = wlock
2163 2163 state.lock = lock
2164 2164 _aborthistedit(ui, repo, state, nobackup=nobackup)
2165 2165
2166 2166
2167 2167 def _edithisteditplan(ui, repo, state, rules):
2168 2168 state.read()
2169 2169 if not rules:
2170 2170 comment = geteditcomment(
2171 2171 ui, node.short(state.parentctxnode), node.short(state.topmost)
2172 2172 )
2173 2173 rules = ruleeditor(repo, ui, state.actions, comment)
2174 2174 else:
2175 2175 rules = _readfile(ui, rules)
2176 2176 actions = parserules(rules, state)
2177 2177 ctxs = [repo[act.node] for act in state.actions if act.node]
2178 2178 warnverifyactions(ui, repo, actions, state, ctxs)
2179 2179 state.actions = actions
2180 2180 state.write()
2181 2181
2182 2182
2183 2183 def _newhistedit(ui, repo, state, revs, freeargs, opts):
2184 2184 outg = opts.get(b'outgoing')
2185 2185 rules = opts.get(b'commands', b'')
2186 2186 force = opts.get(b'force')
2187 2187
2188 2188 cmdutil.checkunfinished(repo)
2189 2189 cmdutil.bailifchanged(repo)
2190 2190
2191 2191 topmost = repo.dirstate.p1()
2192 2192 if outg:
2193 2193 if freeargs:
2194 2194 remote = freeargs[0]
2195 2195 else:
2196 2196 remote = None
2197 2197 root = findoutgoing(ui, repo, remote, force, opts)
2198 2198 else:
2199 2199 rr = list(repo.set(b'roots(%ld)', scmutil.revrange(repo, revs)))
2200 2200 if len(rr) != 1:
2201 2201 raise error.Abort(
2202 2202 _(
2203 2203 b'The specified revisions must have '
2204 2204 b'exactly one common root'
2205 2205 )
2206 2206 )
2207 2207 root = rr[0].node()
2208 2208
2209 2209 revs = between(repo, root, topmost, state.keep)
2210 2210 if not revs:
2211 2211 raise error.Abort(
2212 2212 _(b'%s is not an ancestor of working directory') % node.short(root)
2213 2213 )
2214 2214
2215 2215 ctxs = [repo[r] for r in revs]
2216 2216
2217 2217 wctx = repo[None]
2218 2218 # Please don't ask me why `ancestors` is this value. I figured it
2219 2219 # out with print-debugging, not by actually understanding what the
2220 2220 # merge code is doing. :(
2221 2221 ancs = [repo[b'.']]
2222 2222 # Sniff-test to make sure we won't collide with untracked files in
2223 2223 # the working directory. If we don't do this, we can get a
2224 2224 # collision after we've started histedit and backing out gets ugly
2225 2225 # for everyone, especially the user.
2226 2226 for c in [ctxs[0].p1()] + ctxs:
2227 2227 try:
2228 2228 mergemod.calculateupdates(
2229 2229 repo,
2230 2230 wctx,
2231 2231 c,
2232 2232 ancs,
2233 2233 # These parameters were determined by print-debugging
2234 2234 # what happens later on inside histedit.
2235 2235 branchmerge=False,
2236 2236 force=False,
2237 2237 acceptremote=False,
2238 2238 followcopies=False,
2239 2239 )
2240 2240 except error.Abort:
2241 2241 raise error.Abort(
2242 2242 _(
2243 2243 b"untracked files in working directory conflict with files in %s"
2244 2244 )
2245 2245 % c
2246 2246 )
2247 2247
2248 2248 if not rules:
2249 2249 comment = geteditcomment(ui, node.short(root), node.short(topmost))
2250 2250 actions = [pick(state, r) for r in revs]
2251 2251 rules = ruleeditor(repo, ui, actions, comment)
2252 2252 else:
2253 2253 rules = _readfile(ui, rules)
2254 2254 actions = parserules(rules, state)
2255 2255 warnverifyactions(ui, repo, actions, state, ctxs)
2256 2256
2257 2257 parentctxnode = repo[root].p1().node()
2258 2258
2259 2259 state.parentctxnode = parentctxnode
2260 2260 state.actions = actions
2261 2261 state.topmost = topmost
2262 2262 state.replacements = []
2263 2263
2264 2264 ui.log(
2265 2265 b"histedit",
2266 2266 b"%d actions to histedit\n",
2267 2267 len(actions),
2268 2268 histedit_num_actions=len(actions),
2269 2269 )
2270 2270
2271 2271 # Create a backup so we can always abort completely.
2272 2272 backupfile = None
2273 2273 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2274 2274 backupfile = repair.backupbundle(
2275 2275 repo, [parentctxnode], [topmost], root, b'histedit'
2276 2276 )
2277 2277 state.backupfile = backupfile
2278 2278
2279 2279
2280 2280 def _getsummary(ctx):
2281 2281 # a common pattern is to extract the summary but default to the empty
2282 2282 # string
2283 2283 summary = ctx.description() or b''
2284 2284 if summary:
2285 2285 summary = summary.splitlines()[0]
2286 2286 return summary
2287 2287
2288 2288
2289 2289 def bootstrapcontinue(ui, state, opts):
2290 2290 repo = state.repo
2291 2291
2292 2292 ms = mergemod.mergestate.read(repo)
2293 2293 mergeutil.checkunresolved(ms)
2294 2294
2295 2295 if state.actions:
2296 2296 actobj = state.actions.pop(0)
2297 2297
2298 2298 if _isdirtywc(repo):
2299 2299 actobj.continuedirty()
2300 2300 if _isdirtywc(repo):
2301 2301 abortdirty()
2302 2302
2303 2303 parentctx, replacements = actobj.continueclean()
2304 2304
2305 2305 state.parentctxnode = parentctx.node()
2306 2306 state.replacements.extend(replacements)
2307 2307
2308 2308 return state
2309 2309
2310 2310
2311 2311 def between(repo, old, new, keep):
2312 2312 """select and validate the set of revision to edit
2313 2313
2314 2314 When keep is false, the specified set can't have children."""
2315 2315 revs = repo.revs(b'%n::%n', old, new)
2316 2316 if revs and not keep:
2317 2317 rewriteutil.precheck(repo, revs, b'edit')
2318 2318 if repo.revs(b'(%ld) and merge()', revs):
2319 2319 raise error.Abort(_(b'cannot edit history that contains merges'))
2320 2320 return pycompat.maplist(repo.changelog.node, revs)
2321 2321
2322 2322
2323 2323 def ruleeditor(repo, ui, actions, editcomment=b""):
2324 2324 """open an editor to edit rules
2325 2325
2326 2326 rules are in the format [ [act, ctx], ...] like in state.rules
2327 2327 """
2328 2328 if repo.ui.configbool(b"experimental", b"histedit.autoverb"):
2329 2329 newact = util.sortdict()
2330 2330 for act in actions:
2331 2331 ctx = repo[act.node]
2332 2332 summary = _getsummary(ctx)
2333 2333 fword = summary.split(b' ', 1)[0].lower()
2334 2334 added = False
2335 2335
2336 2336 # if it doesn't end with the special character '!' just skip this
2337 2337 if fword.endswith(b'!'):
2338 2338 fword = fword[:-1]
2339 2339 if fword in primaryactions | secondaryactions | tertiaryactions:
2340 2340 act.verb = fword
2341 2341 # get the target summary
2342 2342 tsum = summary[len(fword) + 1 :].lstrip()
2343 2343 # safe but slow: reverse iterate over the actions so we
2344 2344 # don't clash on two commits having the same summary
2345 2345 for na, l in reversed(list(pycompat.iteritems(newact))):
2346 2346 actx = repo[na.node]
2347 2347 asum = _getsummary(actx)
2348 2348 if asum == tsum:
2349 2349 added = True
2350 2350 l.append(act)
2351 2351 break
2352 2352
2353 2353 if not added:
2354 2354 newact[act] = []
2355 2355
2356 2356 # copy over and flatten the new list
2357 2357 actions = []
2358 2358 for na, l in pycompat.iteritems(newact):
2359 2359 actions.append(na)
2360 2360 actions += l
2361 2361
2362 2362 rules = b'\n'.join([act.torule() for act in actions])
2363 2363 rules += b'\n\n'
2364 2364 rules += editcomment
2365 2365 rules = ui.edit(
2366 2366 rules,
2367 2367 ui.username(),
2368 2368 {b'prefix': b'histedit'},
2369 2369 repopath=repo.path,
2370 2370 action=b'histedit',
2371 2371 )
2372 2372
2373 2373 # Save edit rules in .hg/histedit-last-edit.txt in case
2374 2374 # the user needs to ask for help after something
2375 2375 # surprising happens.
2376 2376 with repo.vfs(b'histedit-last-edit.txt', b'wb') as f:
2377 2377 f.write(rules)
2378 2378
2379 2379 return rules
2380 2380
2381 2381
2382 2382 def parserules(rules, state):
2383 2383 """Read the histedit rules string and return list of action objects """
2384 2384 rules = [
2385 2385 l
2386 2386 for l in (r.strip() for r in rules.splitlines())
2387 2387 if l and not l.startswith(b'#')
2388 2388 ]
2389 2389 actions = []
2390 2390 for r in rules:
2391 2391 if b' ' not in r:
2392 2392 raise error.ParseError(_(b'malformed line "%s"') % r)
2393 2393 verb, rest = r.split(b' ', 1)
2394 2394
2395 2395 if verb not in actiontable:
2396 2396 raise error.ParseError(_(b'unknown action "%s"') % verb)
2397 2397
2398 2398 action = actiontable[verb].fromrule(state, rest)
2399 2399 actions.append(action)
2400 2400 return actions
2401 2401
2402 2402
2403 2403 def warnverifyactions(ui, repo, actions, state, ctxs):
2404 2404 try:
2405 2405 verifyactions(actions, state, ctxs)
2406 2406 except error.ParseError:
2407 2407 if repo.vfs.exists(b'histedit-last-edit.txt'):
2408 2408 ui.warn(
2409 2409 _(
2410 2410 b'warning: histedit rules saved '
2411 2411 b'to: .hg/histedit-last-edit.txt\n'
2412 2412 )
2413 2413 )
2414 2414 raise
2415 2415
2416 2416
2417 2417 def verifyactions(actions, state, ctxs):
2418 2418 """Verify that there exists exactly one action per given changeset and
2419 2419 other constraints.
2420 2420
2421 2421 Will abort if there are to many or too few rules, a malformed rule,
2422 2422 or a rule on a changeset outside of the user-given range.
2423 2423 """
2424 2424 expected = {c.node() for c in ctxs}
2425 2425 seen = set()
2426 2426 prev = None
2427 2427
2428 2428 if actions and actions[0].verb in [b'roll', b'fold']:
2429 2429 raise error.ParseError(
2430 2430 _(b'first changeset cannot use verb "%s"') % actions[0].verb
2431 2431 )
2432 2432
2433 2433 for action in actions:
2434 2434 action.verify(prev, expected, seen)
2435 2435 prev = action
2436 2436 if action.node is not None:
2437 2437 seen.add(action.node)
2438 2438 missing = sorted(expected - seen) # sort to stabilize output
2439 2439
2440 2440 if state.repo.ui.configbool(b'histedit', b'dropmissing'):
2441 2441 if len(actions) == 0:
2442 2442 raise error.ParseError(
2443 2443 _(b'no rules provided'),
2444 2444 hint=_(b'use strip extension to remove commits'),
2445 2445 )
2446 2446
2447 2447 drops = [drop(state, n) for n in missing]
2448 2448 # put the in the beginning so they execute immediately and
2449 2449 # don't show in the edit-plan in the future
2450 2450 actions[:0] = drops
2451 2451 elif missing:
2452 2452 raise error.ParseError(
2453 2453 _(b'missing rules for changeset %s') % node.short(missing[0]),
2454 2454 hint=_(
2455 2455 b'use "drop %s" to discard, see also: '
2456 2456 b"'hg help -e histedit.config'"
2457 2457 )
2458 2458 % node.short(missing[0]),
2459 2459 )
2460 2460
2461 2461
2462 2462 def adjustreplacementsfrommarkers(repo, oldreplacements):
2463 2463 """Adjust replacements from obsolescence markers
2464 2464
2465 2465 Replacements structure is originally generated based on
2466 2466 histedit's state and does not account for changes that are
2467 2467 not recorded there. This function fixes that by adding
2468 2468 data read from obsolescence markers"""
2469 2469 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2470 2470 return oldreplacements
2471 2471
2472 2472 unfi = repo.unfiltered()
2473 2473 get_rev = unfi.changelog.index.get_rev
2474 2474 obsstore = repo.obsstore
2475 2475 newreplacements = list(oldreplacements)
2476 2476 oldsuccs = [r[1] for r in oldreplacements]
2477 2477 # successors that have already been added to succstocheck once
2478 2478 seensuccs = set().union(
2479 2479 *oldsuccs
2480 2480 ) # create a set from an iterable of tuples
2481 2481 succstocheck = list(seensuccs)
2482 2482 while succstocheck:
2483 2483 n = succstocheck.pop()
2484 2484 missing = get_rev(n) is None
2485 2485 markers = obsstore.successors.get(n, ())
2486 2486 if missing and not markers:
2487 2487 # dead end, mark it as such
2488 2488 newreplacements.append((n, ()))
2489 2489 for marker in markers:
2490 2490 nsuccs = marker[1]
2491 2491 newreplacements.append((n, nsuccs))
2492 2492 for nsucc in nsuccs:
2493 2493 if nsucc not in seensuccs:
2494 2494 seensuccs.add(nsucc)
2495 2495 succstocheck.append(nsucc)
2496 2496
2497 2497 return newreplacements
2498 2498
2499 2499
2500 2500 def processreplacement(state):
2501 2501 """process the list of replacements to return
2502 2502
2503 2503 1) the final mapping between original and created nodes
2504 2504 2) the list of temporary node created by histedit
2505 2505 3) the list of new commit created by histedit"""
2506 2506 replacements = adjustreplacementsfrommarkers(state.repo, state.replacements)
2507 2507 allsuccs = set()
2508 2508 replaced = set()
2509 2509 fullmapping = {}
2510 2510 # initialize basic set
2511 2511 # fullmapping records all operations recorded in replacement
2512 2512 for rep in replacements:
2513 2513 allsuccs.update(rep[1])
2514 2514 replaced.add(rep[0])
2515 2515 fullmapping.setdefault(rep[0], set()).update(rep[1])
2516 2516 new = allsuccs - replaced
2517 2517 tmpnodes = allsuccs & replaced
2518 2518 # Reduce content fullmapping into direct relation between original nodes
2519 2519 # and final node created during history edition
2520 2520 # Dropped changeset are replaced by an empty list
2521 2521 toproceed = set(fullmapping)
2522 2522 final = {}
2523 2523 while toproceed:
2524 2524 for x in list(toproceed):
2525 2525 succs = fullmapping[x]
2526 2526 for s in list(succs):
2527 2527 if s in toproceed:
2528 2528 # non final node with unknown closure
2529 2529 # We can't process this now
2530 2530 break
2531 2531 elif s in final:
2532 2532 # non final node, replace with closure
2533 2533 succs.remove(s)
2534 2534 succs.update(final[s])
2535 2535 else:
2536 2536 final[x] = succs
2537 2537 toproceed.remove(x)
2538 2538 # remove tmpnodes from final mapping
2539 2539 for n in tmpnodes:
2540 2540 del final[n]
2541 2541 # we expect all changes involved in final to exist in the repo
2542 2542 # turn `final` into list (topologically sorted)
2543 2543 get_rev = state.repo.changelog.index.get_rev
2544 2544 for prec, succs in final.items():
2545 2545 final[prec] = sorted(succs, key=get_rev)
2546 2546
2547 2547 # computed topmost element (necessary for bookmark)
2548 2548 if new:
2549 2549 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
2550 2550 elif not final:
2551 2551 # Nothing rewritten at all. we won't need `newtopmost`
2552 2552 # It is the same as `oldtopmost` and `processreplacement` know it
2553 2553 newtopmost = None
2554 2554 else:
2555 2555 # every body died. The newtopmost is the parent of the root.
2556 2556 r = state.repo.changelog.rev
2557 2557 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
2558 2558
2559 2559 return final, tmpnodes, new, newtopmost
2560 2560
2561 2561
2562 2562 def movetopmostbookmarks(repo, oldtopmost, newtopmost):
2563 2563 """Move bookmark from oldtopmost to newly created topmost
2564 2564
2565 2565 This is arguably a feature and we may only want that for the active
2566 2566 bookmark. But the behavior is kept compatible with the old version for now.
2567 2567 """
2568 2568 if not oldtopmost or not newtopmost:
2569 2569 return
2570 2570 oldbmarks = repo.nodebookmarks(oldtopmost)
2571 2571 if oldbmarks:
2572 2572 with repo.lock(), repo.transaction(b'histedit') as tr:
2573 2573 marks = repo._bookmarks
2574 2574 changes = []
2575 2575 for name in oldbmarks:
2576 2576 changes.append((name, newtopmost))
2577 2577 marks.applychanges(repo, tr, changes)
2578 2578
2579 2579
2580 2580 def cleanupnode(ui, repo, nodes, nobackup=False):
2581 2581 """strip a group of nodes from the repository
2582 2582
2583 2583 The set of node to strip may contains unknown nodes."""
2584 2584 with repo.lock():
2585 2585 # do not let filtering get in the way of the cleanse
2586 2586 # we should probably get rid of obsolescence marker created during the
2587 2587 # histedit, but we currently do not have such information.
2588 2588 repo = repo.unfiltered()
2589 2589 # Find all nodes that need to be stripped
2590 2590 # (we use %lr instead of %ln to silently ignore unknown items)
2591 2591 has_node = repo.changelog.index.has_node
2592 2592 nodes = sorted(n for n in nodes if has_node(n))
2593 2593 roots = [c.node() for c in repo.set(b"roots(%ln)", nodes)]
2594 2594 if roots:
2595 2595 backup = not nobackup
2596 2596 repair.strip(ui, repo, roots, backup=backup)
2597 2597
2598 2598
2599 2599 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
2600 2600 if isinstance(nodelist, bytes):
2601 2601 nodelist = [nodelist]
2602 2602 state = histeditstate(repo)
2603 2603 if state.inprogress():
2604 2604 state.read()
2605 2605 histedit_nodes = {
2606 2606 action.node for action in state.actions if action.node
2607 2607 }
2608 2608 common_nodes = histedit_nodes & set(nodelist)
2609 2609 if common_nodes:
2610 2610 raise error.Abort(
2611 2611 _(b"histedit in progress, can't strip %s")
2612 2612 % b', '.join(node.short(x) for x in common_nodes)
2613 2613 )
2614 2614 return orig(ui, repo, nodelist, *args, **kwargs)
2615 2615
2616 2616
2617 2617 extensions.wrapfunction(repair, b'strip', stripwrapper)
2618 2618
2619 2619
2620 2620 def summaryhook(ui, repo):
2621 2621 state = histeditstate(repo)
2622 2622 if not state.inprogress():
2623 2623 return
2624 2624 state.read()
2625 2625 if state.actions:
2626 2626 # i18n: column positioning for "hg summary"
2627 2627 ui.write(
2628 2628 _(b'hist: %s (histedit --continue)\n')
2629 2629 % (
2630 2630 ui.label(_(b'%d remaining'), b'histedit.remaining')
2631 2631 % len(state.actions)
2632 2632 )
2633 2633 )
2634 2634
2635 2635
2636 2636 def extsetup(ui):
2637 2637 cmdutil.summaryhooks.add(b'histedit', summaryhook)
2638 2638 statemod.addunfinished(
2639 2639 b'histedit',
2640 2640 fname=b'histedit-state',
2641 2641 allowcommit=True,
2642 2642 continueflag=True,
2643 2643 abortfunc=hgaborthistedit,
2644 2644 )
@@ -1,632 +1,629 b''
1 1 # changelog.py - changelog class for mercurial
2 2 #
3 3 # Copyright 2005-2007 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 from .i18n import _
11 11 from .node import (
12 12 bin,
13 13 hex,
14 14 nullid,
15 15 )
16 16 from .thirdparty import attr
17 17
18 18 from . import (
19 19 copies,
20 20 encoding,
21 21 error,
22 22 pycompat,
23 23 revlog,
24 24 )
25 25 from .utils import (
26 26 dateutil,
27 27 stringutil,
28 28 )
29 29
30 30 from .revlogutils import sidedata as sidedatamod
31 31
32 32 _defaultextra = {b'branch': b'default'}
33 33
34 34
35 35 def _string_escape(text):
36 36 """
37 37 >>> from .pycompat import bytechr as chr
38 38 >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
39 39 >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)s12ab%(cr)scd%(bs)s%(nl)s" % d
40 40 >>> s
41 41 'ab\\ncd\\\\\\\\n\\x0012ab\\rcd\\\\\\n'
42 42 >>> res = _string_escape(s)
43 43 >>> s == _string_unescape(res)
44 44 True
45 45 """
46 46 # subset of the string_escape codec
47 47 text = (
48 48 text.replace(b'\\', b'\\\\')
49 49 .replace(b'\n', b'\\n')
50 50 .replace(b'\r', b'\\r')
51 51 )
52 52 return text.replace(b'\0', b'\\0')
53 53
54 54
55 55 def _string_unescape(text):
56 56 if b'\\0' in text:
57 57 # fix up \0 without getting into trouble with \\0
58 58 text = text.replace(b'\\\\', b'\\\\\n')
59 59 text = text.replace(b'\\0', b'\0')
60 60 text = text.replace(b'\n', b'')
61 61 return stringutil.unescapestr(text)
62 62
63 63
64 64 def decodeextra(text):
65 65 """
66 66 >>> from .pycompat import bytechr as chr
67 67 >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
68 68 ... ).items())
69 69 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
70 70 >>> sorted(decodeextra(encodeextra({b'foo': b'bar',
71 71 ... b'baz': chr(92) + chr(0) + b'2'})
72 72 ... ).items())
73 73 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
74 74 """
75 75 extra = _defaultextra.copy()
76 76 for l in text.split(b'\0'):
77 77 if l:
78 78 k, v = _string_unescape(l).split(b':', 1)
79 79 extra[k] = v
80 80 return extra
81 81
82 82
83 83 def encodeextra(d):
84 84 # keys must be sorted to produce a deterministic changelog entry
85 items = [
86 _string_escape(b'%s:%s' % (k, pycompat.bytestr(d[k])))
87 for k in sorted(d)
88 ]
85 items = [_string_escape(b'%s:%s' % (k, d[k])) for k in sorted(d)]
89 86 return b"\0".join(items)
90 87
91 88
92 89 def stripdesc(desc):
93 90 """strip trailing whitespace and leading and trailing empty lines"""
94 91 return b'\n'.join([l.rstrip() for l in desc.splitlines()]).strip(b'\n')
95 92
96 93
97 94 class appender(object):
98 95 '''the changelog index must be updated last on disk, so we use this class
99 96 to delay writes to it'''
100 97
101 98 def __init__(self, vfs, name, mode, buf):
102 99 self.data = buf
103 100 fp = vfs(name, mode)
104 101 self.fp = fp
105 102 self.offset = fp.tell()
106 103 self.size = vfs.fstat(fp).st_size
107 104 self._end = self.size
108 105
109 106 def end(self):
110 107 return self._end
111 108
112 109 def tell(self):
113 110 return self.offset
114 111
115 112 def flush(self):
116 113 pass
117 114
118 115 @property
119 116 def closed(self):
120 117 return self.fp.closed
121 118
122 119 def close(self):
123 120 self.fp.close()
124 121
125 122 def seek(self, offset, whence=0):
126 123 '''virtual file offset spans real file and data'''
127 124 if whence == 0:
128 125 self.offset = offset
129 126 elif whence == 1:
130 127 self.offset += offset
131 128 elif whence == 2:
132 129 self.offset = self.end() + offset
133 130 if self.offset < self.size:
134 131 self.fp.seek(self.offset)
135 132
136 133 def read(self, count=-1):
137 134 '''only trick here is reads that span real file and data'''
138 135 ret = b""
139 136 if self.offset < self.size:
140 137 s = self.fp.read(count)
141 138 ret = s
142 139 self.offset += len(s)
143 140 if count > 0:
144 141 count -= len(s)
145 142 if count != 0:
146 143 doff = self.offset - self.size
147 144 self.data.insert(0, b"".join(self.data))
148 145 del self.data[1:]
149 146 s = self.data[0][doff : doff + count]
150 147 self.offset += len(s)
151 148 ret += s
152 149 return ret
153 150
154 151 def write(self, s):
155 152 self.data.append(bytes(s))
156 153 self.offset += len(s)
157 154 self._end += len(s)
158 155
159 156 def __enter__(self):
160 157 self.fp.__enter__()
161 158 return self
162 159
163 160 def __exit__(self, *args):
164 161 return self.fp.__exit__(*args)
165 162
166 163
167 164 class _divertopener(object):
168 165 def __init__(self, opener, target):
169 166 self._opener = opener
170 167 self._target = target
171 168
172 169 def __call__(self, name, mode=b'r', checkambig=False, **kwargs):
173 170 if name != self._target:
174 171 return self._opener(name, mode, **kwargs)
175 172 return self._opener(name + b".a", mode, **kwargs)
176 173
177 174 def __getattr__(self, attr):
178 175 return getattr(self._opener, attr)
179 176
180 177
181 178 def _delayopener(opener, target, buf):
182 179 """build an opener that stores chunks in 'buf' instead of 'target'"""
183 180
184 181 def _delay(name, mode=b'r', checkambig=False, **kwargs):
185 182 if name != target:
186 183 return opener(name, mode, **kwargs)
187 184 assert not kwargs
188 185 return appender(opener, name, mode, buf)
189 186
190 187 return _delay
191 188
192 189
193 190 @attr.s
194 191 class _changelogrevision(object):
195 192 # Extensions might modify _defaultextra, so let the constructor below pass
196 193 # it in
197 194 extra = attr.ib()
198 195 manifest = attr.ib(default=nullid)
199 196 user = attr.ib(default=b'')
200 197 date = attr.ib(default=(0, 0))
201 198 files = attr.ib(default=attr.Factory(list))
202 199 filesadded = attr.ib(default=None)
203 200 filesremoved = attr.ib(default=None)
204 201 p1copies = attr.ib(default=None)
205 202 p2copies = attr.ib(default=None)
206 203 description = attr.ib(default=b'')
207 204
208 205
209 206 class changelogrevision(object):
210 207 """Holds results of a parsed changelog revision.
211 208
212 209 Changelog revisions consist of multiple pieces of data, including
213 210 the manifest node, user, and date. This object exposes a view into
214 211 the parsed object.
215 212 """
216 213
217 214 __slots__ = (
218 215 '_offsets',
219 216 '_text',
220 217 '_sidedata',
221 218 '_cpsd',
222 219 )
223 220
224 221 def __new__(cls, text, sidedata, cpsd):
225 222 if not text:
226 223 return _changelogrevision(extra=_defaultextra)
227 224
228 225 self = super(changelogrevision, cls).__new__(cls)
229 226 # We could return here and implement the following as an __init__.
230 227 # But doing it here is equivalent and saves an extra function call.
231 228
232 229 # format used:
233 230 # nodeid\n : manifest node in ascii
234 231 # user\n : user, no \n or \r allowed
235 232 # time tz extra\n : date (time is int or float, timezone is int)
236 233 # : extra is metadata, encoded and separated by '\0'
237 234 # : older versions ignore it
238 235 # files\n\n : files modified by the cset, no \n or \r allowed
239 236 # (.*) : comment (free text, ideally utf-8)
240 237 #
241 238 # changelog v0 doesn't use extra
242 239
243 240 nl1 = text.index(b'\n')
244 241 nl2 = text.index(b'\n', nl1 + 1)
245 242 nl3 = text.index(b'\n', nl2 + 1)
246 243
247 244 # The list of files may be empty. Which means nl3 is the first of the
248 245 # double newline that precedes the description.
249 246 if text[nl3 + 1 : nl3 + 2] == b'\n':
250 247 doublenl = nl3
251 248 else:
252 249 doublenl = text.index(b'\n\n', nl3 + 1)
253 250
254 251 self._offsets = (nl1, nl2, nl3, doublenl)
255 252 self._text = text
256 253 self._sidedata = sidedata
257 254 self._cpsd = cpsd
258 255
259 256 return self
260 257
261 258 @property
262 259 def manifest(self):
263 260 return bin(self._text[0 : self._offsets[0]])
264 261
265 262 @property
266 263 def user(self):
267 264 off = self._offsets
268 265 return encoding.tolocal(self._text[off[0] + 1 : off[1]])
269 266
270 267 @property
271 268 def _rawdate(self):
272 269 off = self._offsets
273 270 dateextra = self._text[off[1] + 1 : off[2]]
274 271 return dateextra.split(b' ', 2)[0:2]
275 272
276 273 @property
277 274 def _rawextra(self):
278 275 off = self._offsets
279 276 dateextra = self._text[off[1] + 1 : off[2]]
280 277 fields = dateextra.split(b' ', 2)
281 278 if len(fields) != 3:
282 279 return None
283 280
284 281 return fields[2]
285 282
286 283 @property
287 284 def date(self):
288 285 raw = self._rawdate
289 286 time = float(raw[0])
290 287 # Various tools did silly things with the timezone.
291 288 try:
292 289 timezone = int(raw[1])
293 290 except ValueError:
294 291 timezone = 0
295 292
296 293 return time, timezone
297 294
298 295 @property
299 296 def extra(self):
300 297 raw = self._rawextra
301 298 if raw is None:
302 299 return _defaultextra
303 300
304 301 return decodeextra(raw)
305 302
306 303 @property
307 304 def files(self):
308 305 off = self._offsets
309 306 if off[2] == off[3]:
310 307 return []
311 308
312 309 return self._text[off[2] + 1 : off[3]].split(b'\n')
313 310
314 311 @property
315 312 def filesadded(self):
316 313 if self._cpsd:
317 314 rawindices = self._sidedata.get(sidedatamod.SD_FILESADDED)
318 315 if not rawindices:
319 316 return []
320 317 else:
321 318 rawindices = self.extra.get(b'filesadded')
322 319 if rawindices is None:
323 320 return None
324 321 return copies.decodefileindices(self.files, rawindices)
325 322
326 323 @property
327 324 def filesremoved(self):
328 325 if self._cpsd:
329 326 rawindices = self._sidedata.get(sidedatamod.SD_FILESREMOVED)
330 327 if not rawindices:
331 328 return []
332 329 else:
333 330 rawindices = self.extra.get(b'filesremoved')
334 331 if rawindices is None:
335 332 return None
336 333 return copies.decodefileindices(self.files, rawindices)
337 334
338 335 @property
339 336 def p1copies(self):
340 337 if self._cpsd:
341 338 rawcopies = self._sidedata.get(sidedatamod.SD_P1COPIES)
342 339 if not rawcopies:
343 340 return {}
344 341 else:
345 342 rawcopies = self.extra.get(b'p1copies')
346 343 if rawcopies is None:
347 344 return None
348 345 return copies.decodecopies(self.files, rawcopies)
349 346
350 347 @property
351 348 def p2copies(self):
352 349 if self._cpsd:
353 350 rawcopies = self._sidedata.get(sidedatamod.SD_P2COPIES)
354 351 if not rawcopies:
355 352 return {}
356 353 else:
357 354 rawcopies = self.extra.get(b'p2copies')
358 355 if rawcopies is None:
359 356 return None
360 357 return copies.decodecopies(self.files, rawcopies)
361 358
362 359 @property
363 360 def description(self):
364 361 return encoding.tolocal(self._text[self._offsets[3] + 2 :])
365 362
366 363
367 364 class changelog(revlog.revlog):
368 365 def __init__(self, opener, trypending=False):
369 366 """Load a changelog revlog using an opener.
370 367
371 368 If ``trypending`` is true, we attempt to load the index from a
372 369 ``00changelog.i.a`` file instead of the default ``00changelog.i``.
373 370 The ``00changelog.i.a`` file contains index (and possibly inline
374 371 revision) data for a transaction that hasn't been finalized yet.
375 372 It exists in a separate file to facilitate readers (such as
376 373 hooks processes) accessing data before a transaction is finalized.
377 374 """
378 375 if trypending and opener.exists(b'00changelog.i.a'):
379 376 indexfile = b'00changelog.i.a'
380 377 else:
381 378 indexfile = b'00changelog.i'
382 379
383 380 datafile = b'00changelog.d'
384 381 revlog.revlog.__init__(
385 382 self,
386 383 opener,
387 384 indexfile,
388 385 datafile=datafile,
389 386 checkambig=True,
390 387 mmaplargeindex=True,
391 388 persistentnodemap=opener.options.get(
392 389 b'exp-persistent-nodemap', False
393 390 ),
394 391 )
395 392
396 393 if self._initempty and (self.version & 0xFFFF == revlog.REVLOGV1):
397 394 # changelogs don't benefit from generaldelta.
398 395
399 396 self.version &= ~revlog.FLAG_GENERALDELTA
400 397 self._generaldelta = False
401 398
402 399 # Delta chains for changelogs tend to be very small because entries
403 400 # tend to be small and don't delta well with each. So disable delta
404 401 # chains.
405 402 self._storedeltachains = False
406 403
407 404 self._realopener = opener
408 405 self._delayed = False
409 406 self._delaybuf = None
410 407 self._divert = False
411 408 self.filteredrevs = frozenset()
412 409 self._copiesstorage = opener.options.get(b'copies-storage')
413 410
414 411 def delayupdate(self, tr):
415 412 """delay visibility of index updates to other readers"""
416 413
417 414 if not self._delayed:
418 415 if len(self) == 0:
419 416 self._divert = True
420 417 if self._realopener.exists(self.indexfile + b'.a'):
421 418 self._realopener.unlink(self.indexfile + b'.a')
422 419 self.opener = _divertopener(self._realopener, self.indexfile)
423 420 else:
424 421 self._delaybuf = []
425 422 self.opener = _delayopener(
426 423 self._realopener, self.indexfile, self._delaybuf
427 424 )
428 425 self._delayed = True
429 426 tr.addpending(b'cl-%i' % id(self), self._writepending)
430 427 tr.addfinalize(b'cl-%i' % id(self), self._finalize)
431 428
432 429 def _finalize(self, tr):
433 430 """finalize index updates"""
434 431 self._delayed = False
435 432 self.opener = self._realopener
436 433 # move redirected index data back into place
437 434 if self._divert:
438 435 assert not self._delaybuf
439 436 tmpname = self.indexfile + b".a"
440 437 nfile = self.opener.open(tmpname)
441 438 nfile.close()
442 439 self.opener.rename(tmpname, self.indexfile, checkambig=True)
443 440 elif self._delaybuf:
444 441 fp = self.opener(self.indexfile, b'a', checkambig=True)
445 442 fp.write(b"".join(self._delaybuf))
446 443 fp.close()
447 444 self._delaybuf = None
448 445 self._divert = False
449 446 # split when we're done
450 447 self._enforceinlinesize(tr)
451 448
452 449 def _writepending(self, tr):
453 450 """create a file containing the unfinalized state for
454 451 pretxnchangegroup"""
455 452 if self._delaybuf:
456 453 # make a temporary copy of the index
457 454 fp1 = self._realopener(self.indexfile)
458 455 pendingfilename = self.indexfile + b".a"
459 456 # register as a temp file to ensure cleanup on failure
460 457 tr.registertmp(pendingfilename)
461 458 # write existing data
462 459 fp2 = self._realopener(pendingfilename, b"w")
463 460 fp2.write(fp1.read())
464 461 # add pending data
465 462 fp2.write(b"".join(self._delaybuf))
466 463 fp2.close()
467 464 # switch modes so finalize can simply rename
468 465 self._delaybuf = None
469 466 self._divert = True
470 467 self.opener = _divertopener(self._realopener, self.indexfile)
471 468
472 469 if self._divert:
473 470 return True
474 471
475 472 return False
476 473
477 474 def _enforceinlinesize(self, tr, fp=None):
478 475 if not self._delayed:
479 476 revlog.revlog._enforceinlinesize(self, tr, fp)
480 477
481 478 def read(self, node):
482 479 """Obtain data from a parsed changelog revision.
483 480
484 481 Returns a 6-tuple of:
485 482
486 483 - manifest node in binary
487 484 - author/user as a localstr
488 485 - date as a 2-tuple of (time, timezone)
489 486 - list of files
490 487 - commit message as a localstr
491 488 - dict of extra metadata
492 489
493 490 Unless you need to access all fields, consider calling
494 491 ``changelogrevision`` instead, as it is faster for partial object
495 492 access.
496 493 """
497 494 d, s = self._revisiondata(node)
498 495 c = changelogrevision(
499 496 d, s, self._copiesstorage == b'changeset-sidedata'
500 497 )
501 498 return (c.manifest, c.user, c.date, c.files, c.description, c.extra)
502 499
503 500 def changelogrevision(self, nodeorrev):
504 501 """Obtain a ``changelogrevision`` for a node or revision."""
505 502 text, sidedata = self._revisiondata(nodeorrev)
506 503 return changelogrevision(
507 504 text, sidedata, self._copiesstorage == b'changeset-sidedata'
508 505 )
509 506
510 507 def readfiles(self, node):
511 508 """
512 509 short version of read that only returns the files modified by the cset
513 510 """
514 511 text = self.revision(node)
515 512 if not text:
516 513 return []
517 514 last = text.index(b"\n\n")
518 515 l = text[:last].split(b'\n')
519 516 return l[3:]
520 517
521 518 def add(
522 519 self,
523 520 manifest,
524 521 files,
525 522 desc,
526 523 transaction,
527 524 p1,
528 525 p2,
529 526 user,
530 527 date=None,
531 528 extra=None,
532 529 p1copies=None,
533 530 p2copies=None,
534 531 filesadded=None,
535 532 filesremoved=None,
536 533 ):
537 534 # Convert to UTF-8 encoded bytestrings as the very first
538 535 # thing: calling any method on a localstr object will turn it
539 536 # into a str object and the cached UTF-8 string is thus lost.
540 537 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
541 538
542 539 user = user.strip()
543 540 # An empty username or a username with a "\n" will make the
544 541 # revision text contain two "\n\n" sequences -> corrupt
545 542 # repository since read cannot unpack the revision.
546 543 if not user:
547 544 raise error.StorageError(_(b"empty username"))
548 545 if b"\n" in user:
549 546 raise error.StorageError(
550 547 _(b"username %r contains a newline") % pycompat.bytestr(user)
551 548 )
552 549
553 550 desc = stripdesc(desc)
554 551
555 552 if date:
556 553 parseddate = b"%d %d" % dateutil.parsedate(date)
557 554 else:
558 555 parseddate = b"%d %d" % dateutil.makedate()
559 556 if extra:
560 557 branch = extra.get(b"branch")
561 558 if branch in (b"default", b""):
562 559 del extra[b"branch"]
563 560 elif branch in (b".", b"null", b"tip"):
564 561 raise error.StorageError(
565 562 _(b'the name \'%s\' is reserved') % branch
566 563 )
567 564 sortedfiles = sorted(files)
568 565 sidedata = None
569 566 if extra is not None:
570 567 for name in (
571 568 b'p1copies',
572 569 b'p2copies',
573 570 b'filesadded',
574 571 b'filesremoved',
575 572 ):
576 573 extra.pop(name, None)
577 574 if p1copies is not None:
578 575 p1copies = copies.encodecopies(sortedfiles, p1copies)
579 576 if p2copies is not None:
580 577 p2copies = copies.encodecopies(sortedfiles, p2copies)
581 578 if filesadded is not None:
582 579 filesadded = copies.encodefileindices(sortedfiles, filesadded)
583 580 if filesremoved is not None:
584 581 filesremoved = copies.encodefileindices(sortedfiles, filesremoved)
585 582 if self._copiesstorage == b'extra':
586 583 extrasentries = p1copies, p2copies, filesadded, filesremoved
587 584 if extra is None and any(x is not None for x in extrasentries):
588 585 extra = {}
589 586 if p1copies is not None:
590 587 extra[b'p1copies'] = p1copies
591 588 if p2copies is not None:
592 589 extra[b'p2copies'] = p2copies
593 590 if filesadded is not None:
594 591 extra[b'filesadded'] = filesadded
595 592 if filesremoved is not None:
596 593 extra[b'filesremoved'] = filesremoved
597 594 elif self._copiesstorage == b'changeset-sidedata':
598 595 sidedata = {}
599 596 if p1copies:
600 597 sidedata[sidedatamod.SD_P1COPIES] = p1copies
601 598 if p2copies:
602 599 sidedata[sidedatamod.SD_P2COPIES] = p2copies
603 600 if filesadded:
604 601 sidedata[sidedatamod.SD_FILESADDED] = filesadded
605 602 if filesremoved:
606 603 sidedata[sidedatamod.SD_FILESREMOVED] = filesremoved
607 604 if not sidedata:
608 605 sidedata = None
609 606
610 607 if extra:
611 608 extra = encodeextra(extra)
612 609 parseddate = b"%s %s" % (parseddate, extra)
613 610 l = [hex(manifest), user, parseddate] + sortedfiles + [b"", desc]
614 611 text = b"\n".join(l)
615 612 return self.addrevision(
616 613 text, transaction, len(self), p1, p2, sidedata=sidedata
617 614 )
618 615
619 616 def branchinfo(self, rev):
620 617 """return the branch name and open/close state of a revision
621 618
622 619 This function exists because creating a changectx object
623 620 just to access this is costly."""
624 621 extra = self.read(rev)[5]
625 622 return encoding.tolocal(extra.get(b"branch")), b'close' in extra
626 623
627 624 def _nodeduplicatecallback(self, transaction, node):
628 625 # keep track of revisions that got "re-added", eg: unbunde of know rev.
629 626 #
630 627 # We track them in a list to preserve their order from the source bundle
631 628 duplicates = transaction.changes.setdefault(b'revduplicates', [])
632 629 duplicates.append(self.rev(node))
@@ -1,2329 +1,2335 b''
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 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 collections
11 11 import contextlib
12 12 import errno
13 13 import getpass
14 14 import inspect
15 15 import os
16 16 import re
17 17 import signal
18 18 import socket
19 19 import subprocess
20 20 import sys
21 21 import traceback
22 22
23 23 from .i18n import _
24 24 from .node import hex
25 25 from .pycompat import (
26 26 getattr,
27 27 open,
28 28 setattr,
29 29 )
30 30
31 31 from . import (
32 32 color,
33 33 config,
34 34 configitems,
35 35 encoding,
36 36 error,
37 37 formatter,
38 38 loggingutil,
39 39 progress,
40 40 pycompat,
41 41 rcutil,
42 42 scmutil,
43 43 util,
44 44 )
45 45 from .utils import (
46 46 dateutil,
47 47 procutil,
48 48 resourceutil,
49 49 stringutil,
50 50 )
51 51
52 52 urlreq = util.urlreq
53 53
54 54 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
55 55 _keepalnum = b''.join(
56 56 c for c in map(pycompat.bytechr, range(256)) if not c.isalnum()
57 57 )
58 58
59 59 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
60 60 tweakrc = b"""
61 61 [ui]
62 62 # The rollback command is dangerous. As a rule, don't use it.
63 63 rollback = False
64 64 # Make `hg status` report copy information
65 65 statuscopies = yes
66 66 # Prefer curses UIs when available. Revert to plain-text with `text`.
67 67 interface = curses
68 68 # Make compatible commands emit cwd-relative paths by default.
69 69 relative-paths = yes
70 70
71 71 [commands]
72 72 # Grep working directory by default.
73 73 grep.all-files = True
74 74 # Refuse to perform an `hg update` that would cause a file content merge
75 75 update.check = noconflict
76 76 # Show conflicts information in `hg status`
77 77 status.verbose = True
78 78 # Make `hg resolve` with no action (like `-m`) fail instead of re-merging.
79 79 resolve.explicit-re-merge = True
80 80
81 81 [diff]
82 82 git = 1
83 83 showfunc = 1
84 84 word-diff = 1
85 85 """
86 86
87 87 samplehgrcs = {
88 88 b'user': b"""# example user config (see 'hg help config' for more info)
89 89 [ui]
90 90 # name and email, e.g.
91 91 # username = Jane Doe <jdoe@example.com>
92 92 username =
93 93
94 94 # We recommend enabling tweakdefaults to get slight improvements to
95 95 # the UI over time. Make sure to set HGPLAIN in the environment when
96 96 # writing scripts!
97 97 # tweakdefaults = True
98 98
99 99 # uncomment to disable color in command output
100 100 # (see 'hg help color' for details)
101 101 # color = never
102 102
103 103 # uncomment to disable command output pagination
104 104 # (see 'hg help pager' for details)
105 105 # paginate = never
106 106
107 107 [extensions]
108 108 # uncomment the lines below to enable some popular extensions
109 109 # (see 'hg help extensions' for more info)
110 110 #
111 111 # histedit =
112 112 # rebase =
113 113 # uncommit =
114 114 """,
115 115 b'cloned': b"""# example repository config (see 'hg help config' for more info)
116 116 [paths]
117 117 default = %s
118 118
119 119 # path aliases to other clones of this repo in URLs or filesystem paths
120 120 # (see 'hg help config.paths' for more info)
121 121 #
122 122 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
123 123 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
124 124 # my-clone = /home/jdoe/jdoes-clone
125 125
126 126 [ui]
127 127 # name and email (local to this repository, optional), e.g.
128 128 # username = Jane Doe <jdoe@example.com>
129 129 """,
130 130 b'local': b"""# example repository config (see 'hg help config' for more info)
131 131 [paths]
132 132 # path aliases to other clones of this repo in URLs or filesystem paths
133 133 # (see 'hg help config.paths' for more info)
134 134 #
135 135 # default = http://example.com/hg/example-repo
136 136 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
137 137 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
138 138 # my-clone = /home/jdoe/jdoes-clone
139 139
140 140 [ui]
141 141 # name and email (local to this repository, optional), e.g.
142 142 # username = Jane Doe <jdoe@example.com>
143 143 """,
144 144 b'global': b"""# example system-wide hg config (see 'hg help config' for more info)
145 145
146 146 [ui]
147 147 # uncomment to disable color in command output
148 148 # (see 'hg help color' for details)
149 149 # color = never
150 150
151 151 # uncomment to disable command output pagination
152 152 # (see 'hg help pager' for details)
153 153 # paginate = never
154 154
155 155 [extensions]
156 156 # uncomment the lines below to enable some popular extensions
157 157 # (see 'hg help extensions' for more info)
158 158 #
159 159 # blackbox =
160 160 # churn =
161 161 """,
162 162 }
163 163
164 164
165 165 def _maybestrurl(maybebytes):
166 166 return pycompat.rapply(pycompat.strurl, maybebytes)
167 167
168 168
169 169 def _maybebytesurl(maybestr):
170 170 return pycompat.rapply(pycompat.bytesurl, maybestr)
171 171
172 172
173 173 class httppasswordmgrdbproxy(object):
174 174 """Delays loading urllib2 until it's needed."""
175 175
176 176 def __init__(self):
177 177 self._mgr = None
178 178
179 179 def _get_mgr(self):
180 180 if self._mgr is None:
181 181 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
182 182 return self._mgr
183 183
184 184 def add_password(self, realm, uris, user, passwd):
185 185 return self._get_mgr().add_password(
186 186 _maybestrurl(realm),
187 187 _maybestrurl(uris),
188 188 _maybestrurl(user),
189 189 _maybestrurl(passwd),
190 190 )
191 191
192 192 def find_user_password(self, realm, uri):
193 193 mgr = self._get_mgr()
194 194 return _maybebytesurl(
195 195 mgr.find_user_password(_maybestrurl(realm), _maybestrurl(uri))
196 196 )
197 197
198 198
199 199 def _catchterm(*args):
200 200 raise error.SignalInterrupt
201 201
202 202
203 203 # unique object used to detect no default value has been provided when
204 204 # retrieving configuration value.
205 205 _unset = object()
206 206
207 207 # _reqexithandlers: callbacks run at the end of a request
208 208 _reqexithandlers = []
209 209
210 210
211 211 class ui(object):
212 212 def __init__(self, src=None):
213 213 """Create a fresh new ui object if no src given
214 214
215 215 Use uimod.ui.load() to create a ui which knows global and user configs.
216 216 In most cases, you should use ui.copy() to create a copy of an existing
217 217 ui object.
218 218 """
219 219 # _buffers: used for temporary capture of output
220 220 self._buffers = []
221 221 # 3-tuple describing how each buffer in the stack behaves.
222 222 # Values are (capture stderr, capture subprocesses, apply labels).
223 223 self._bufferstates = []
224 224 # When a buffer is active, defines whether we are expanding labels.
225 225 # This exists to prevent an extra list lookup.
226 226 self._bufferapplylabels = None
227 227 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
228 228 self._reportuntrusted = True
229 229 self._knownconfig = configitems.coreitems
230 230 self._ocfg = config.config() # overlay
231 231 self._tcfg = config.config() # trusted
232 232 self._ucfg = config.config() # untrusted
233 233 self._trustusers = set()
234 234 self._trustgroups = set()
235 235 self.callhooks = True
236 236 # Insecure server connections requested.
237 237 self.insecureconnections = False
238 238 # Blocked time
239 239 self.logblockedtimes = False
240 240 # color mode: see mercurial/color.py for possible value
241 241 self._colormode = None
242 242 self._terminfoparams = {}
243 243 self._styles = {}
244 244 self._uninterruptible = False
245 245
246 246 if src:
247 247 self._fout = src._fout
248 248 self._ferr = src._ferr
249 249 self._fin = src._fin
250 250 self._fmsg = src._fmsg
251 251 self._fmsgout = src._fmsgout
252 252 self._fmsgerr = src._fmsgerr
253 253 self._finoutredirected = src._finoutredirected
254 254 self._loggers = src._loggers.copy()
255 255 self.pageractive = src.pageractive
256 256 self._disablepager = src._disablepager
257 257 self._tweaked = src._tweaked
258 258
259 259 self._tcfg = src._tcfg.copy()
260 260 self._ucfg = src._ucfg.copy()
261 261 self._ocfg = src._ocfg.copy()
262 262 self._trustusers = src._trustusers.copy()
263 263 self._trustgroups = src._trustgroups.copy()
264 264 self.environ = src.environ
265 265 self.callhooks = src.callhooks
266 266 self.insecureconnections = src.insecureconnections
267 267 self._colormode = src._colormode
268 268 self._terminfoparams = src._terminfoparams.copy()
269 269 self._styles = src._styles.copy()
270 270
271 271 self.fixconfig()
272 272
273 273 self.httppasswordmgrdb = src.httppasswordmgrdb
274 274 self._blockedtimes = src._blockedtimes
275 275 else:
276 276 self._fout = procutil.stdout
277 277 self._ferr = procutil.stderr
278 278 self._fin = procutil.stdin
279 279 self._fmsg = None
280 280 self._fmsgout = self.fout # configurable
281 281 self._fmsgerr = self.ferr # configurable
282 282 self._finoutredirected = False
283 283 self._loggers = {}
284 284 self.pageractive = False
285 285 self._disablepager = False
286 286 self._tweaked = False
287 287
288 288 # shared read-only environment
289 289 self.environ = encoding.environ
290 290
291 291 self.httppasswordmgrdb = httppasswordmgrdbproxy()
292 292 self._blockedtimes = collections.defaultdict(int)
293 293
294 294 allowed = self.configlist(b'experimental', b'exportableenviron')
295 295 if b'*' in allowed:
296 296 self._exportableenviron = self.environ
297 297 else:
298 298 self._exportableenviron = {}
299 299 for k in allowed:
300 300 if k in self.environ:
301 301 self._exportableenviron[k] = self.environ[k]
302 302
303 303 @classmethod
304 304 def load(cls):
305 305 """Create a ui and load global and user configs"""
306 306 u = cls()
307 307 # we always trust global config files and environment variables
308 308 for t, f in rcutil.rccomponents():
309 309 if t == b'path':
310 310 u.readconfig(f, trust=True)
311 311 elif t == b'resource':
312 312 u.read_resource_config(f, trust=True)
313 313 elif t == b'items':
314 314 sections = set()
315 315 for section, name, value, source in f:
316 316 # do not set u._ocfg
317 317 # XXX clean this up once immutable config object is a thing
318 318 u._tcfg.set(section, name, value, source)
319 319 u._ucfg.set(section, name, value, source)
320 320 sections.add(section)
321 321 for section in sections:
322 322 u.fixconfig(section=section)
323 323 else:
324 324 raise error.ProgrammingError(b'unknown rctype: %s' % t)
325 325 u._maybetweakdefaults()
326 326 return u
327 327
328 328 def _maybetweakdefaults(self):
329 329 if not self.configbool(b'ui', b'tweakdefaults'):
330 330 return
331 331 if self._tweaked or self.plain(b'tweakdefaults'):
332 332 return
333 333
334 334 # Note: it is SUPER IMPORTANT that you set self._tweaked to
335 335 # True *before* any calls to setconfig(), otherwise you'll get
336 336 # infinite recursion between setconfig and this method.
337 337 #
338 338 # TODO: We should extract an inner method in setconfig() to
339 339 # avoid this weirdness.
340 340 self._tweaked = True
341 341 tmpcfg = config.config()
342 342 tmpcfg.parse(b'<tweakdefaults>', tweakrc)
343 343 for section in tmpcfg:
344 344 for name, value in tmpcfg.items(section):
345 345 if not self.hasconfig(section, name):
346 346 self.setconfig(section, name, value, b"<tweakdefaults>")
347 347
348 348 def copy(self):
349 349 return self.__class__(self)
350 350
351 351 def resetstate(self):
352 352 """Clear internal state that shouldn't persist across commands"""
353 353 if self._progbar:
354 354 self._progbar.resetstate() # reset last-print time of progress bar
355 355 self.httppasswordmgrdb = httppasswordmgrdbproxy()
356 356
357 357 @contextlib.contextmanager
358 358 def timeblockedsection(self, key):
359 359 # this is open-coded below - search for timeblockedsection to find them
360 360 starttime = util.timer()
361 361 try:
362 362 yield
363 363 finally:
364 364 self._blockedtimes[key + b'_blocked'] += (
365 365 util.timer() - starttime
366 366 ) * 1000
367 367
368 368 @contextlib.contextmanager
369 369 def uninterruptible(self):
370 370 """Mark an operation as unsafe.
371 371
372 372 Most operations on a repository are safe to interrupt, but a
373 373 few are risky (for example repair.strip). This context manager
374 374 lets you advise Mercurial that something risky is happening so
375 375 that control-C etc can be blocked if desired.
376 376 """
377 377 enabled = self.configbool(b'experimental', b'nointerrupt')
378 378 if enabled and self.configbool(
379 379 b'experimental', b'nointerrupt-interactiveonly'
380 380 ):
381 381 enabled = self.interactive()
382 382 if self._uninterruptible or not enabled:
383 383 # if nointerrupt support is turned off, the process isn't
384 384 # interactive, or we're already in an uninterruptible
385 385 # block, do nothing.
386 386 yield
387 387 return
388 388
389 389 def warn():
390 390 self.warn(_(b"shutting down cleanly\n"))
391 391 self.warn(
392 392 _(b"press ^C again to terminate immediately (dangerous)\n")
393 393 )
394 394 return True
395 395
396 396 with procutil.uninterruptible(warn):
397 397 try:
398 398 self._uninterruptible = True
399 399 yield
400 400 finally:
401 401 self._uninterruptible = False
402 402
403 403 def formatter(self, topic, opts):
404 404 return formatter.formatter(self, self, topic, opts)
405 405
406 406 def _trusted(self, fp, f):
407 407 st = util.fstat(fp)
408 408 if util.isowner(st):
409 409 return True
410 410
411 411 tusers, tgroups = self._trustusers, self._trustgroups
412 412 if b'*' in tusers or b'*' in tgroups:
413 413 return True
414 414
415 415 user = util.username(st.st_uid)
416 416 group = util.groupname(st.st_gid)
417 417 if user in tusers or group in tgroups or user == util.username():
418 418 return True
419 419
420 420 if self._reportuntrusted:
421 421 self.warn(
422 422 _(
423 423 b'not trusting file %s from untrusted '
424 424 b'user %s, group %s\n'
425 425 )
426 426 % (f, user, group)
427 427 )
428 428 return False
429 429
430 430 def read_resource_config(
431 431 self, name, root=None, trust=False, sections=None, remap=None
432 432 ):
433 433 try:
434 434 fp = resourceutil.open_resource(name[0], name[1])
435 435 except IOError:
436 436 if not sections: # ignore unless we were looking for something
437 437 return
438 438 raise
439 439
440 440 self._readconfig(
441 441 b'resource:%s.%s' % name, fp, root, trust, sections, remap
442 442 )
443 443
444 444 def readconfig(
445 445 self, filename, root=None, trust=False, sections=None, remap=None
446 446 ):
447 447 try:
448 448 fp = open(filename, 'rb')
449 449 except IOError:
450 450 if not sections: # ignore unless we were looking for something
451 451 return
452 452 raise
453 453
454 454 self._readconfig(filename, fp, root, trust, sections, remap)
455 455
456 456 def _readconfig(
457 457 self, filename, fp, root=None, trust=False, sections=None, remap=None
458 458 ):
459 459 with fp:
460 460 cfg = config.config()
461 461 trusted = sections or trust or self._trusted(fp, filename)
462 462
463 463 try:
464 464 cfg.read(filename, fp, sections=sections, remap=remap)
465 465 except error.ParseError as inst:
466 466 if trusted:
467 467 raise
468 468 self.warn(_(b'ignored: %s\n') % stringutil.forcebytestr(inst))
469 469
470 470 self._applyconfig(cfg, trusted, root)
471 471
472 472 def applyconfig(self, configitems, source=b"", root=None):
473 473 """Add configitems from a non-file source. Unlike with ``setconfig()``,
474 474 they can be overridden by subsequent config file reads. The items are
475 475 in the same format as ``configoverride()``, namely a dict of the
476 476 following structures: {(section, name) : value}
477 477
478 478 Typically this is used by extensions that inject themselves into the
479 479 config file load procedure by monkeypatching ``localrepo.loadhgrc()``.
480 480 """
481 481 cfg = config.config()
482 482
483 483 for (section, name), value in configitems.items():
484 484 cfg.set(section, name, value, source)
485 485
486 486 self._applyconfig(cfg, True, root)
487 487
488 488 def _applyconfig(self, cfg, trusted, root):
489 489 if self.plain():
490 490 for k in (
491 491 b'debug',
492 492 b'fallbackencoding',
493 493 b'quiet',
494 494 b'slash',
495 495 b'logtemplate',
496 496 b'message-output',
497 497 b'statuscopies',
498 498 b'style',
499 499 b'traceback',
500 500 b'verbose',
501 501 ):
502 502 if k in cfg[b'ui']:
503 503 del cfg[b'ui'][k]
504 504 for k, v in cfg.items(b'defaults'):
505 505 del cfg[b'defaults'][k]
506 506 for k, v in cfg.items(b'commands'):
507 507 del cfg[b'commands'][k]
508 508 # Don't remove aliases from the configuration if in the exceptionlist
509 509 if self.plain(b'alias'):
510 510 for k, v in cfg.items(b'alias'):
511 511 del cfg[b'alias'][k]
512 512 if self.plain(b'revsetalias'):
513 513 for k, v in cfg.items(b'revsetalias'):
514 514 del cfg[b'revsetalias'][k]
515 515 if self.plain(b'templatealias'):
516 516 for k, v in cfg.items(b'templatealias'):
517 517 del cfg[b'templatealias'][k]
518 518
519 519 if trusted:
520 520 self._tcfg.update(cfg)
521 521 self._tcfg.update(self._ocfg)
522 522 self._ucfg.update(cfg)
523 523 self._ucfg.update(self._ocfg)
524 524
525 525 if root is None:
526 526 root = os.path.expanduser(b'~')
527 527 self.fixconfig(root=root)
528 528
529 529 def fixconfig(self, root=None, section=None):
530 530 if section in (None, b'paths'):
531 531 # expand vars and ~
532 532 # translate paths relative to root (or home) into absolute paths
533 533 root = root or encoding.getcwd()
534 534 for c in self._tcfg, self._ucfg, self._ocfg:
535 535 for n, p in c.items(b'paths'):
536 536 # Ignore sub-options.
537 537 if b':' in n:
538 538 continue
539 539 if not p:
540 540 continue
541 541 if b'%%' in p:
542 542 s = self.configsource(b'paths', n) or b'none'
543 543 self.warn(
544 544 _(b"(deprecated '%%' in path %s=%s from %s)\n")
545 545 % (n, p, s)
546 546 )
547 547 p = p.replace(b'%%', b'%')
548 548 p = util.expandpath(p)
549 549 if not util.hasscheme(p) and not os.path.isabs(p):
550 550 p = os.path.normpath(os.path.join(root, p))
551 551 c.set(b"paths", n, p)
552 552
553 553 if section in (None, b'ui'):
554 554 # update ui options
555 555 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
556 556 self.debugflag = self.configbool(b'ui', b'debug')
557 557 self.verbose = self.debugflag or self.configbool(b'ui', b'verbose')
558 558 self.quiet = not self.debugflag and self.configbool(b'ui', b'quiet')
559 559 if self.verbose and self.quiet:
560 560 self.quiet = self.verbose = False
561 561 self._reportuntrusted = self.debugflag or self.configbool(
562 562 b"ui", b"report_untrusted"
563 563 )
564 564 self.tracebackflag = self.configbool(b'ui', b'traceback')
565 565 self.logblockedtimes = self.configbool(b'ui', b'logblockedtimes')
566 566
567 567 if section in (None, b'trusted'):
568 568 # update trust information
569 569 self._trustusers.update(self.configlist(b'trusted', b'users'))
570 570 self._trustgroups.update(self.configlist(b'trusted', b'groups'))
571 571
572 572 if section in (None, b'devel', b'ui') and self.debugflag:
573 573 tracked = set()
574 574 if self.configbool(b'devel', b'debug.extensions'):
575 575 tracked.add(b'extension')
576 576 if tracked:
577 577 logger = loggingutil.fileobjectlogger(self._ferr, tracked)
578 578 self.setlogger(b'debug', logger)
579 579
580 580 def backupconfig(self, section, item):
581 581 return (
582 582 self._ocfg.backup(section, item),
583 583 self._tcfg.backup(section, item),
584 584 self._ucfg.backup(section, item),
585 585 )
586 586
587 587 def restoreconfig(self, data):
588 588 self._ocfg.restore(data[0])
589 589 self._tcfg.restore(data[1])
590 590 self._ucfg.restore(data[2])
591 591
592 592 def setconfig(self, section, name, value, source=b''):
593 593 for cfg in (self._ocfg, self._tcfg, self._ucfg):
594 594 cfg.set(section, name, value, source)
595 595 self.fixconfig(section=section)
596 596 self._maybetweakdefaults()
597 597
598 598 def _data(self, untrusted):
599 599 return untrusted and self._ucfg or self._tcfg
600 600
601 601 def configsource(self, section, name, untrusted=False):
602 602 return self._data(untrusted).source(section, name)
603 603
604 604 def config(self, section, name, default=_unset, untrusted=False):
605 605 """return the plain string version of a config"""
606 606 value = self._config(
607 607 section, name, default=default, untrusted=untrusted
608 608 )
609 609 if value is _unset:
610 610 return None
611 611 return value
612 612
613 613 def _config(self, section, name, default=_unset, untrusted=False):
614 614 value = itemdefault = default
615 615 item = self._knownconfig.get(section, {}).get(name)
616 616 alternates = [(section, name)]
617 617
618 618 if item is not None:
619 619 alternates.extend(item.alias)
620 620 if callable(item.default):
621 621 itemdefault = item.default()
622 622 else:
623 623 itemdefault = item.default
624 624 else:
625 625 msg = b"accessing unregistered config item: '%s.%s'"
626 626 msg %= (section, name)
627 627 self.develwarn(msg, 2, b'warn-config-unknown')
628 628
629 629 if default is _unset:
630 630 if item is None:
631 631 value = default
632 632 elif item.default is configitems.dynamicdefault:
633 633 value = None
634 634 msg = b"config item requires an explicit default value: '%s.%s'"
635 635 msg %= (section, name)
636 636 self.develwarn(msg, 2, b'warn-config-default')
637 637 else:
638 638 value = itemdefault
639 639 elif (
640 640 item is not None
641 641 and item.default is not configitems.dynamicdefault
642 642 and default != itemdefault
643 643 ):
644 644 msg = (
645 645 b"specifying a mismatched default value for a registered "
646 646 b"config item: '%s.%s' '%s'"
647 647 )
648 648 msg %= (section, name, pycompat.bytestr(default))
649 649 self.develwarn(msg, 2, b'warn-config-default')
650 650
651 651 for s, n in alternates:
652 652 candidate = self._data(untrusted).get(s, n, None)
653 653 if candidate is not None:
654 654 value = candidate
655 655 break
656 656
657 657 if self.debugflag and not untrusted and self._reportuntrusted:
658 658 for s, n in alternates:
659 659 uvalue = self._ucfg.get(s, n)
660 660 if uvalue is not None and uvalue != value:
661 661 self.debug(
662 662 b"ignoring untrusted configuration option "
663 663 b"%s.%s = %s\n" % (s, n, uvalue)
664 664 )
665 665 return value
666 666
667 667 def configsuboptions(self, section, name, default=_unset, untrusted=False):
668 668 """Get a config option and all sub-options.
669 669
670 670 Some config options have sub-options that are declared with the
671 671 format "key:opt = value". This method is used to return the main
672 672 option and all its declared sub-options.
673 673
674 674 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
675 675 is a dict of defined sub-options where keys and values are strings.
676 676 """
677 677 main = self.config(section, name, default, untrusted=untrusted)
678 678 data = self._data(untrusted)
679 679 sub = {}
680 680 prefix = b'%s:' % name
681 681 for k, v in data.items(section):
682 682 if k.startswith(prefix):
683 683 sub[k[len(prefix) :]] = v
684 684
685 685 if self.debugflag and not untrusted and self._reportuntrusted:
686 686 for k, v in sub.items():
687 687 uvalue = self._ucfg.get(section, b'%s:%s' % (name, k))
688 688 if uvalue is not None and uvalue != v:
689 689 self.debug(
690 690 b'ignoring untrusted configuration option '
691 691 b'%s:%s.%s = %s\n' % (section, name, k, uvalue)
692 692 )
693 693
694 694 return main, sub
695 695
696 696 def configpath(self, section, name, default=_unset, untrusted=False):
697 697 """get a path config item, expanded relative to repo root or config
698 698 file"""
699 699 v = self.config(section, name, default, untrusted)
700 700 if v is None:
701 701 return None
702 702 if not os.path.isabs(v) or b"://" not in v:
703 703 src = self.configsource(section, name, untrusted)
704 704 if b':' in src:
705 705 base = os.path.dirname(src.rsplit(b':')[0])
706 706 v = os.path.join(base, os.path.expanduser(v))
707 707 return v
708 708
709 709 def configbool(self, section, name, default=_unset, untrusted=False):
710 710 """parse a configuration element as a boolean
711 711
712 712 >>> u = ui(); s = b'foo'
713 713 >>> u.setconfig(s, b'true', b'yes')
714 714 >>> u.configbool(s, b'true')
715 715 True
716 716 >>> u.setconfig(s, b'false', b'no')
717 717 >>> u.configbool(s, b'false')
718 718 False
719 719 >>> u.configbool(s, b'unknown')
720 720 False
721 721 >>> u.configbool(s, b'unknown', True)
722 722 True
723 723 >>> u.setconfig(s, b'invalid', b'somevalue')
724 724 >>> u.configbool(s, b'invalid')
725 725 Traceback (most recent call last):
726 726 ...
727 727 ConfigError: foo.invalid is not a boolean ('somevalue')
728 728 """
729 729
730 730 v = self._config(section, name, default, untrusted=untrusted)
731 731 if v is None:
732 732 return v
733 733 if v is _unset:
734 734 if default is _unset:
735 735 return False
736 736 return default
737 737 if isinstance(v, bool):
738 738 return v
739 739 b = stringutil.parsebool(v)
740 740 if b is None:
741 741 raise error.ConfigError(
742 742 _(b"%s.%s is not a boolean ('%s')") % (section, name, v)
743 743 )
744 744 return b
745 745
746 746 def configwith(
747 747 self, convert, section, name, default=_unset, desc=None, untrusted=False
748 748 ):
749 749 """parse a configuration element with a conversion function
750 750
751 751 >>> u = ui(); s = b'foo'
752 752 >>> u.setconfig(s, b'float1', b'42')
753 753 >>> u.configwith(float, s, b'float1')
754 754 42.0
755 755 >>> u.setconfig(s, b'float2', b'-4.25')
756 756 >>> u.configwith(float, s, b'float2')
757 757 -4.25
758 758 >>> u.configwith(float, s, b'unknown', 7)
759 759 7.0
760 760 >>> u.setconfig(s, b'invalid', b'somevalue')
761 761 >>> u.configwith(float, s, b'invalid')
762 762 Traceback (most recent call last):
763 763 ...
764 764 ConfigError: foo.invalid is not a valid float ('somevalue')
765 765 >>> u.configwith(float, s, b'invalid', desc=b'womble')
766 766 Traceback (most recent call last):
767 767 ...
768 768 ConfigError: foo.invalid is not a valid womble ('somevalue')
769 769 """
770 770
771 771 v = self.config(section, name, default, untrusted)
772 772 if v is None:
773 773 return v # do not attempt to convert None
774 774 try:
775 775 return convert(v)
776 776 except (ValueError, error.ParseError):
777 777 if desc is None:
778 778 desc = pycompat.sysbytes(convert.__name__)
779 779 raise error.ConfigError(
780 780 _(b"%s.%s is not a valid %s ('%s')") % (section, name, desc, v)
781 781 )
782 782
783 783 def configint(self, section, name, default=_unset, untrusted=False):
784 784 """parse a configuration element as an integer
785 785
786 786 >>> u = ui(); s = b'foo'
787 787 >>> u.setconfig(s, b'int1', b'42')
788 788 >>> u.configint(s, b'int1')
789 789 42
790 790 >>> u.setconfig(s, b'int2', b'-42')
791 791 >>> u.configint(s, b'int2')
792 792 -42
793 793 >>> u.configint(s, b'unknown', 7)
794 794 7
795 795 >>> u.setconfig(s, b'invalid', b'somevalue')
796 796 >>> u.configint(s, b'invalid')
797 797 Traceback (most recent call last):
798 798 ...
799 799 ConfigError: foo.invalid is not a valid integer ('somevalue')
800 800 """
801 801
802 802 return self.configwith(
803 803 int, section, name, default, b'integer', untrusted
804 804 )
805 805
806 806 def configbytes(self, section, name, default=_unset, untrusted=False):
807 807 """parse a configuration element as a quantity in bytes
808 808
809 809 Units can be specified as b (bytes), k or kb (kilobytes), m or
810 810 mb (megabytes), g or gb (gigabytes).
811 811
812 812 >>> u = ui(); s = b'foo'
813 813 >>> u.setconfig(s, b'val1', b'42')
814 814 >>> u.configbytes(s, b'val1')
815 815 42
816 816 >>> u.setconfig(s, b'val2', b'42.5 kb')
817 817 >>> u.configbytes(s, b'val2')
818 818 43520
819 819 >>> u.configbytes(s, b'unknown', b'7 MB')
820 820 7340032
821 821 >>> u.setconfig(s, b'invalid', b'somevalue')
822 822 >>> u.configbytes(s, b'invalid')
823 823 Traceback (most recent call last):
824 824 ...
825 825 ConfigError: foo.invalid is not a byte quantity ('somevalue')
826 826 """
827 827
828 828 value = self._config(section, name, default, untrusted)
829 829 if value is _unset:
830 830 if default is _unset:
831 831 default = 0
832 832 value = default
833 833 if not isinstance(value, bytes):
834 834 return value
835 835 try:
836 836 return util.sizetoint(value)
837 837 except error.ParseError:
838 838 raise error.ConfigError(
839 839 _(b"%s.%s is not a byte quantity ('%s')")
840 840 % (section, name, value)
841 841 )
842 842
843 843 def configlist(self, section, name, default=_unset, untrusted=False):
844 844 """parse a configuration element as a list of comma/space separated
845 845 strings
846 846
847 847 >>> u = ui(); s = b'foo'
848 848 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
849 849 >>> u.configlist(s, b'list1')
850 850 ['this', 'is', 'a small', 'test']
851 851 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
852 852 >>> u.configlist(s, b'list2')
853 853 ['this', 'is', 'a small', 'test']
854 854 """
855 855 # default is not always a list
856 856 v = self.configwith(
857 857 config.parselist, section, name, default, b'list', untrusted
858 858 )
859 859 if isinstance(v, bytes):
860 860 return config.parselist(v)
861 861 elif v is None:
862 862 return []
863 863 return v
864 864
865 865 def configdate(self, section, name, default=_unset, untrusted=False):
866 866 """parse a configuration element as a tuple of ints
867 867
868 868 >>> u = ui(); s = b'foo'
869 869 >>> u.setconfig(s, b'date', b'0 0')
870 870 >>> u.configdate(s, b'date')
871 871 (0, 0)
872 872 """
873 873 if self.config(section, name, default, untrusted):
874 874 return self.configwith(
875 875 dateutil.parsedate, section, name, default, b'date', untrusted
876 876 )
877 877 if default is _unset:
878 878 return None
879 879 return default
880 880
881 881 def configdefault(self, section, name):
882 882 """returns the default value of the config item"""
883 883 item = self._knownconfig.get(section, {}).get(name)
884 884 itemdefault = None
885 885 if item is not None:
886 886 if callable(item.default):
887 887 itemdefault = item.default()
888 888 else:
889 889 itemdefault = item.default
890 890 return itemdefault
891 891
892 892 def hasconfig(self, section, name, untrusted=False):
893 893 return self._data(untrusted).hasitem(section, name)
894 894
895 895 def has_section(self, section, untrusted=False):
896 896 '''tell whether section exists in config.'''
897 897 return section in self._data(untrusted)
898 898
899 899 def configitems(self, section, untrusted=False, ignoresub=False):
900 900 items = self._data(untrusted).items(section)
901 901 if ignoresub:
902 902 items = [i for i in items if b':' not in i[0]]
903 903 if self.debugflag and not untrusted and self._reportuntrusted:
904 904 for k, v in self._ucfg.items(section):
905 905 if self._tcfg.get(section, k) != v:
906 906 self.debug(
907 907 b"ignoring untrusted configuration option "
908 908 b"%s.%s = %s\n" % (section, k, v)
909 909 )
910 910 return items
911 911
912 912 def walkconfig(self, untrusted=False):
913 913 cfg = self._data(untrusted)
914 914 for section in cfg.sections():
915 915 for name, value in self.configitems(section, untrusted):
916 916 yield section, name, value
917 917
918 918 def plain(self, feature=None):
919 919 '''is plain mode active?
920 920
921 921 Plain mode means that all configuration variables which affect
922 922 the behavior and output of Mercurial should be
923 923 ignored. Additionally, the output should be stable,
924 924 reproducible and suitable for use in scripts or applications.
925 925
926 926 The only way to trigger plain mode is by setting either the
927 927 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
928 928
929 929 The return value can either be
930 930 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
931 931 - False if feature is disabled by default and not included in HGPLAIN
932 932 - True otherwise
933 933 '''
934 934 if (
935 935 b'HGPLAIN' not in encoding.environ
936 936 and b'HGPLAINEXCEPT' not in encoding.environ
937 937 ):
938 938 return False
939 939 exceptions = (
940 940 encoding.environ.get(b'HGPLAINEXCEPT', b'').strip().split(b',')
941 941 )
942 942 # TODO: add support for HGPLAIN=+feature,-feature syntax
943 943 if b'+strictflags' not in encoding.environ.get(b'HGPLAIN', b'').split(
944 944 b','
945 945 ):
946 946 exceptions.append(b'strictflags')
947 947 if feature and exceptions:
948 948 return feature not in exceptions
949 949 return True
950 950
951 951 def username(self, acceptempty=False):
952 952 """Return default username to be used in commits.
953 953
954 954 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
955 955 and stop searching if one of these is set.
956 956 If not found and acceptempty is True, returns None.
957 957 If not found and ui.askusername is True, ask the user, else use
958 958 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
959 959 If no username could be found, raise an Abort error.
960 960 """
961 961 user = encoding.environ.get(b"HGUSER")
962 962 if user is None:
963 963 user = self.config(b"ui", b"username")
964 964 if user is not None:
965 965 user = os.path.expandvars(user)
966 966 if user is None:
967 967 user = encoding.environ.get(b"EMAIL")
968 968 if user is None and acceptempty:
969 969 return user
970 970 if user is None and self.configbool(b"ui", b"askusername"):
971 971 user = self.prompt(_(b"enter a commit username:"), default=None)
972 972 if user is None and not self.interactive():
973 973 try:
974 974 user = b'%s@%s' % (
975 975 procutil.getuser(),
976 976 encoding.strtolocal(socket.getfqdn()),
977 977 )
978 978 self.warn(_(b"no username found, using '%s' instead\n") % user)
979 979 except KeyError:
980 980 pass
981 981 if not user:
982 982 raise error.Abort(
983 983 _(b'no username supplied'),
984 984 hint=_(b"use 'hg config --edit' " b'to set your username'),
985 985 )
986 986 if b"\n" in user:
987 987 raise error.Abort(
988 988 _(b"username %r contains a newline\n") % pycompat.bytestr(user)
989 989 )
990 990 return user
991 991
992 992 def shortuser(self, user):
993 993 """Return a short representation of a user name or email address."""
994 994 if not self.verbose:
995 995 user = stringutil.shortuser(user)
996 996 return user
997 997
998 998 def expandpath(self, loc, default=None):
999 999 """Return repository location relative to cwd or from [paths]"""
1000 1000 try:
1001 1001 p = self.paths.getpath(loc)
1002 1002 if p:
1003 1003 return p.rawloc
1004 1004 except error.RepoError:
1005 1005 pass
1006 1006
1007 1007 if default:
1008 1008 try:
1009 1009 p = self.paths.getpath(default)
1010 1010 if p:
1011 1011 return p.rawloc
1012 1012 except error.RepoError:
1013 1013 pass
1014 1014
1015 1015 return loc
1016 1016
1017 1017 @util.propertycache
1018 1018 def paths(self):
1019 1019 return paths(self)
1020 1020
1021 1021 @property
1022 1022 def fout(self):
1023 1023 return self._fout
1024 1024
1025 1025 @fout.setter
1026 1026 def fout(self, f):
1027 1027 self._fout = f
1028 1028 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1029 1029
1030 1030 @property
1031 1031 def ferr(self):
1032 1032 return self._ferr
1033 1033
1034 1034 @ferr.setter
1035 1035 def ferr(self, f):
1036 1036 self._ferr = f
1037 1037 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1038 1038
1039 1039 @property
1040 1040 def fin(self):
1041 1041 return self._fin
1042 1042
1043 1043 @fin.setter
1044 1044 def fin(self, f):
1045 1045 self._fin = f
1046 1046
1047 1047 @property
1048 1048 def fmsg(self):
1049 1049 """Stream dedicated for status/error messages; may be None if
1050 1050 fout/ferr are used"""
1051 1051 return self._fmsg
1052 1052
1053 1053 @fmsg.setter
1054 1054 def fmsg(self, f):
1055 1055 self._fmsg = f
1056 1056 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1057 1057
1058 1058 def pushbuffer(self, error=False, subproc=False, labeled=False):
1059 1059 """install a buffer to capture standard output of the ui object
1060 1060
1061 1061 If error is True, the error output will be captured too.
1062 1062
1063 1063 If subproc is True, output from subprocesses (typically hooks) will be
1064 1064 captured too.
1065 1065
1066 1066 If labeled is True, any labels associated with buffered
1067 1067 output will be handled. By default, this has no effect
1068 1068 on the output returned, but extensions and GUI tools may
1069 1069 handle this argument and returned styled output. If output
1070 1070 is being buffered so it can be captured and parsed or
1071 1071 processed, labeled should not be set to True.
1072 1072 """
1073 1073 self._buffers.append([])
1074 1074 self._bufferstates.append((error, subproc, labeled))
1075 1075 self._bufferapplylabels = labeled
1076 1076
1077 1077 def popbuffer(self):
1078 1078 '''pop the last buffer and return the buffered output'''
1079 1079 self._bufferstates.pop()
1080 1080 if self._bufferstates:
1081 1081 self._bufferapplylabels = self._bufferstates[-1][2]
1082 1082 else:
1083 1083 self._bufferapplylabels = None
1084 1084
1085 1085 return b"".join(self._buffers.pop())
1086 1086
1087 1087 def _isbuffered(self, dest):
1088 1088 if dest is self._fout:
1089 1089 return bool(self._buffers)
1090 1090 if dest is self._ferr:
1091 1091 return bool(self._bufferstates and self._bufferstates[-1][0])
1092 1092 return False
1093 1093
1094 1094 def canwritewithoutlabels(self):
1095 1095 '''check if write skips the label'''
1096 1096 if self._buffers and not self._bufferapplylabels:
1097 1097 return True
1098 1098 return self._colormode is None
1099 1099
1100 1100 def canbatchlabeledwrites(self):
1101 1101 '''check if write calls with labels are batchable'''
1102 1102 # Windows color printing is special, see ``write``.
1103 1103 return self._colormode != b'win32'
1104 1104
1105 1105 def write(self, *args, **opts):
1106 1106 '''write args to output
1107 1107
1108 1108 By default, this method simply writes to the buffer or stdout.
1109 1109 Color mode can be set on the UI class to have the output decorated
1110 1110 with color modifier before being written to stdout.
1111 1111
1112 1112 The color used is controlled by an optional keyword argument, "label".
1113 1113 This should be a string containing label names separated by space.
1114 1114 Label names take the form of "topic.type". For example, ui.debug()
1115 1115 issues a label of "ui.debug".
1116 1116
1117 1117 Progress reports via stderr are normally cleared before writing as
1118 1118 stdout and stderr go to the same terminal. This can be skipped with
1119 1119 the optional keyword argument "keepprogressbar". The progress bar
1120 1120 will continue to occupy a partial line on stderr in that case.
1121 1121 This functionality is intended when Mercurial acts as data source
1122 1122 in a pipe.
1123 1123
1124 1124 When labeling output for a specific command, a label of
1125 1125 "cmdname.type" is recommended. For example, status issues
1126 1126 a label of "status.modified" for modified files.
1127 1127 '''
1128 1128 dest = self._fout
1129 1129
1130 1130 # inlined _write() for speed
1131 1131 if self._buffers:
1132 1132 label = opts.get('label', b'')
1133 1133 if label and self._bufferapplylabels:
1134 1134 self._buffers[-1].extend(self.label(a, label) for a in args)
1135 1135 else:
1136 1136 self._buffers[-1].extend(args)
1137 1137 return
1138 1138
1139 1139 # inlined _writenobuf() for speed
1140 1140 if not opts.get('keepprogressbar', False):
1141 1141 self._progclear()
1142 1142 msg = b''.join(args)
1143 1143
1144 1144 # opencode timeblockedsection because this is a critical path
1145 1145 starttime = util.timer()
1146 1146 try:
1147 1147 if self._colormode == b'win32':
1148 1148 # windows color printing is its own can of crab, defer to
1149 1149 # the color module and that is it.
1150 1150 color.win32print(self, dest.write, msg, **opts)
1151 1151 else:
1152 1152 if self._colormode is not None:
1153 1153 label = opts.get('label', b'')
1154 1154 msg = self.label(msg, label)
1155 1155 dest.write(msg)
1156 1156 except IOError as err:
1157 1157 raise error.StdioError(err)
1158 1158 finally:
1159 1159 self._blockedtimes[b'stdio_blocked'] += (
1160 1160 util.timer() - starttime
1161 1161 ) * 1000
1162 1162
1163 1163 def write_err(self, *args, **opts):
1164 1164 self._write(self._ferr, *args, **opts)
1165 1165
1166 1166 def _write(self, dest, *args, **opts):
1167 1167 # update write() as well if you touch this code
1168 1168 if self._isbuffered(dest):
1169 1169 label = opts.get('label', b'')
1170 1170 if label and self._bufferapplylabels:
1171 1171 self._buffers[-1].extend(self.label(a, label) for a in args)
1172 1172 else:
1173 1173 self._buffers[-1].extend(args)
1174 1174 else:
1175 1175 self._writenobuf(dest, *args, **opts)
1176 1176
1177 1177 def _writenobuf(self, dest, *args, **opts):
1178 1178 # update write() as well if you touch this code
1179 1179 if not opts.get('keepprogressbar', False):
1180 1180 self._progclear()
1181 1181 msg = b''.join(args)
1182 1182
1183 1183 # opencode timeblockedsection because this is a critical path
1184 1184 starttime = util.timer()
1185 1185 try:
1186 1186 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1187 1187 self._fout.flush()
1188 1188 if getattr(dest, 'structured', False):
1189 1189 # channel for machine-readable output with metadata, where
1190 1190 # no extra colorization is necessary.
1191 1191 dest.write(msg, **opts)
1192 1192 elif self._colormode == b'win32':
1193 1193 # windows color printing is its own can of crab, defer to
1194 1194 # the color module and that is it.
1195 1195 color.win32print(self, dest.write, msg, **opts)
1196 1196 else:
1197 1197 if self._colormode is not None:
1198 1198 label = opts.get('label', b'')
1199 1199 msg = self.label(msg, label)
1200 1200 dest.write(msg)
1201 1201 # stderr may be buffered under win32 when redirected to files,
1202 1202 # including stdout.
1203 1203 if dest is self._ferr and not getattr(self._ferr, 'closed', False):
1204 1204 dest.flush()
1205 1205 except IOError as err:
1206 1206 if dest is self._ferr and err.errno in (
1207 1207 errno.EPIPE,
1208 1208 errno.EIO,
1209 1209 errno.EBADF,
1210 1210 ):
1211 1211 # no way to report the error, so ignore it
1212 1212 return
1213 1213 raise error.StdioError(err)
1214 1214 finally:
1215 1215 self._blockedtimes[b'stdio_blocked'] += (
1216 1216 util.timer() - starttime
1217 1217 ) * 1000
1218 1218
1219 1219 def _writemsg(self, dest, *args, **opts):
1220 1220 _writemsgwith(self._write, dest, *args, **opts)
1221 1221
1222 1222 def _writemsgnobuf(self, dest, *args, **opts):
1223 1223 _writemsgwith(self._writenobuf, dest, *args, **opts)
1224 1224
1225 1225 def flush(self):
1226 1226 # opencode timeblockedsection because this is a critical path
1227 1227 starttime = util.timer()
1228 1228 try:
1229 1229 try:
1230 1230 self._fout.flush()
1231 1231 except IOError as err:
1232 1232 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1233 1233 raise error.StdioError(err)
1234 1234 finally:
1235 1235 try:
1236 1236 self._ferr.flush()
1237 1237 except IOError as err:
1238 1238 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1239 1239 raise error.StdioError(err)
1240 1240 finally:
1241 1241 self._blockedtimes[b'stdio_blocked'] += (
1242 1242 util.timer() - starttime
1243 1243 ) * 1000
1244 1244
1245 1245 def _isatty(self, fh):
1246 1246 if self.configbool(b'ui', b'nontty'):
1247 1247 return False
1248 1248 return procutil.isatty(fh)
1249 1249
1250 1250 def protectfinout(self):
1251 1251 """Duplicate ui streams and redirect original if they are stdio
1252 1252
1253 1253 Returns (fin, fout) which point to the original ui fds, but may be
1254 1254 copy of them. The returned streams can be considered "owned" in that
1255 1255 print(), exec(), etc. never reach to them.
1256 1256 """
1257 1257 if self._finoutredirected:
1258 1258 # if already redirected, protectstdio() would just create another
1259 1259 # nullfd pair, which is equivalent to returning self._fin/_fout.
1260 1260 return self._fin, self._fout
1261 1261 fin, fout = procutil.protectstdio(self._fin, self._fout)
1262 1262 self._finoutredirected = (fin, fout) != (self._fin, self._fout)
1263 1263 return fin, fout
1264 1264
1265 1265 def restorefinout(self, fin, fout):
1266 1266 """Restore ui streams from possibly duplicated (fin, fout)"""
1267 1267 if (fin, fout) == (self._fin, self._fout):
1268 1268 return
1269 1269 procutil.restorestdio(self._fin, self._fout, fin, fout)
1270 1270 # protectfinout() won't create more than one duplicated streams,
1271 1271 # so we can just turn the redirection flag off.
1272 1272 self._finoutredirected = False
1273 1273
1274 1274 @contextlib.contextmanager
1275 1275 def protectedfinout(self):
1276 1276 """Run code block with protected standard streams"""
1277 1277 fin, fout = self.protectfinout()
1278 1278 try:
1279 1279 yield fin, fout
1280 1280 finally:
1281 1281 self.restorefinout(fin, fout)
1282 1282
1283 1283 def disablepager(self):
1284 1284 self._disablepager = True
1285 1285
1286 1286 def pager(self, command):
1287 1287 """Start a pager for subsequent command output.
1288 1288
1289 1289 Commands which produce a long stream of output should call
1290 1290 this function to activate the user's preferred pagination
1291 1291 mechanism (which may be no pager). Calling this function
1292 1292 precludes any future use of interactive functionality, such as
1293 1293 prompting the user or activating curses.
1294 1294
1295 1295 Args:
1296 1296 command: The full, non-aliased name of the command. That is, "log"
1297 1297 not "history, "summary" not "summ", etc.
1298 1298 """
1299 1299 if self._disablepager or self.pageractive:
1300 1300 # how pager should do is already determined
1301 1301 return
1302 1302
1303 1303 if not command.startswith(b'internal-always-') and (
1304 1304 # explicit --pager=on (= 'internal-always-' prefix) should
1305 1305 # take precedence over disabling factors below
1306 1306 command in self.configlist(b'pager', b'ignore')
1307 1307 or not self.configbool(b'ui', b'paginate')
1308 1308 or not self.configbool(b'pager', b'attend-' + command, True)
1309 1309 or encoding.environ.get(b'TERM') == b'dumb'
1310 1310 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1311 1311 # formatted() will need some adjustment.
1312 1312 or not self.formatted()
1313 1313 or self.plain()
1314 1314 or self._buffers
1315 1315 # TODO: expose debugger-enabled on the UI object
1316 1316 or b'--debugger' in pycompat.sysargv
1317 1317 ):
1318 1318 # We only want to paginate if the ui appears to be
1319 1319 # interactive, the user didn't say HGPLAIN or
1320 1320 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1321 1321 return
1322 1322
1323 1323 pagercmd = self.config(b'pager', b'pager', rcutil.fallbackpager)
1324 1324 if not pagercmd:
1325 1325 return
1326 1326
1327 1327 pagerenv = {}
1328 1328 for name, value in rcutil.defaultpagerenv().items():
1329 1329 if name not in encoding.environ:
1330 1330 pagerenv[name] = value
1331 1331
1332 1332 self.debug(
1333 1333 b'starting pager for command %s\n' % stringutil.pprint(command)
1334 1334 )
1335 1335 self.flush()
1336 1336
1337 1337 wasformatted = self.formatted()
1338 1338 if util.safehasattr(signal, b"SIGPIPE"):
1339 1339 signal.signal(signal.SIGPIPE, _catchterm)
1340 1340 if self._runpager(pagercmd, pagerenv):
1341 1341 self.pageractive = True
1342 1342 # Preserve the formatted-ness of the UI. This is important
1343 1343 # because we mess with stdout, which might confuse
1344 1344 # auto-detection of things being formatted.
1345 1345 self.setconfig(b'ui', b'formatted', wasformatted, b'pager')
1346 1346 self.setconfig(b'ui', b'interactive', False, b'pager')
1347 1347
1348 1348 # If pagermode differs from color.mode, reconfigure color now that
1349 1349 # pageractive is set.
1350 1350 cm = self._colormode
1351 1351 if cm != self.config(b'color', b'pagermode', cm):
1352 1352 color.setup(self)
1353 1353 else:
1354 1354 # If the pager can't be spawned in dispatch when --pager=on is
1355 1355 # given, don't try again when the command runs, to avoid a duplicate
1356 1356 # warning about a missing pager command.
1357 1357 self.disablepager()
1358 1358
1359 1359 def _runpager(self, command, env=None):
1360 1360 """Actually start the pager and set up file descriptors.
1361 1361
1362 1362 This is separate in part so that extensions (like chg) can
1363 1363 override how a pager is invoked.
1364 1364 """
1365 1365 if command == b'cat':
1366 1366 # Save ourselves some work.
1367 1367 return False
1368 1368 # If the command doesn't contain any of these characters, we
1369 1369 # assume it's a binary and exec it directly. This means for
1370 1370 # simple pager command configurations, we can degrade
1371 1371 # gracefully and tell the user about their broken pager.
1372 1372 shell = any(c in command for c in b"|&;<>()$`\\\"' \t\n*?[#~=%")
1373 1373
1374 1374 if pycompat.iswindows and not shell:
1375 1375 # Window's built-in `more` cannot be invoked with shell=False, but
1376 1376 # its `more.com` can. Hide this implementation detail from the
1377 1377 # user so we can also get sane bad PAGER behavior. MSYS has
1378 1378 # `more.exe`, so do a cmd.exe style resolution of the executable to
1379 1379 # determine which one to use.
1380 1380 fullcmd = procutil.findexe(command)
1381 1381 if not fullcmd:
1382 1382 self.warn(
1383 1383 _(b"missing pager command '%s', skipping pager\n") % command
1384 1384 )
1385 1385 return False
1386 1386
1387 1387 command = fullcmd
1388 1388
1389 1389 try:
1390 1390 pager = subprocess.Popen(
1391 1391 procutil.tonativestr(command),
1392 1392 shell=shell,
1393 1393 bufsize=-1,
1394 1394 close_fds=procutil.closefds,
1395 1395 stdin=subprocess.PIPE,
1396 1396 stdout=procutil.stdout,
1397 1397 stderr=procutil.stderr,
1398 1398 env=procutil.tonativeenv(procutil.shellenviron(env)),
1399 1399 )
1400 1400 except OSError as e:
1401 1401 if e.errno == errno.ENOENT and not shell:
1402 1402 self.warn(
1403 1403 _(b"missing pager command '%s', skipping pager\n") % command
1404 1404 )
1405 1405 return False
1406 1406 raise
1407 1407
1408 1408 # back up original file descriptors
1409 1409 stdoutfd = os.dup(procutil.stdout.fileno())
1410 1410 stderrfd = os.dup(procutil.stderr.fileno())
1411 1411
1412 1412 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1413 1413 if self._isatty(procutil.stderr):
1414 1414 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1415 1415
1416 1416 @self.atexit
1417 1417 def killpager():
1418 1418 if util.safehasattr(signal, b"SIGINT"):
1419 1419 signal.signal(signal.SIGINT, signal.SIG_IGN)
1420 1420 # restore original fds, closing pager.stdin copies in the process
1421 1421 os.dup2(stdoutfd, procutil.stdout.fileno())
1422 1422 os.dup2(stderrfd, procutil.stderr.fileno())
1423 1423 pager.stdin.close()
1424 1424 pager.wait()
1425 1425
1426 1426 return True
1427 1427
1428 1428 @property
1429 1429 def _exithandlers(self):
1430 1430 return _reqexithandlers
1431 1431
1432 1432 def atexit(self, func, *args, **kwargs):
1433 1433 '''register a function to run after dispatching a request
1434 1434
1435 1435 Handlers do not stay registered across request boundaries.'''
1436 1436 self._exithandlers.append((func, args, kwargs))
1437 1437 return func
1438 1438
1439 1439 def interface(self, feature):
1440 1440 """what interface to use for interactive console features?
1441 1441
1442 1442 The interface is controlled by the value of `ui.interface` but also by
1443 1443 the value of feature-specific configuration. For example:
1444 1444
1445 1445 ui.interface.histedit = text
1446 1446 ui.interface.chunkselector = curses
1447 1447
1448 1448 Here the features are "histedit" and "chunkselector".
1449 1449
1450 1450 The configuration above means that the default interfaces for commands
1451 1451 is curses, the interface for histedit is text and the interface for
1452 1452 selecting chunk is crecord (the best curses interface available).
1453 1453
1454 1454 Consider the following example:
1455 1455 ui.interface = curses
1456 1456 ui.interface.histedit = text
1457 1457
1458 1458 Then histedit will use the text interface and chunkselector will use
1459 1459 the default curses interface (crecord at the moment).
1460 1460 """
1461 1461 alldefaults = frozenset([b"text", b"curses"])
1462 1462
1463 1463 featureinterfaces = {
1464 1464 b"chunkselector": [b"text", b"curses",],
1465 1465 b"histedit": [b"text", b"curses",],
1466 1466 }
1467 1467
1468 1468 # Feature-specific interface
1469 1469 if feature not in featureinterfaces.keys():
1470 1470 # Programming error, not user error
1471 1471 raise ValueError(b"Unknown feature requested %s" % feature)
1472 1472
1473 1473 availableinterfaces = frozenset(featureinterfaces[feature])
1474 1474 if alldefaults > availableinterfaces:
1475 1475 # Programming error, not user error. We need a use case to
1476 1476 # define the right thing to do here.
1477 1477 raise ValueError(
1478 1478 b"Feature %s does not handle all default interfaces" % feature
1479 1479 )
1480 1480
1481 1481 if self.plain() or encoding.environ.get(b'TERM') == b'dumb':
1482 1482 return b"text"
1483 1483
1484 1484 # Default interface for all the features
1485 1485 defaultinterface = b"text"
1486 1486 i = self.config(b"ui", b"interface")
1487 1487 if i in alldefaults:
1488 1488 defaultinterface = i
1489 1489
1490 1490 choseninterface = defaultinterface
1491 1491 f = self.config(b"ui", b"interface.%s" % feature)
1492 1492 if f in availableinterfaces:
1493 1493 choseninterface = f
1494 1494
1495 1495 if i is not None and defaultinterface != i:
1496 1496 if f is not None:
1497 1497 self.warn(_(b"invalid value for ui.interface: %s\n") % (i,))
1498 1498 else:
1499 1499 self.warn(
1500 1500 _(b"invalid value for ui.interface: %s (using %s)\n")
1501 1501 % (i, choseninterface)
1502 1502 )
1503 1503 if f is not None and choseninterface != f:
1504 1504 self.warn(
1505 1505 _(b"invalid value for ui.interface.%s: %s (using %s)\n")
1506 1506 % (feature, f, choseninterface)
1507 1507 )
1508 1508
1509 1509 return choseninterface
1510 1510
1511 1511 def interactive(self):
1512 1512 '''is interactive input allowed?
1513 1513
1514 1514 An interactive session is a session where input can be reasonably read
1515 1515 from `sys.stdin'. If this function returns false, any attempt to read
1516 1516 from stdin should fail with an error, unless a sensible default has been
1517 1517 specified.
1518 1518
1519 1519 Interactiveness is triggered by the value of the `ui.interactive'
1520 1520 configuration variable or - if it is unset - when `sys.stdin' points
1521 1521 to a terminal device.
1522 1522
1523 1523 This function refers to input only; for output, see `ui.formatted()'.
1524 1524 '''
1525 1525 i = self.configbool(b"ui", b"interactive")
1526 1526 if i is None:
1527 1527 # some environments replace stdin without implementing isatty
1528 1528 # usually those are non-interactive
1529 1529 return self._isatty(self._fin)
1530 1530
1531 1531 return i
1532 1532
1533 1533 def termwidth(self):
1534 1534 '''how wide is the terminal in columns?
1535 1535 '''
1536 1536 if b'COLUMNS' in encoding.environ:
1537 1537 try:
1538 1538 return int(encoding.environ[b'COLUMNS'])
1539 1539 except ValueError:
1540 1540 pass
1541 1541 return scmutil.termsize(self)[0]
1542 1542
1543 1543 def formatted(self):
1544 1544 '''should formatted output be used?
1545 1545
1546 1546 It is often desirable to format the output to suite the output medium.
1547 1547 Examples of this are truncating long lines or colorizing messages.
1548 1548 However, this is not often not desirable when piping output into other
1549 1549 utilities, e.g. `grep'.
1550 1550
1551 1551 Formatted output is triggered by the value of the `ui.formatted'
1552 1552 configuration variable or - if it is unset - when `sys.stdout' points
1553 1553 to a terminal device. Please note that `ui.formatted' should be
1554 1554 considered an implementation detail; it is not intended for use outside
1555 1555 Mercurial or its extensions.
1556 1556
1557 1557 This function refers to output only; for input, see `ui.interactive()'.
1558 1558 This function always returns false when in plain mode, see `ui.plain()'.
1559 1559 '''
1560 1560 if self.plain():
1561 1561 return False
1562 1562
1563 1563 i = self.configbool(b"ui", b"formatted")
1564 1564 if i is None:
1565 1565 # some environments replace stdout without implementing isatty
1566 1566 # usually those are non-interactive
1567 1567 return self._isatty(self._fout)
1568 1568
1569 1569 return i
1570 1570
1571 1571 def _readline(self, prompt=b' ', promptopts=None):
1572 1572 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1573 1573 # because they have to be text streams with *no buffering*. Instead,
1574 1574 # we use rawinput() only if call_readline() will be invoked by
1575 1575 # PyOS_Readline(), so no I/O will be made at Python layer.
1576 1576 usereadline = (
1577 1577 self._isatty(self._fin)
1578 1578 and self._isatty(self._fout)
1579 1579 and procutil.isstdin(self._fin)
1580 1580 and procutil.isstdout(self._fout)
1581 1581 )
1582 1582 if usereadline:
1583 1583 try:
1584 1584 # magically add command line editing support, where
1585 1585 # available
1586 1586 import readline
1587 1587
1588 1588 # force demandimport to really load the module
1589 1589 readline.read_history_file
1590 1590 # windows sometimes raises something other than ImportError
1591 1591 except Exception:
1592 1592 usereadline = False
1593 1593
1594 1594 if self._colormode == b'win32' or not usereadline:
1595 1595 if not promptopts:
1596 1596 promptopts = {}
1597 1597 self._writemsgnobuf(
1598 1598 self._fmsgout, prompt, type=b'prompt', **promptopts
1599 1599 )
1600 1600 self.flush()
1601 1601 prompt = b' '
1602 1602 else:
1603 1603 prompt = self.label(prompt, b'ui.prompt') + b' '
1604 1604
1605 1605 # prompt ' ' must exist; otherwise readline may delete entire line
1606 1606 # - http://bugs.python.org/issue12833
1607 1607 with self.timeblockedsection(b'stdio'):
1608 1608 if usereadline:
1609 1609 self.flush()
1610 1610 prompt = encoding.strfromlocal(prompt)
1611 1611 line = encoding.strtolocal(pycompat.rawinput(prompt))
1612 1612 # When stdin is in binary mode on Windows, it can cause
1613 1613 # raw_input() to emit an extra trailing carriage return
1614 1614 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1615 1615 line = line[:-1]
1616 1616 else:
1617 1617 self._fout.write(pycompat.bytestr(prompt))
1618 1618 self._fout.flush()
1619 1619 line = self._fin.readline()
1620 1620 if not line:
1621 1621 raise EOFError
1622 1622 line = line.rstrip(pycompat.oslinesep)
1623 1623
1624 1624 return line
1625 1625
1626 1626 def prompt(self, msg, default=b"y"):
1627 1627 """Prompt user with msg, read response.
1628 1628 If ui is not interactive, the default is returned.
1629 1629 """
1630 1630 return self._prompt(msg, default=default)
1631 1631
1632 1632 def _prompt(self, msg, **opts):
1633 1633 default = opts['default']
1634 1634 if not self.interactive():
1635 1635 self._writemsg(self._fmsgout, msg, b' ', type=b'prompt', **opts)
1636 1636 self._writemsg(
1637 1637 self._fmsgout, default or b'', b"\n", type=b'promptecho'
1638 1638 )
1639 1639 return default
1640 1640 try:
1641 1641 r = self._readline(prompt=msg, promptopts=opts)
1642 1642 if not r:
1643 1643 r = default
1644 1644 if self.configbool(b'ui', b'promptecho'):
1645 1645 self._writemsg(self._fmsgout, r, b"\n", type=b'promptecho')
1646 1646 return r
1647 1647 except EOFError:
1648 1648 raise error.ResponseExpected()
1649 1649
1650 1650 @staticmethod
1651 1651 def extractchoices(prompt):
1652 1652 """Extract prompt message and list of choices from specified prompt.
1653 1653
1654 1654 This returns tuple "(message, choices)", and "choices" is the
1655 1655 list of tuple "(response character, text without &)".
1656 1656
1657 1657 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1658 1658 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1659 1659 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1660 1660 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1661 1661 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1662 1662 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1663 1663 """
1664 1664
1665 1665 # Sadly, the prompt string may have been built with a filename
1666 1666 # containing "$$" so let's try to find the first valid-looking
1667 1667 # prompt to start parsing. Sadly, we also can't rely on
1668 1668 # choices containing spaces, ASCII, or basically anything
1669 1669 # except an ampersand followed by a character.
1670 1670 m = re.match(br'(?s)(.+?)\$\$([^$]*&[^ $].*)', prompt)
1671 1671 msg = m.group(1)
1672 1672 choices = [p.strip(b' ') for p in m.group(2).split(b'$$')]
1673 1673
1674 1674 def choicetuple(s):
1675 1675 ampidx = s.index(b'&')
1676 1676 return s[ampidx + 1 : ampidx + 2].lower(), s.replace(b'&', b'', 1)
1677 1677
1678 1678 return (msg, [choicetuple(s) for s in choices])
1679 1679
1680 1680 def promptchoice(self, prompt, default=0):
1681 1681 """Prompt user with a message, read response, and ensure it matches
1682 1682 one of the provided choices. The prompt is formatted as follows:
1683 1683
1684 1684 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1685 1685
1686 1686 The index of the choice is returned. Responses are case
1687 1687 insensitive. If ui is not interactive, the default is
1688 1688 returned.
1689 1689 """
1690 1690
1691 1691 msg, choices = self.extractchoices(prompt)
1692 1692 resps = [r for r, t in choices]
1693 1693 while True:
1694 1694 r = self._prompt(msg, default=resps[default], choices=choices)
1695 1695 if r.lower() in resps:
1696 1696 return resps.index(r.lower())
1697 1697 # TODO: shouldn't it be a warning?
1698 1698 self._writemsg(self._fmsgout, _(b"unrecognized response\n"))
1699 1699
1700 1700 def getpass(self, prompt=None, default=None):
1701 1701 if not self.interactive():
1702 1702 return default
1703 1703 try:
1704 1704 self._writemsg(
1705 1705 self._fmsgerr,
1706 1706 prompt or _(b'password: '),
1707 1707 type=b'prompt',
1708 1708 password=True,
1709 1709 )
1710 1710 # disable getpass() only if explicitly specified. it's still valid
1711 1711 # to interact with tty even if fin is not a tty.
1712 1712 with self.timeblockedsection(b'stdio'):
1713 1713 if self.configbool(b'ui', b'nontty'):
1714 1714 l = self._fin.readline()
1715 1715 if not l:
1716 1716 raise EOFError
1717 1717 return l.rstrip(b'\n')
1718 1718 else:
1719 1719 return getpass.getpass('')
1720 1720 except EOFError:
1721 1721 raise error.ResponseExpected()
1722 1722
1723 1723 def status(self, *msg, **opts):
1724 1724 '''write status message to output (if ui.quiet is False)
1725 1725
1726 1726 This adds an output label of "ui.status".
1727 1727 '''
1728 1728 if not self.quiet:
1729 1729 self._writemsg(self._fmsgout, type=b'status', *msg, **opts)
1730 1730
1731 1731 def warn(self, *msg, **opts):
1732 1732 '''write warning message to output (stderr)
1733 1733
1734 1734 This adds an output label of "ui.warning".
1735 1735 '''
1736 1736 self._writemsg(self._fmsgerr, type=b'warning', *msg, **opts)
1737 1737
1738 1738 def error(self, *msg, **opts):
1739 1739 '''write error message to output (stderr)
1740 1740
1741 1741 This adds an output label of "ui.error".
1742 1742 '''
1743 1743 self._writemsg(self._fmsgerr, type=b'error', *msg, **opts)
1744 1744
1745 1745 def note(self, *msg, **opts):
1746 1746 '''write note to output (if ui.verbose is True)
1747 1747
1748 1748 This adds an output label of "ui.note".
1749 1749 '''
1750 1750 if self.verbose:
1751 1751 self._writemsg(self._fmsgout, type=b'note', *msg, **opts)
1752 1752
1753 1753 def debug(self, *msg, **opts):
1754 1754 '''write debug message to output (if ui.debugflag is True)
1755 1755
1756 1756 This adds an output label of "ui.debug".
1757 1757 '''
1758 1758 if self.debugflag:
1759 1759 self._writemsg(self._fmsgout, type=b'debug', *msg, **opts)
1760 1760 self.log(b'debug', b'%s', b''.join(msg))
1761 1761
1762 1762 # Aliases to defeat check-code.
1763 1763 statusnoi18n = status
1764 1764 notenoi18n = note
1765 1765 warnnoi18n = warn
1766 1766 writenoi18n = write
1767 1767
1768 1768 def edit(
1769 1769 self,
1770 1770 text,
1771 1771 user,
1772 1772 extra=None,
1773 1773 editform=None,
1774 1774 pending=None,
1775 1775 repopath=None,
1776 1776 action=None,
1777 1777 ):
1778 1778 if action is None:
1779 1779 self.develwarn(
1780 1780 b'action is None but will soon be a required '
1781 1781 b'parameter to ui.edit()'
1782 1782 )
1783 1783 extra_defaults = {
1784 1784 b'prefix': b'editor',
1785 1785 b'suffix': b'.txt',
1786 1786 }
1787 1787 if extra is not None:
1788 1788 if extra.get(b'suffix') is not None:
1789 1789 self.develwarn(
1790 1790 b'extra.suffix is not None but will soon be '
1791 1791 b'ignored by ui.edit()'
1792 1792 )
1793 1793 extra_defaults.update(extra)
1794 1794 extra = extra_defaults
1795 1795
1796 1796 if action == b'diff':
1797 1797 suffix = b'.diff'
1798 1798 elif action:
1799 1799 suffix = b'.%s.hg.txt' % action
1800 1800 else:
1801 1801 suffix = extra[b'suffix']
1802 1802
1803 1803 rdir = None
1804 1804 if self.configbool(b'experimental', b'editortmpinhg'):
1805 1805 rdir = repopath
1806 1806 (fd, name) = pycompat.mkstemp(
1807 1807 prefix=b'hg-' + extra[b'prefix'] + b'-', suffix=suffix, dir=rdir
1808 1808 )
1809 1809 try:
1810 1810 with os.fdopen(fd, 'wb') as f:
1811 1811 f.write(util.tonativeeol(text))
1812 1812
1813 1813 environ = {b'HGUSER': user}
1814 1814 if b'transplant_source' in extra:
1815 1815 environ.update(
1816 1816 {b'HGREVISION': hex(extra[b'transplant_source'])}
1817 1817 )
1818 1818 for label in (b'intermediate-source', b'source', b'rebase_source'):
1819 1819 if label in extra:
1820 1820 environ.update({b'HGREVISION': extra[label]})
1821 1821 break
1822 1822 if editform:
1823 1823 environ.update({b'HGEDITFORM': editform})
1824 1824 if pending:
1825 1825 environ.update({b'HG_PENDING': pending})
1826 1826
1827 1827 editor = self.geteditor()
1828 1828
1829 1829 self.system(
1830 1830 b"%s \"%s\"" % (editor, name),
1831 1831 environ=environ,
1832 1832 onerr=error.Abort,
1833 1833 errprefix=_(b"edit failed"),
1834 1834 blockedtag=b'editor',
1835 1835 )
1836 1836
1837 1837 with open(name, 'rb') as f:
1838 1838 t = util.fromnativeeol(f.read())
1839 1839 finally:
1840 1840 os.unlink(name)
1841 1841
1842 1842 return t
1843 1843
1844 1844 def system(
1845 1845 self,
1846 1846 cmd,
1847 1847 environ=None,
1848 1848 cwd=None,
1849 1849 onerr=None,
1850 1850 errprefix=None,
1851 1851 blockedtag=None,
1852 1852 ):
1853 1853 '''execute shell command with appropriate output stream. command
1854 1854 output will be redirected if fout is not stdout.
1855 1855
1856 1856 if command fails and onerr is None, return status, else raise onerr
1857 1857 object as exception.
1858 1858 '''
1859 1859 if blockedtag is None:
1860 1860 # Long cmds tend to be because of an absolute path on cmd. Keep
1861 1861 # the tail end instead
1862 1862 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1863 1863 blockedtag = b'unknown_system_' + cmdsuffix
1864 1864 out = self._fout
1865 1865 if any(s[1] for s in self._bufferstates):
1866 1866 out = self
1867 1867 with self.timeblockedsection(blockedtag):
1868 1868 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1869 1869 if rc and onerr:
1870 1870 errmsg = b'%s %s' % (
1871 1871 procutil.shellsplit(cmd)[0],
1872 1872 procutil.explainexit(rc),
1873 1873 )
1874 1874 if errprefix:
1875 1875 errmsg = b'%s: %s' % (errprefix, errmsg)
1876 1876 raise onerr(errmsg)
1877 1877 return rc
1878 1878
1879 1879 def _runsystem(self, cmd, environ, cwd, out):
1880 1880 """actually execute the given shell command (can be overridden by
1881 1881 extensions like chg)"""
1882 1882 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1883 1883
1884 1884 def traceback(self, exc=None, force=False):
1885 1885 '''print exception traceback if traceback printing enabled or forced.
1886 1886 only to call in exception handler. returns true if traceback
1887 1887 printed.'''
1888 1888 if self.tracebackflag or force:
1889 1889 if exc is None:
1890 1890 exc = sys.exc_info()
1891 1891 cause = getattr(exc[1], 'cause', None)
1892 1892
1893 1893 if cause is not None:
1894 1894 causetb = traceback.format_tb(cause[2])
1895 1895 exctb = traceback.format_tb(exc[2])
1896 1896 exconly = traceback.format_exception_only(cause[0], cause[1])
1897 1897
1898 1898 # exclude frame where 'exc' was chained and rethrown from exctb
1899 1899 self.write_err(
1900 1900 b'Traceback (most recent call last):\n',
1901 1901 encoding.strtolocal(''.join(exctb[:-1])),
1902 1902 encoding.strtolocal(''.join(causetb)),
1903 1903 encoding.strtolocal(''.join(exconly)),
1904 1904 )
1905 1905 else:
1906 1906 output = traceback.format_exception(exc[0], exc[1], exc[2])
1907 1907 self.write_err(encoding.strtolocal(''.join(output)))
1908 1908 return self.tracebackflag or force
1909 1909
1910 1910 def geteditor(self):
1911 1911 '''return editor to use'''
1912 1912 if pycompat.sysplatform == b'plan9':
1913 1913 # vi is the MIPS instruction simulator on Plan 9. We
1914 1914 # instead default to E to plumb commit messages to
1915 1915 # avoid confusion.
1916 1916 editor = b'E'
1917 elif pycompat.isdarwin:
1918 # vi on darwin is POSIX compatible to a fault, and that includes
1919 # exiting non-zero if you make any mistake when running an ex
1920 # command. Proof: `vi -c ':unknown' -c ':qa'; echo $?` produces 1,
1921 # while s/vi/vim/ doesn't.
1922 editor = b'vim'
1917 1923 else:
1918 1924 editor = b'vi'
1919 1925 return encoding.environ.get(b"HGEDITOR") or self.config(
1920 1926 b"ui", b"editor", editor
1921 1927 )
1922 1928
1923 1929 @util.propertycache
1924 1930 def _progbar(self):
1925 1931 """setup the progbar singleton to the ui object"""
1926 1932 if (
1927 1933 self.quiet
1928 1934 or self.debugflag
1929 1935 or self.configbool(b'progress', b'disable')
1930 1936 or not progress.shouldprint(self)
1931 1937 ):
1932 1938 return None
1933 1939 return getprogbar(self)
1934 1940
1935 1941 def _progclear(self):
1936 1942 """clear progress bar output if any. use it before any output"""
1937 1943 if not haveprogbar(): # nothing loaded yet
1938 1944 return
1939 1945 if self._progbar is not None and self._progbar.printed:
1940 1946 self._progbar.clear()
1941 1947
1942 1948 def makeprogress(self, topic, unit=b"", total=None):
1943 1949 """Create a progress helper for the specified topic"""
1944 1950 if getattr(self._fmsgerr, 'structured', False):
1945 1951 # channel for machine-readable output with metadata, just send
1946 1952 # raw information
1947 1953 # TODO: consider porting some useful information (e.g. estimated
1948 1954 # time) from progbar. we might want to support update delay to
1949 1955 # reduce the cost of transferring progress messages.
1950 1956 def updatebar(topic, pos, item, unit, total):
1951 1957 self._fmsgerr.write(
1952 1958 None,
1953 1959 type=b'progress',
1954 1960 topic=topic,
1955 1961 pos=pos,
1956 1962 item=item,
1957 1963 unit=unit,
1958 1964 total=total,
1959 1965 )
1960 1966
1961 1967 elif self._progbar is not None:
1962 1968 updatebar = self._progbar.progress
1963 1969 else:
1964 1970
1965 1971 def updatebar(topic, pos, item, unit, total):
1966 1972 pass
1967 1973
1968 1974 return scmutil.progress(self, updatebar, topic, unit, total)
1969 1975
1970 1976 def getlogger(self, name):
1971 1977 """Returns a logger of the given name; or None if not registered"""
1972 1978 return self._loggers.get(name)
1973 1979
1974 1980 def setlogger(self, name, logger):
1975 1981 """Install logger which can be identified later by the given name
1976 1982
1977 1983 More than one loggers can be registered. Use extension or module
1978 1984 name to uniquely identify the logger instance.
1979 1985 """
1980 1986 self._loggers[name] = logger
1981 1987
1982 1988 def log(self, event, msgfmt, *msgargs, **opts):
1983 1989 '''hook for logging facility extensions
1984 1990
1985 1991 event should be a readily-identifiable subsystem, which will
1986 1992 allow filtering.
1987 1993
1988 1994 msgfmt should be a newline-terminated format string to log, and
1989 1995 *msgargs are %-formatted into it.
1990 1996
1991 1997 **opts currently has no defined meanings.
1992 1998 '''
1993 1999 if not self._loggers:
1994 2000 return
1995 2001 activeloggers = [
1996 2002 l for l in pycompat.itervalues(self._loggers) if l.tracked(event)
1997 2003 ]
1998 2004 if not activeloggers:
1999 2005 return
2000 2006 msg = msgfmt % msgargs
2001 2007 opts = pycompat.byteskwargs(opts)
2002 2008 # guard against recursion from e.g. ui.debug()
2003 2009 registeredloggers = self._loggers
2004 2010 self._loggers = {}
2005 2011 try:
2006 2012 for logger in activeloggers:
2007 2013 logger.log(self, event, msg, opts)
2008 2014 finally:
2009 2015 self._loggers = registeredloggers
2010 2016
2011 2017 def label(self, msg, label):
2012 2018 '''style msg based on supplied label
2013 2019
2014 2020 If some color mode is enabled, this will add the necessary control
2015 2021 characters to apply such color. In addition, 'debug' color mode adds
2016 2022 markup showing which label affects a piece of text.
2017 2023
2018 2024 ui.write(s, 'label') is equivalent to
2019 2025 ui.write(ui.label(s, 'label')).
2020 2026 '''
2021 2027 if self._colormode is not None:
2022 2028 return color.colorlabel(self, msg, label)
2023 2029 return msg
2024 2030
2025 2031 def develwarn(self, msg, stacklevel=1, config=None):
2026 2032 """issue a developer warning message
2027 2033
2028 2034 Use 'stacklevel' to report the offender some layers further up in the
2029 2035 stack.
2030 2036 """
2031 2037 if not self.configbool(b'devel', b'all-warnings'):
2032 2038 if config is None or not self.configbool(b'devel', config):
2033 2039 return
2034 2040 msg = b'devel-warn: ' + msg
2035 2041 stacklevel += 1 # get in develwarn
2036 2042 if self.tracebackflag:
2037 2043 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
2038 2044 self.log(
2039 2045 b'develwarn',
2040 2046 b'%s at:\n%s'
2041 2047 % (msg, b''.join(util.getstackframes(stacklevel))),
2042 2048 )
2043 2049 else:
2044 2050 curframe = inspect.currentframe()
2045 2051 calframe = inspect.getouterframes(curframe, 2)
2046 2052 fname, lineno, fmsg = calframe[stacklevel][1:4]
2047 2053 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
2048 2054 self.write_err(b'%s at: %s:%d (%s)\n' % (msg, fname, lineno, fmsg))
2049 2055 self.log(
2050 2056 b'develwarn', b'%s at: %s:%d (%s)\n', msg, fname, lineno, fmsg
2051 2057 )
2052 2058
2053 2059 # avoid cycles
2054 2060 del curframe
2055 2061 del calframe
2056 2062
2057 2063 def deprecwarn(self, msg, version, stacklevel=2):
2058 2064 """issue a deprecation warning
2059 2065
2060 2066 - msg: message explaining what is deprecated and how to upgrade,
2061 2067 - version: last version where the API will be supported,
2062 2068 """
2063 2069 if not (
2064 2070 self.configbool(b'devel', b'all-warnings')
2065 2071 or self.configbool(b'devel', b'deprec-warn')
2066 2072 ):
2067 2073 return
2068 2074 msg += (
2069 2075 b"\n(compatibility will be dropped after Mercurial-%s,"
2070 2076 b" update your code.)"
2071 2077 ) % version
2072 2078 self.develwarn(msg, stacklevel=stacklevel, config=b'deprec-warn')
2073 2079
2074 2080 def exportableenviron(self):
2075 2081 """The environment variables that are safe to export, e.g. through
2076 2082 hgweb.
2077 2083 """
2078 2084 return self._exportableenviron
2079 2085
2080 2086 @contextlib.contextmanager
2081 2087 def configoverride(self, overrides, source=b""):
2082 2088 """Context manager for temporary config overrides
2083 2089 `overrides` must be a dict of the following structure:
2084 2090 {(section, name) : value}"""
2085 2091 backups = {}
2086 2092 try:
2087 2093 for (section, name), value in overrides.items():
2088 2094 backups[(section, name)] = self.backupconfig(section, name)
2089 2095 self.setconfig(section, name, value, source)
2090 2096 yield
2091 2097 finally:
2092 2098 for __, backup in backups.items():
2093 2099 self.restoreconfig(backup)
2094 2100 # just restoring ui.quiet config to the previous value is not enough
2095 2101 # as it does not update ui.quiet class member
2096 2102 if (b'ui', b'quiet') in overrides:
2097 2103 self.fixconfig(section=b'ui')
2098 2104
2099 2105
2100 2106 class paths(dict):
2101 2107 """Represents a collection of paths and their configs.
2102 2108
2103 2109 Data is initially derived from ui instances and the config files they have
2104 2110 loaded.
2105 2111 """
2106 2112
2107 2113 def __init__(self, ui):
2108 2114 dict.__init__(self)
2109 2115
2110 2116 for name, loc in ui.configitems(b'paths', ignoresub=True):
2111 2117 # No location is the same as not existing.
2112 2118 if not loc:
2113 2119 continue
2114 2120 loc, sub = ui.configsuboptions(b'paths', name)
2115 2121 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
2116 2122
2117 2123 def getpath(self, name, default=None):
2118 2124 """Return a ``path`` from a string, falling back to default.
2119 2125
2120 2126 ``name`` can be a named path or locations. Locations are filesystem
2121 2127 paths or URIs.
2122 2128
2123 2129 Returns None if ``name`` is not a registered path, a URI, or a local
2124 2130 path to a repo.
2125 2131 """
2126 2132 # Only fall back to default if no path was requested.
2127 2133 if name is None:
2128 2134 if not default:
2129 2135 default = ()
2130 2136 elif not isinstance(default, (tuple, list)):
2131 2137 default = (default,)
2132 2138 for k in default:
2133 2139 try:
2134 2140 return self[k]
2135 2141 except KeyError:
2136 2142 continue
2137 2143 return None
2138 2144
2139 2145 # Most likely empty string.
2140 2146 # This may need to raise in the future.
2141 2147 if not name:
2142 2148 return None
2143 2149
2144 2150 try:
2145 2151 return self[name]
2146 2152 except KeyError:
2147 2153 # Try to resolve as a local path or URI.
2148 2154 try:
2149 2155 # We don't pass sub-options in, so no need to pass ui instance.
2150 2156 return path(None, None, rawloc=name)
2151 2157 except ValueError:
2152 2158 raise error.RepoError(_(b'repository %s does not exist') % name)
2153 2159
2154 2160
2155 2161 _pathsuboptions = {}
2156 2162
2157 2163
2158 2164 def pathsuboption(option, attr):
2159 2165 """Decorator used to declare a path sub-option.
2160 2166
2161 2167 Arguments are the sub-option name and the attribute it should set on
2162 2168 ``path`` instances.
2163 2169
2164 2170 The decorated function will receive as arguments a ``ui`` instance,
2165 2171 ``path`` instance, and the string value of this option from the config.
2166 2172 The function should return the value that will be set on the ``path``
2167 2173 instance.
2168 2174
2169 2175 This decorator can be used to perform additional verification of
2170 2176 sub-options and to change the type of sub-options.
2171 2177 """
2172 2178
2173 2179 def register(func):
2174 2180 _pathsuboptions[option] = (attr, func)
2175 2181 return func
2176 2182
2177 2183 return register
2178 2184
2179 2185
2180 2186 @pathsuboption(b'pushurl', b'pushloc')
2181 2187 def pushurlpathoption(ui, path, value):
2182 2188 u = util.url(value)
2183 2189 # Actually require a URL.
2184 2190 if not u.scheme:
2185 2191 ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
2186 2192 return None
2187 2193
2188 2194 # Don't support the #foo syntax in the push URL to declare branch to
2189 2195 # push.
2190 2196 if u.fragment:
2191 2197 ui.warn(
2192 2198 _(
2193 2199 b'("#fragment" in paths.%s:pushurl not supported; '
2194 2200 b'ignoring)\n'
2195 2201 )
2196 2202 % path.name
2197 2203 )
2198 2204 u.fragment = None
2199 2205
2200 2206 return bytes(u)
2201 2207
2202 2208
2203 2209 @pathsuboption(b'pushrev', b'pushrev')
2204 2210 def pushrevpathoption(ui, path, value):
2205 2211 return value
2206 2212
2207 2213
2208 2214 class path(object):
2209 2215 """Represents an individual path and its configuration."""
2210 2216
2211 2217 def __init__(self, ui, name, rawloc=None, suboptions=None):
2212 2218 """Construct a path from its config options.
2213 2219
2214 2220 ``ui`` is the ``ui`` instance the path is coming from.
2215 2221 ``name`` is the symbolic name of the path.
2216 2222 ``rawloc`` is the raw location, as defined in the config.
2217 2223 ``pushloc`` is the raw locations pushes should be made to.
2218 2224
2219 2225 If ``name`` is not defined, we require that the location be a) a local
2220 2226 filesystem path with a .hg directory or b) a URL. If not,
2221 2227 ``ValueError`` is raised.
2222 2228 """
2223 2229 if not rawloc:
2224 2230 raise ValueError(b'rawloc must be defined')
2225 2231
2226 2232 # Locations may define branches via syntax <base>#<branch>.
2227 2233 u = util.url(rawloc)
2228 2234 branch = None
2229 2235 if u.fragment:
2230 2236 branch = u.fragment
2231 2237 u.fragment = None
2232 2238
2233 2239 self.url = u
2234 2240 self.branch = branch
2235 2241
2236 2242 self.name = name
2237 2243 self.rawloc = rawloc
2238 2244 self.loc = b'%s' % u
2239 2245
2240 2246 # When given a raw location but not a symbolic name, validate the
2241 2247 # location is valid.
2242 2248 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
2243 2249 raise ValueError(
2244 2250 b'location is not a URL or path to a local '
2245 2251 b'repo: %s' % rawloc
2246 2252 )
2247 2253
2248 2254 suboptions = suboptions or {}
2249 2255
2250 2256 # Now process the sub-options. If a sub-option is registered, its
2251 2257 # attribute will always be present. The value will be None if there
2252 2258 # was no valid sub-option.
2253 2259 for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
2254 2260 if suboption not in suboptions:
2255 2261 setattr(self, attr, None)
2256 2262 continue
2257 2263
2258 2264 value = func(ui, self, suboptions[suboption])
2259 2265 setattr(self, attr, value)
2260 2266
2261 2267 def _isvalidlocalpath(self, path):
2262 2268 """Returns True if the given path is a potentially valid repository.
2263 2269 This is its own function so that extensions can change the definition of
2264 2270 'valid' in this case (like when pulling from a git repo into a hg
2265 2271 one)."""
2266 2272 try:
2267 2273 return os.path.isdir(os.path.join(path, b'.hg'))
2268 2274 # Python 2 may return TypeError. Python 3, ValueError.
2269 2275 except (TypeError, ValueError):
2270 2276 return False
2271 2277
2272 2278 @property
2273 2279 def suboptions(self):
2274 2280 """Return sub-options and their values for this path.
2275 2281
2276 2282 This is intended to be used for presentation purposes.
2277 2283 """
2278 2284 d = {}
2279 2285 for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
2280 2286 value = getattr(self, attr)
2281 2287 if value is not None:
2282 2288 d[subopt] = value
2283 2289 return d
2284 2290
2285 2291
2286 2292 # we instantiate one globally shared progress bar to avoid
2287 2293 # competing progress bars when multiple UI objects get created
2288 2294 _progresssingleton = None
2289 2295
2290 2296
2291 2297 def getprogbar(ui):
2292 2298 global _progresssingleton
2293 2299 if _progresssingleton is None:
2294 2300 # passing 'ui' object to the singleton is fishy,
2295 2301 # this is how the extension used to work but feel free to rework it.
2296 2302 _progresssingleton = progress.progbar(ui)
2297 2303 return _progresssingleton
2298 2304
2299 2305
2300 2306 def haveprogbar():
2301 2307 return _progresssingleton is not None
2302 2308
2303 2309
2304 2310 def _selectmsgdests(ui):
2305 2311 name = ui.config(b'ui', b'message-output')
2306 2312 if name == b'channel':
2307 2313 if ui.fmsg:
2308 2314 return ui.fmsg, ui.fmsg
2309 2315 else:
2310 2316 # fall back to ferr if channel isn't ready so that status/error
2311 2317 # messages can be printed
2312 2318 return ui.ferr, ui.ferr
2313 2319 if name == b'stdio':
2314 2320 return ui.fout, ui.ferr
2315 2321 if name == b'stderr':
2316 2322 return ui.ferr, ui.ferr
2317 2323 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2318 2324
2319 2325
2320 2326 def _writemsgwith(write, dest, *args, **opts):
2321 2327 """Write ui message with the given ui._write*() function
2322 2328
2323 2329 The specified message type is translated to 'ui.<type>' label if the dest
2324 2330 isn't a structured channel, so that the message will be colorized.
2325 2331 """
2326 2332 # TODO: maybe change 'type' to a mandatory option
2327 2333 if 'type' in opts and not getattr(dest, 'structured', False):
2328 2334 opts['label'] = opts.get('label', b'') + b' ui.%s' % opts.pop('type')
2329 2335 write(dest, *args, **opts)
@@ -1,1753 +1,1753 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 return ''
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 893 'error running gendoc.py: %s' % '\n'.join([out, err])
894 894 )
895 895
896 896 with open(txt, 'wb') as fh:
897 897 fh.write(out)
898 898
899 899 def gengendoc(root):
900 900 gendoc = 'doc/%s.gendoc.txt' % root
901 901
902 902 log.info('generating %s' % gendoc)
903 903 res, out, err = runcmd(
904 904 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
905 905 os.environ,
906 906 cwd='doc',
907 907 )
908 908 if res:
909 909 raise SystemExit(
910 910 'error running gendoc: %s' % '\n'.join([out, err])
911 911 )
912 912
913 913 with open(gendoc, 'wb') as fh:
914 914 fh.write(out)
915 915
916 916 def genman(root):
917 917 log.info('generating doc/%s' % root)
918 918 res, out, err = runcmd(
919 919 [
920 920 sys.executable,
921 921 'runrst',
922 922 'hgmanpage',
923 923 '--halt',
924 924 'warning',
925 925 '--strip-elements-with-class',
926 926 'htmlonly',
927 927 '%s.txt' % root,
928 928 root,
929 929 ],
930 930 os.environ,
931 931 cwd='doc',
932 932 )
933 933 if res:
934 934 raise SystemExit(
935 935 'error running runrst: %s' % '\n'.join([out, err])
936 936 )
937 937
938 938 normalizecrlf('doc/%s' % root)
939 939
940 940 def genhtml(root):
941 941 log.info('generating doc/%s.html' % root)
942 942 res, out, err = runcmd(
943 943 [
944 944 sys.executable,
945 945 'runrst',
946 946 'html',
947 947 '--halt',
948 948 'warning',
949 949 '--link-stylesheet',
950 950 '--stylesheet-path',
951 951 'style.css',
952 952 '%s.txt' % root,
953 953 '%s.html' % root,
954 954 ],
955 955 os.environ,
956 956 cwd='doc',
957 957 )
958 958 if res:
959 959 raise SystemExit(
960 960 'error running runrst: %s' % '\n'.join([out, err])
961 961 )
962 962
963 963 normalizecrlf('doc/%s.html' % root)
964 964
965 965 # This logic is duplicated in doc/Makefile.
966 966 sources = {
967 967 f
968 968 for f in os.listdir('mercurial/helptext')
969 969 if re.search(r'[0-9]\.txt$', f)
970 970 }
971 971
972 972 # common.txt is a one-off.
973 973 gentxt('common')
974 974
975 975 for source in sorted(sources):
976 976 assert source[-4:] == '.txt'
977 977 root = source[:-4]
978 978
979 979 gentxt(root)
980 980 gengendoc(root)
981 981
982 982 if self.man:
983 983 genman(root)
984 984 if self.html:
985 985 genhtml(root)
986 986
987 987
988 988 class hginstall(install):
989 989
990 990 user_options = install.user_options + [
991 991 (
992 992 'old-and-unmanageable',
993 993 None,
994 994 'noop, present for eggless setuptools compat',
995 995 ),
996 996 (
997 997 'single-version-externally-managed',
998 998 None,
999 999 'noop, present for eggless setuptools compat',
1000 1000 ),
1001 1001 ]
1002 1002
1003 1003 # Also helps setuptools not be sad while we refuse to create eggs.
1004 1004 single_version_externally_managed = True
1005 1005
1006 1006 def get_sub_commands(self):
1007 1007 # Screen out egg related commands to prevent egg generation. But allow
1008 1008 # mercurial.egg-info generation, since that is part of modern
1009 1009 # packaging.
1010 1010 excl = {'bdist_egg'}
1011 1011 return filter(lambda x: x not in excl, install.get_sub_commands(self))
1012 1012
1013 1013
1014 1014 class hginstalllib(install_lib):
1015 1015 '''
1016 1016 This is a specialization of install_lib that replaces the copy_file used
1017 1017 there so that it supports setting the mode of files after copying them,
1018 1018 instead of just preserving the mode that the files originally had. If your
1019 1019 system has a umask of something like 027, preserving the permissions when
1020 1020 copying will lead to a broken install.
1021 1021
1022 1022 Note that just passing keep_permissions=False to copy_file would be
1023 1023 insufficient, as it might still be applying a umask.
1024 1024 '''
1025 1025
1026 1026 def run(self):
1027 1027 realcopyfile = file_util.copy_file
1028 1028
1029 1029 def copyfileandsetmode(*args, **kwargs):
1030 1030 src, dst = args[0], args[1]
1031 1031 dst, copied = realcopyfile(*args, **kwargs)
1032 1032 if copied:
1033 1033 st = os.stat(src)
1034 1034 # Persist executable bit (apply it to group and other if user
1035 1035 # has it)
1036 1036 if st[stat.ST_MODE] & stat.S_IXUSR:
1037 1037 setmode = int('0755', 8)
1038 1038 else:
1039 1039 setmode = int('0644', 8)
1040 1040 m = stat.S_IMODE(st[stat.ST_MODE])
1041 1041 m = (m & ~int('0777', 8)) | setmode
1042 1042 os.chmod(dst, m)
1043 1043
1044 1044 file_util.copy_file = copyfileandsetmode
1045 1045 try:
1046 1046 install_lib.run(self)
1047 1047 finally:
1048 1048 file_util.copy_file = realcopyfile
1049 1049
1050 1050
1051 1051 class hginstallscripts(install_scripts):
1052 1052 '''
1053 1053 This is a specialization of install_scripts that replaces the @LIBDIR@ with
1054 1054 the configured directory for modules. If possible, the path is made relative
1055 1055 to the directory for scripts.
1056 1056 '''
1057 1057
1058 1058 def initialize_options(self):
1059 1059 install_scripts.initialize_options(self)
1060 1060
1061 1061 self.install_lib = None
1062 1062
1063 1063 def finalize_options(self):
1064 1064 install_scripts.finalize_options(self)
1065 1065 self.set_undefined_options('install', ('install_lib', 'install_lib'))
1066 1066
1067 1067 def run(self):
1068 1068 install_scripts.run(self)
1069 1069
1070 1070 # It only makes sense to replace @LIBDIR@ with the install path if
1071 1071 # the install path is known. For wheels, the logic below calculates
1072 1072 # the libdir to be "../..". This is because the internal layout of a
1073 1073 # wheel archive looks like:
1074 1074 #
1075 1075 # mercurial-3.6.1.data/scripts/hg
1076 1076 # mercurial/__init__.py
1077 1077 #
1078 1078 # When installing wheels, the subdirectories of the "<pkg>.data"
1079 1079 # directory are translated to system local paths and files therein
1080 1080 # are copied in place. The mercurial/* files are installed into the
1081 1081 # site-packages directory. However, the site-packages directory
1082 1082 # isn't known until wheel install time. This means we have no clue
1083 1083 # at wheel generation time what the installed site-packages directory
1084 1084 # will be. And, wheels don't appear to provide the ability to register
1085 1085 # custom code to run during wheel installation. This all means that
1086 1086 # we can't reliably set the libdir in wheels: the default behavior
1087 1087 # of looking in sys.path must do.
1088 1088
1089 1089 if (
1090 1090 os.path.splitdrive(self.install_dir)[0]
1091 1091 != os.path.splitdrive(self.install_lib)[0]
1092 1092 ):
1093 1093 # can't make relative paths from one drive to another, so use an
1094 1094 # absolute path instead
1095 1095 libdir = self.install_lib
1096 1096 else:
1097 1097 libdir = os.path.relpath(self.install_lib, self.install_dir)
1098 1098
1099 1099 for outfile in self.outfiles:
1100 1100 with open(outfile, 'rb') as fp:
1101 1101 data = fp.read()
1102 1102
1103 1103 # skip binary files
1104 1104 if b'\0' in data:
1105 1105 continue
1106 1106
1107 1107 # During local installs, the shebang will be rewritten to the final
1108 1108 # install path. During wheel packaging, the shebang has a special
1109 1109 # value.
1110 1110 if data.startswith(b'#!python'):
1111 1111 log.info(
1112 1112 'not rewriting @LIBDIR@ in %s because install path '
1113 1113 'not known' % outfile
1114 1114 )
1115 1115 continue
1116 1116
1117 1117 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
1118 1118 with open(outfile, 'wb') as fp:
1119 1119 fp.write(data)
1120 1120
1121 1121
1122 1122 # virtualenv installs custom distutils/__init__.py and
1123 1123 # distutils/distutils.cfg files which essentially proxy back to the
1124 1124 # "real" distutils in the main Python install. The presence of this
1125 1125 # directory causes py2exe to pick up the "hacked" distutils package
1126 1126 # from the virtualenv and "import distutils" will fail from the py2exe
1127 1127 # build because the "real" distutils files can't be located.
1128 1128 #
1129 1129 # We work around this by monkeypatching the py2exe code finding Python
1130 1130 # modules to replace the found virtualenv distutils modules with the
1131 1131 # original versions via filesystem scanning. This is a bit hacky. But
1132 1132 # it allows us to use virtualenvs for py2exe packaging, which is more
1133 1133 # deterministic and reproducible.
1134 1134 #
1135 1135 # It's worth noting that the common StackOverflow suggestions for this
1136 1136 # problem involve copying the original distutils files into the
1137 1137 # virtualenv or into the staging directory after setup() is invoked.
1138 1138 # The former is very brittle and can easily break setup(). Our hacking
1139 1139 # of the found modules routine has a similar result as copying the files
1140 1140 # manually. But it makes fewer assumptions about how py2exe works and
1141 1141 # is less brittle.
1142 1142
1143 1143 # This only catches virtualenvs made with virtualenv (as opposed to
1144 1144 # venv, which is likely what Python 3 uses).
1145 1145 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
1146 1146
1147 1147 if py2exehacked:
1148 1148 from distutils.command.py2exe import py2exe as buildpy2exe
1149 1149 from py2exe.mf import Module as py2exemodule
1150 1150
1151 1151 class hgbuildpy2exe(buildpy2exe):
1152 1152 def find_needed_modules(self, mf, files, modules):
1153 1153 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
1154 1154
1155 1155 # Replace virtualenv's distutils modules with the real ones.
1156 1156 modules = {}
1157 1157 for k, v in res.modules.items():
1158 1158 if k != 'distutils' and not k.startswith('distutils.'):
1159 1159 modules[k] = v
1160 1160
1161 1161 res.modules = modules
1162 1162
1163 1163 import opcode
1164 1164
1165 1165 distutilsreal = os.path.join(
1166 1166 os.path.dirname(opcode.__file__), 'distutils'
1167 1167 )
1168 1168
1169 1169 for root, dirs, files in os.walk(distutilsreal):
1170 1170 for f in sorted(files):
1171 1171 if not f.endswith('.py'):
1172 1172 continue
1173 1173
1174 1174 full = os.path.join(root, f)
1175 1175
1176 1176 parents = ['distutils']
1177 1177
1178 1178 if root != distutilsreal:
1179 1179 rel = os.path.relpath(root, distutilsreal)
1180 1180 parents.extend(p for p in rel.split(os.sep))
1181 1181
1182 1182 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1183 1183
1184 1184 if modname.startswith('distutils.tests.'):
1185 1185 continue
1186 1186
1187 1187 if modname.endswith('.__init__'):
1188 1188 modname = modname[: -len('.__init__')]
1189 1189 path = os.path.dirname(full)
1190 1190 else:
1191 1191 path = None
1192 1192
1193 1193 res.modules[modname] = py2exemodule(
1194 1194 modname, full, path=path
1195 1195 )
1196 1196
1197 1197 if 'distutils' not in res.modules:
1198 1198 raise SystemExit('could not find distutils modules')
1199 1199
1200 1200 return res
1201 1201
1202 1202
1203 1203 cmdclass = {
1204 1204 'build': hgbuild,
1205 1205 'build_doc': hgbuilddoc,
1206 1206 'build_mo': hgbuildmo,
1207 1207 'build_ext': hgbuildext,
1208 1208 'build_py': hgbuildpy,
1209 1209 'build_scripts': hgbuildscripts,
1210 1210 'build_hgextindex': buildhgextindex,
1211 1211 'install': hginstall,
1212 1212 'install_lib': hginstalllib,
1213 1213 'install_scripts': hginstallscripts,
1214 1214 'build_hgexe': buildhgexe,
1215 1215 }
1216 1216
1217 1217 if py2exehacked:
1218 1218 cmdclass['py2exe'] = hgbuildpy2exe
1219 1219
1220 1220 packages = [
1221 1221 'mercurial',
1222 1222 'mercurial.cext',
1223 1223 'mercurial.cffi',
1224 1224 'mercurial.defaultrc',
1225 1225 'mercurial.helptext',
1226 1226 'mercurial.helptext.internals',
1227 1227 'mercurial.hgweb',
1228 1228 'mercurial.interfaces',
1229 1229 'mercurial.pure',
1230 1230 'mercurial.thirdparty',
1231 1231 'mercurial.thirdparty.attr',
1232 1232 'mercurial.thirdparty.zope',
1233 1233 'mercurial.thirdparty.zope.interface',
1234 1234 'mercurial.utils',
1235 1235 'mercurial.revlogutils',
1236 1236 'mercurial.testing',
1237 1237 'hgext',
1238 1238 'hgext.convert',
1239 1239 'hgext.fsmonitor',
1240 1240 'hgext.fastannotate',
1241 1241 'hgext.fsmonitor.pywatchman',
1242 1242 'hgext.git',
1243 1243 'hgext.highlight',
1244 1244 'hgext.hooklib',
1245 1245 'hgext.infinitepush',
1246 1246 'hgext.largefiles',
1247 1247 'hgext.lfs',
1248 1248 'hgext.narrow',
1249 1249 'hgext.remotefilelog',
1250 1250 'hgext.zeroconf',
1251 1251 'hgext3rd',
1252 1252 'hgdemandimport',
1253 1253 ]
1254 1254 if sys.version_info[0] == 2:
1255 1255 packages.extend(
1256 1256 [
1257 1257 'mercurial.thirdparty.concurrent',
1258 1258 'mercurial.thirdparty.concurrent.futures',
1259 1259 ]
1260 1260 )
1261 1261
1262 1262 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1263 1263 # py2exe can't cope with namespace packages very well, so we have to
1264 1264 # install any hgext3rd.* extensions that we want in the final py2exe
1265 1265 # image here. This is gross, but you gotta do what you gotta do.
1266 1266 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1267 1267
1268 1268 common_depends = [
1269 1269 'mercurial/bitmanipulation.h',
1270 1270 'mercurial/compat.h',
1271 1271 'mercurial/cext/util.h',
1272 1272 ]
1273 1273 common_include_dirs = ['mercurial']
1274 1274
1275 1275 common_cflags = []
1276 1276
1277 1277 # MSVC 2008 still needs declarations at the top of the scope, but Python 3.9
1278 1278 # makes declarations not at the top of a scope in the headers.
1279 1279 if os.name != 'nt' and sys.version_info[1] < 9:
1280 1280 common_cflags = ['-Werror=declaration-after-statement']
1281 1281
1282 1282 osutil_cflags = []
1283 1283 osutil_ldflags = []
1284 1284
1285 1285 # platform specific macros
1286 1286 for plat, func in [('bsd', 'setproctitle')]:
1287 1287 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1288 1288 osutil_cflags.append('-DHAVE_%s' % func.upper())
1289 1289
1290 1290 for plat, macro, code in [
1291 1291 (
1292 1292 'bsd|darwin',
1293 1293 'BSD_STATFS',
1294 1294 '''
1295 1295 #include <sys/param.h>
1296 1296 #include <sys/mount.h>
1297 1297 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1298 1298 ''',
1299 1299 ),
1300 1300 (
1301 1301 'linux',
1302 1302 'LINUX_STATFS',
1303 1303 '''
1304 1304 #include <linux/magic.h>
1305 1305 #include <sys/vfs.h>
1306 1306 int main() { struct statfs s; return sizeof(s.f_type); }
1307 1307 ''',
1308 1308 ),
1309 1309 ]:
1310 1310 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1311 1311 osutil_cflags.append('-DHAVE_%s' % macro)
1312 1312
1313 1313 if sys.platform == 'darwin':
1314 1314 osutil_ldflags += ['-framework', 'ApplicationServices']
1315 1315
1316 1316 xdiff_srcs = [
1317 1317 'mercurial/thirdparty/xdiff/xdiffi.c',
1318 1318 'mercurial/thirdparty/xdiff/xprepare.c',
1319 1319 'mercurial/thirdparty/xdiff/xutils.c',
1320 1320 ]
1321 1321
1322 1322 xdiff_headers = [
1323 1323 'mercurial/thirdparty/xdiff/xdiff.h',
1324 1324 'mercurial/thirdparty/xdiff/xdiffi.h',
1325 1325 'mercurial/thirdparty/xdiff/xinclude.h',
1326 1326 'mercurial/thirdparty/xdiff/xmacros.h',
1327 1327 'mercurial/thirdparty/xdiff/xprepare.h',
1328 1328 'mercurial/thirdparty/xdiff/xtypes.h',
1329 1329 'mercurial/thirdparty/xdiff/xutils.h',
1330 1330 ]
1331 1331
1332 1332
1333 1333 class RustCompilationError(CCompilerError):
1334 1334 """Exception class for Rust compilation errors."""
1335 1335
1336 1336
1337 1337 class RustExtension(Extension):
1338 1338 """Base classes for concrete Rust Extension classes.
1339 1339 """
1340 1340
1341 1341 rusttargetdir = os.path.join('rust', 'target', 'release')
1342 1342
1343 1343 def __init__(
1344 1344 self, mpath, sources, rustlibname, subcrate, py3_features=None, **kw
1345 1345 ):
1346 1346 Extension.__init__(self, mpath, sources, **kw)
1347 1347 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1348 1348 self.py3_features = py3_features
1349 1349
1350 1350 # adding Rust source and control files to depends so that the extension
1351 1351 # gets rebuilt if they've changed
1352 1352 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1353 1353 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1354 1354 if os.path.exists(cargo_lock):
1355 1355 self.depends.append(cargo_lock)
1356 1356 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1357 1357 self.depends.extend(
1358 1358 os.path.join(dirpath, fname)
1359 1359 for fname in fnames
1360 1360 if os.path.splitext(fname)[1] == '.rs'
1361 1361 )
1362 1362
1363 1363 @staticmethod
1364 1364 def rustdylibsuffix():
1365 1365 """Return the suffix for shared libraries produced by rustc.
1366 1366
1367 1367 See also: https://doc.rust-lang.org/reference/linkage.html
1368 1368 """
1369 1369 if sys.platform == 'darwin':
1370 1370 return '.dylib'
1371 1371 elif os.name == 'nt':
1372 1372 return '.dll'
1373 1373 else:
1374 1374 return '.so'
1375 1375
1376 1376 def rustbuild(self):
1377 1377 env = os.environ.copy()
1378 1378 if 'HGTEST_RESTOREENV' in env:
1379 1379 # Mercurial tests change HOME to a temporary directory,
1380 1380 # but, if installed with rustup, the Rust toolchain needs
1381 1381 # HOME to be correct (otherwise the 'no default toolchain'
1382 1382 # error message is issued and the build fails).
1383 1383 # This happens currently with test-hghave.t, which does
1384 1384 # invoke this build.
1385 1385
1386 1386 # Unix only fix (os.path.expanduser not really reliable if
1387 1387 # HOME is shadowed like this)
1388 1388 import pwd
1389 1389
1390 1390 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1391 1391
1392 1392 cargocmd = ['cargo', 'rustc', '-vv', '--release']
1393 1393
1394 1394 feature_flags = []
1395 1395
1396 1396 if sys.version_info[0] == 3 and self.py3_features is not None:
1397 1397 feature_flags.append(self.py3_features)
1398 1398 cargocmd.append('--no-default-features')
1399 1399
1400 1400 rust_features = env.get("HG_RUST_FEATURES")
1401 1401 if rust_features:
1402 1402 feature_flags.append(rust_features)
1403 1403
1404 1404 cargocmd.extend(('--features', " ".join(feature_flags)))
1405 1405
1406 1406 cargocmd.append('--')
1407 1407 if sys.platform == 'darwin':
1408 1408 cargocmd.extend(
1409 1409 ("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup")
1410 1410 )
1411 1411 try:
1412 1412 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1413 1413 except OSError as exc:
1414 1414 if exc.errno == errno.ENOENT:
1415 1415 raise RustCompilationError("Cargo not found")
1416 1416 elif exc.errno == errno.EACCES:
1417 1417 raise RustCompilationError(
1418 1418 "Cargo found, but permisssion to execute it is denied"
1419 1419 )
1420 1420 else:
1421 1421 raise
1422 1422 except subprocess.CalledProcessError:
1423 1423 raise RustCompilationError(
1424 1424 "Cargo failed. Working directory: %r, "
1425 1425 "command: %r, environment: %r"
1426 1426 % (self.rustsrcdir, cargocmd, env)
1427 1427 )
1428 1428
1429 1429
1430 1430 class RustStandaloneExtension(RustExtension):
1431 1431 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1432 1432 RustExtension.__init__(
1433 1433 self, pydottedname, [], dylibname, rustcrate, **kw
1434 1434 )
1435 1435 self.dylibname = dylibname
1436 1436
1437 1437 def build(self, target_dir):
1438 1438 self.rustbuild()
1439 1439 target = [target_dir]
1440 1440 target.extend(self.name.split('.'))
1441 1441 target[-1] += DYLIB_SUFFIX
1442 1442 shutil.copy2(
1443 1443 os.path.join(
1444 1444 self.rusttargetdir, self.dylibname + self.rustdylibsuffix()
1445 1445 ),
1446 1446 os.path.join(*target),
1447 1447 )
1448 1448
1449 1449
1450 1450 extmodules = [
1451 1451 Extension(
1452 1452 'mercurial.cext.base85',
1453 1453 ['mercurial/cext/base85.c'],
1454 1454 include_dirs=common_include_dirs,
1455 1455 extra_compile_args=common_cflags,
1456 1456 depends=common_depends,
1457 1457 ),
1458 1458 Extension(
1459 1459 'mercurial.cext.bdiff',
1460 1460 ['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1461 1461 include_dirs=common_include_dirs,
1462 1462 extra_compile_args=common_cflags,
1463 1463 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers,
1464 1464 ),
1465 1465 Extension(
1466 1466 'mercurial.cext.mpatch',
1467 1467 ['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'],
1468 1468 include_dirs=common_include_dirs,
1469 1469 extra_compile_args=common_cflags,
1470 1470 depends=common_depends,
1471 1471 ),
1472 1472 Extension(
1473 1473 'mercurial.cext.parsers',
1474 1474 [
1475 1475 'mercurial/cext/charencode.c',
1476 1476 'mercurial/cext/dirs.c',
1477 1477 'mercurial/cext/manifest.c',
1478 1478 'mercurial/cext/parsers.c',
1479 1479 'mercurial/cext/pathencode.c',
1480 1480 'mercurial/cext/revlog.c',
1481 1481 ],
1482 1482 include_dirs=common_include_dirs,
1483 1483 extra_compile_args=common_cflags,
1484 1484 depends=common_depends
1485 1485 + ['mercurial/cext/charencode.h', 'mercurial/cext/revlog.h',],
1486 1486 ),
1487 1487 Extension(
1488 1488 'mercurial.cext.osutil',
1489 1489 ['mercurial/cext/osutil.c'],
1490 1490 include_dirs=common_include_dirs,
1491 1491 extra_compile_args=common_cflags + osutil_cflags,
1492 1492 extra_link_args=osutil_ldflags,
1493 1493 depends=common_depends,
1494 1494 ),
1495 1495 Extension(
1496 1496 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations',
1497 1497 [
1498 1498 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1499 1499 ],
1500 1500 extra_compile_args=common_cflags,
1501 1501 ),
1502 1502 Extension(
1503 1503 'mercurial.thirdparty.sha1dc',
1504 1504 [
1505 1505 'mercurial/thirdparty/sha1dc/cext.c',
1506 1506 'mercurial/thirdparty/sha1dc/lib/sha1.c',
1507 1507 'mercurial/thirdparty/sha1dc/lib/ubc_check.c',
1508 1508 ],
1509 1509 extra_compile_args=common_cflags,
1510 1510 ),
1511 1511 Extension(
1512 1512 'hgext.fsmonitor.pywatchman.bser',
1513 1513 ['hgext/fsmonitor/pywatchman/bser.c'],
1514 1514 extra_compile_args=common_cflags,
1515 1515 ),
1516 1516 RustStandaloneExtension(
1517 1517 'mercurial.rustext', 'hg-cpython', 'librusthg', py3_features='python3'
1518 1518 ),
1519 1519 ]
1520 1520
1521 1521
1522 1522 sys.path.insert(0, 'contrib/python-zstandard')
1523 1523 import setup_zstd
1524 1524
1525 1525 zstd = setup_zstd.get_c_extension(
1526 1526 name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__))
1527 1527 )
1528 1528 zstd.extra_compile_args += common_cflags
1529 1529 extmodules.append(zstd)
1530 1530
1531 1531 try:
1532 1532 from distutils import cygwinccompiler
1533 1533
1534 1534 # the -mno-cygwin option has been deprecated for years
1535 1535 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1536 1536
1537 1537 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1538 1538 def __init__(self, *args, **kwargs):
1539 1539 mingw32compilerclass.__init__(self, *args, **kwargs)
1540 1540 for i in 'compiler compiler_so linker_exe linker_so'.split():
1541 1541 try:
1542 1542 getattr(self, i).remove('-mno-cygwin')
1543 1543 except ValueError:
1544 1544 pass
1545 1545
1546 1546 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1547 1547 except ImportError:
1548 1548 # the cygwinccompiler package is not available on some Python
1549 1549 # distributions like the ones from the optware project for Synology
1550 1550 # DiskStation boxes
1551 1551 class HackedMingw32CCompiler(object):
1552 1552 pass
1553 1553
1554 1554
1555 1555 if os.name == 'nt':
1556 1556 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1557 1557 # extra_link_args to distutils.extensions.Extension() doesn't have any
1558 1558 # effect.
1559 1559 from distutils import msvccompiler
1560 1560
1561 1561 msvccompilerclass = msvccompiler.MSVCCompiler
1562 1562
1563 1563 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1564 1564 def initialize(self):
1565 1565 msvccompilerclass.initialize(self)
1566 1566 # "warning LNK4197: export 'func' specified multiple times"
1567 1567 self.ldflags_shared.append('/ignore:4197')
1568 1568 self.ldflags_shared_debug.append('/ignore:4197')
1569 1569
1570 1570 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1571 1571
1572 1572 packagedata = {
1573 1573 'mercurial': [
1574 1574 'locale/*/LC_MESSAGES/hg.mo',
1575 1575 'defaultrc/*.rc',
1576 1576 'dummycert.pem',
1577 1577 ],
1578 1578 'mercurial.helptext': ['*.txt',],
1579 1579 'mercurial.helptext.internals': ['*.txt',],
1580 1580 }
1581 1581
1582 1582
1583 1583 def ordinarypath(p):
1584 1584 return p and p[0] != '.' and p[-1] != '~'
1585 1585
1586 1586
1587 1587 for root in ('templates',):
1588 1588 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1589 1589 curdir = curdir.split(os.sep, 1)[1]
1590 1590 dirs[:] = filter(ordinarypath, dirs)
1591 1591 for f in filter(ordinarypath, files):
1592 1592 f = os.path.join(curdir, f)
1593 1593 packagedata['mercurial'].append(f)
1594 1594
1595 1595 datafiles = []
1596 1596
1597 1597 # distutils expects version to be str/unicode. Converting it to
1598 1598 # unicode on Python 2 still works because it won't contain any
1599 1599 # non-ascii bytes and will be implicitly converted back to bytes
1600 1600 # when operated on.
1601 1601 assert isinstance(version, bytes)
1602 1602 setupversion = version.decode('ascii')
1603 1603
1604 1604 extra = {}
1605 1605
1606 1606 py2exepackages = [
1607 1607 'hgdemandimport',
1608 1608 'hgext3rd',
1609 1609 'hgext',
1610 1610 'email',
1611 1611 # implicitly imported per module policy
1612 1612 # (cffi wouldn't be used as a frozen exe)
1613 1613 'mercurial.cext',
1614 1614 #'mercurial.cffi',
1615 1615 'mercurial.pure',
1616 1616 ]
1617 1617
1618 1618 py2exeexcludes = []
1619 1619 py2exedllexcludes = ['crypt32.dll']
1620 1620
1621 1621 if issetuptools:
1622 1622 extra['python_requires'] = supportedpy
1623 1623
1624 1624 if py2exeloaded:
1625 1625 extra['console'] = [
1626 1626 {
1627 1627 'script': 'hg',
1628 1628 'copyright': 'Copyright (C) 2005-2020 Matt Mackall and others',
1629 1629 'product_version': version,
1630 1630 }
1631 1631 ]
1632 1632 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1633 1633 # Need to override hgbuild because it has a private copy of
1634 1634 # build.sub_commands.
1635 1635 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1636 1636 # put dlls in sub directory so that they won't pollute PATH
1637 1637 extra['zipfile'] = 'lib/library.zip'
1638 1638
1639 1639 # We allow some configuration to be supplemented via environment
1640 1640 # variables. This is better than setup.cfg files because it allows
1641 1641 # supplementing configs instead of replacing them.
1642 1642 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1643 1643 if extrapackages:
1644 1644 py2exepackages.extend(extrapackages.split(' '))
1645 1645
1646 1646 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1647 1647 if excludes:
1648 1648 py2exeexcludes.extend(excludes.split(' '))
1649 1649
1650 1650 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1651 1651 if dllexcludes:
1652 1652 py2exedllexcludes.extend(dllexcludes.split(' '))
1653 1653
1654 1654 if os.name == 'nt':
1655 1655 # Windows binary file versions for exe/dll files must have the
1656 1656 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1657 1657 setupversion = setupversion.split(r'+', 1)[0]
1658 1658
1659 1659 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1660 1660 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1661 1661 if version:
1662 1662 version = version[0]
1663 1663 if sys.version_info[0] == 3:
1664 1664 version = version.decode('utf-8')
1665 1665 xcode4 = version.startswith('Xcode') and StrictVersion(
1666 1666 version.split()[1]
1667 1667 ) >= StrictVersion('4.0')
1668 1668 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1669 1669 else:
1670 1670 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1671 1671 # installed, but instead with only command-line tools. Assume
1672 1672 # that only happens on >= Lion, thus no PPC support.
1673 1673 xcode4 = True
1674 1674 xcode51 = False
1675 1675
1676 1676 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1677 1677 # distutils.sysconfig
1678 1678 if xcode4:
1679 1679 os.environ['ARCHFLAGS'] = ''
1680 1680
1681 1681 # XCode 5.1 changes clang such that it now fails to compile if the
1682 1682 # -mno-fused-madd flag is passed, but the version of Python shipped with
1683 1683 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1684 1684 # C extension modules, and a bug has been filed upstream at
1685 1685 # http://bugs.python.org/issue21244. We also need to patch this here
1686 1686 # so Mercurial can continue to compile in the meantime.
1687 1687 if xcode51:
1688 1688 cflags = get_config_var('CFLAGS')
1689 1689 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1690 1690 os.environ['CFLAGS'] = (
1691 1691 os.environ.get('CFLAGS', '') + ' -Qunused-arguments'
1692 1692 )
1693 1693
1694 1694 setup(
1695 1695 name='mercurial',
1696 1696 version=setupversion,
1697 1697 author='Matt Mackall and many others',
1698 1698 author_email='mercurial@mercurial-scm.org',
1699 1699 url='https://mercurial-scm.org/',
1700 1700 download_url='https://mercurial-scm.org/release/',
1701 1701 description=(
1702 1702 'Fast scalable distributed SCM (revision control, version '
1703 1703 'control) system'
1704 1704 ),
1705 1705 long_description=(
1706 1706 'Mercurial is a distributed SCM tool written in Python.'
1707 1707 ' It is used by a number of large projects that require'
1708 1708 ' fast, reliable distributed revision control, such as '
1709 1709 'Mozilla.'
1710 1710 ),
1711 1711 license='GNU GPLv2 or any later version',
1712 1712 classifiers=[
1713 1713 'Development Status :: 6 - Mature',
1714 1714 'Environment :: Console',
1715 1715 'Intended Audience :: Developers',
1716 1716 'Intended Audience :: System Administrators',
1717 1717 'License :: OSI Approved :: GNU General Public License (GPL)',
1718 1718 'Natural Language :: Danish',
1719 1719 'Natural Language :: English',
1720 1720 'Natural Language :: German',
1721 1721 'Natural Language :: Italian',
1722 1722 'Natural Language :: Japanese',
1723 1723 'Natural Language :: Portuguese (Brazilian)',
1724 1724 'Operating System :: Microsoft :: Windows',
1725 1725 'Operating System :: OS Independent',
1726 1726 'Operating System :: POSIX',
1727 1727 'Programming Language :: C',
1728 1728 'Programming Language :: Python',
1729 1729 'Topic :: Software Development :: Version Control',
1730 1730 ],
1731 1731 scripts=scripts,
1732 1732 packages=packages,
1733 1733 ext_modules=extmodules,
1734 1734 data_files=datafiles,
1735 1735 package_data=packagedata,
1736 1736 cmdclass=cmdclass,
1737 1737 distclass=hgdist,
1738 1738 options={
1739 1739 'py2exe': {
1740 1740 'bundle_files': 3,
1741 1741 'dll_excludes': py2exedllexcludes,
1742 1742 'excludes': py2exeexcludes,
1743 1743 'packages': py2exepackages,
1744 1744 },
1745 1745 'bdist_mpkg': {
1746 1746 'zipdist': False,
1747 1747 'license': 'COPYING',
1748 1748 'readme': 'contrib/packaging/macosx/Readme.html',
1749 1749 'welcome': 'contrib/packaging/macosx/Welcome.html',
1750 1750 },
1751 1751 },
1752 1752 **extra
1753 1753 )
General Comments 0
You need to be logged in to leave comments. Login now