##// END OF EJS Templates
merge with stable
Augie Fackler -
r43188:f059d6ff merge default
parent child Browse files
Show More
@@ -1,185 +1,186 b''
1 35fb62a3a673d5322f6274a44ba6456e5e4b3b37 0 iD8DBQBEYmO2ywK+sNU5EO8RAnaYAKCO7x15xUn5mnhqWNXqk/ehlhRt2QCfRDfY0LrUq2q4oK/KypuJYPHgq1A=
1 35fb62a3a673d5322f6274a44ba6456e5e4b3b37 0 iD8DBQBEYmO2ywK+sNU5EO8RAnaYAKCO7x15xUn5mnhqWNXqk/ehlhRt2QCfRDfY0LrUq2q4oK/KypuJYPHgq1A=
2 2be3001847cb18a23c403439d9e7d0ace30804e9 0 iD8DBQBExUbjywK+sNU5EO8RAhzxAKCtyHAQUzcTSZTqlfJ0by6vhREwWQCghaQFHfkfN0l9/40EowNhuMOKnJk=
2 2be3001847cb18a23c403439d9e7d0ace30804e9 0 iD8DBQBExUbjywK+sNU5EO8RAhzxAKCtyHAQUzcTSZTqlfJ0by6vhREwWQCghaQFHfkfN0l9/40EowNhuMOKnJk=
3 36a957364b1b89c150f2d0e60a99befe0ee08bd3 0 iD8DBQBFfL2QywK+sNU5EO8RAjYFAKCoGlaWRTeMsjdmxAjUYx6diZxOBwCfY6IpBYsKvPTwB3oktnPt5Rmrlys=
3 36a957364b1b89c150f2d0e60a99befe0ee08bd3 0 iD8DBQBFfL2QywK+sNU5EO8RAjYFAKCoGlaWRTeMsjdmxAjUYx6diZxOBwCfY6IpBYsKvPTwB3oktnPt5Rmrlys=
4 27230c29bfec36d5540fbe1c976810aefecfd1d2 0 iD8DBQBFheweywK+sNU5EO8RAt7VAKCrqJQWT2/uo2RWf0ZI4bLp6v82jACgjrMdsaTbxRsypcmEsdPhlG6/8F4=
4 27230c29bfec36d5540fbe1c976810aefecfd1d2 0 iD8DBQBFheweywK+sNU5EO8RAt7VAKCrqJQWT2/uo2RWf0ZI4bLp6v82jACgjrMdsaTbxRsypcmEsdPhlG6/8F4=
5 fb4b6d5fe100b0886f8bc3d6731ec0e5ed5c4694 0 iD8DBQBGgHicywK+sNU5EO8RAgNxAJ0VG8ixAaeudx4sZbhngI1syu49HQCeNUJQfWBgA8bkJ2pvsFpNxwYaX3I=
5 fb4b6d5fe100b0886f8bc3d6731ec0e5ed5c4694 0 iD8DBQBGgHicywK+sNU5EO8RAgNxAJ0VG8ixAaeudx4sZbhngI1syu49HQCeNUJQfWBgA8bkJ2pvsFpNxwYaX3I=
6 23889160905a1b09fffe1c07378e9fc1827606eb 0 iD8DBQBHGTzoywK+sNU5EO8RAr/UAJ0Y8s4jQtzgS+G9vM8z6CWBThZ8fwCcCT5XDj2XwxKkz/0s6UELwjsO3LU=
6 23889160905a1b09fffe1c07378e9fc1827606eb 0 iD8DBQBHGTzoywK+sNU5EO8RAr/UAJ0Y8s4jQtzgS+G9vM8z6CWBThZ8fwCcCT5XDj2XwxKkz/0s6UELwjsO3LU=
7 bae2e9c838e90a393bae3973a7850280413e091a 0 iD8DBQBH6DO5ywK+sNU5EO8RAsfrAJ0e4r9c9GF/MJsM7Xjd3NesLRC3+ACffj6+6HXdZf8cswAoFPO+DY00oD0=
7 bae2e9c838e90a393bae3973a7850280413e091a 0 iD8DBQBH6DO5ywK+sNU5EO8RAsfrAJ0e4r9c9GF/MJsM7Xjd3NesLRC3+ACffj6+6HXdZf8cswAoFPO+DY00oD0=
8 d5cbbe2c49cee22a9fbeb9ea41daa0ac4e26b846 0 iD8DBQBINdwsywK+sNU5EO8RAjIUAKCPmlFJSpsPAAUKF+iNHAwVnwmzeQCdEXrL27CWclXuUKdbQC8De7LICtE=
8 d5cbbe2c49cee22a9fbeb9ea41daa0ac4e26b846 0 iD8DBQBINdwsywK+sNU5EO8RAjIUAKCPmlFJSpsPAAUKF+iNHAwVnwmzeQCdEXrL27CWclXuUKdbQC8De7LICtE=
9 d2375bbee6d47e62ba8e415c86e83a465dc4dce9 0 iD8DBQBIo1wpywK+sNU5EO8RAmRNAJ94x3OFt6blbqu/yBoypm/AJ44fuACfUaldXcV5z9tht97hSp22DVTEPGc=
9 d2375bbee6d47e62ba8e415c86e83a465dc4dce9 0 iD8DBQBIo1wpywK+sNU5EO8RAmRNAJ94x3OFt6blbqu/yBoypm/AJ44fuACfUaldXcV5z9tht97hSp22DVTEPGc=
10 2a67430f92f15ea5159c26b09ec4839a0c549a26 0 iEYEABECAAYFAkk1hykACgkQywK+sNU5EO85QACeNJNUanjc2tl4wUoPHNuv+lSj0ZMAoIm93wSTc/feyYnO2YCaQ1iyd9Nu
10 2a67430f92f15ea5159c26b09ec4839a0c549a26 0 iEYEABECAAYFAkk1hykACgkQywK+sNU5EO85QACeNJNUanjc2tl4wUoPHNuv+lSj0ZMAoIm93wSTc/feyYnO2YCaQ1iyd9Nu
11 3773e510d433969e277b1863c317b674cbee2065 0 iEYEABECAAYFAklNbbAACgkQywK+sNU5EO8o+gCfeb2/lfIJZMvyDA1m+G1CsBAxfFsAoIa6iAMG8SBY7hW1Q85Yf/LXEvaE
11 3773e510d433969e277b1863c317b674cbee2065 0 iEYEABECAAYFAklNbbAACgkQywK+sNU5EO8o+gCfeb2/lfIJZMvyDA1m+G1CsBAxfFsAoIa6iAMG8SBY7hW1Q85Yf/LXEvaE
12 11a4eb81fb4f4742451591489e2797dc47903277 0 iEYEABECAAYFAklcAnsACgkQywK+sNU5EO+uXwCbBVHNNsLy1g7BlAyQJwadYVyHOXoAoKvtAVO71+bv7EbVoukwTzT+P4Sx
12 11a4eb81fb4f4742451591489e2797dc47903277 0 iEYEABECAAYFAklcAnsACgkQywK+sNU5EO+uXwCbBVHNNsLy1g7BlAyQJwadYVyHOXoAoKvtAVO71+bv7EbVoukwTzT+P4Sx
13 11efa41037e280d08cfb07c09ad485df30fb0ea8 0 iEYEABECAAYFAkmvJRQACgkQywK+sNU5EO9XZwCeLMgDgPSMWMm6vgjL4lDs2pEc5+0AnRxfiFbpbBfuEFTqKz9nbzeyoBlx
13 11efa41037e280d08cfb07c09ad485df30fb0ea8 0 iEYEABECAAYFAkmvJRQACgkQywK+sNU5EO9XZwCeLMgDgPSMWMm6vgjL4lDs2pEc5+0AnRxfiFbpbBfuEFTqKz9nbzeyoBlx
14 02981000012e3adf40c4849bd7b3d5618f9ce82d 0 iEYEABECAAYFAknEH3wACgkQywK+sNU5EO+uXwCeI+LbLMmhjU1lKSfU3UWJHjjUC7oAoIZLvYDGOL/tNZFUuatc3RnZ2eje
14 02981000012e3adf40c4849bd7b3d5618f9ce82d 0 iEYEABECAAYFAknEH3wACgkQywK+sNU5EO+uXwCeI+LbLMmhjU1lKSfU3UWJHjjUC7oAoIZLvYDGOL/tNZFUuatc3RnZ2eje
15 196d40e7c885fa6e95f89134809b3ec7bdbca34b 0 iEYEABECAAYFAkpL2X4ACgkQywK+sNU5EO9FOwCfXJycjyKJXsvQqKkHrglwOQhEKS4An36GfKzptfN8b1qNc3+ya/5c2WOM
15 196d40e7c885fa6e95f89134809b3ec7bdbca34b 0 iEYEABECAAYFAkpL2X4ACgkQywK+sNU5EO9FOwCfXJycjyKJXsvQqKkHrglwOQhEKS4An36GfKzptfN8b1qNc3+ya/5c2WOM
16 3ef6c14a1e8e83a31226f5881b7fe6095bbfa6f6 0 iEYEABECAAYFAkpopLIACgkQywK+sNU5EO8QSgCfZ0ztsd071rOa2lhmp9Fyue/WoI0AoLTei80/xrhRlB8L/rZEf2KBl8dA
16 3ef6c14a1e8e83a31226f5881b7fe6095bbfa6f6 0 iEYEABECAAYFAkpopLIACgkQywK+sNU5EO8QSgCfZ0ztsd071rOa2lhmp9Fyue/WoI0AoLTei80/xrhRlB8L/rZEf2KBl8dA
17 31ec469f9b556f11819937cf68ee53f2be927ebf 0 iEYEABECAAYFAksBuxAACgkQywK+sNU5EO+mBwCfagB+A0txzWZ6dRpug3LEoK7Z1QsAoKpbk8vsLjv6/oRDicSk/qBu33+m
17 31ec469f9b556f11819937cf68ee53f2be927ebf 0 iEYEABECAAYFAksBuxAACgkQywK+sNU5EO+mBwCfagB+A0txzWZ6dRpug3LEoK7Z1QsAoKpbk8vsLjv6/oRDicSk/qBu33+m
18 439d7ea6fe3aa4ab9ec274a68846779153789de9 0 iEYEABECAAYFAksVw0kACgkQywK+sNU5EO/oZwCfdfBEkgp38xq6wN2F4nj+SzofrJIAnjmxt04vaJSeOOeHylHvk6lzuQsw
18 439d7ea6fe3aa4ab9ec274a68846779153789de9 0 iEYEABECAAYFAksVw0kACgkQywK+sNU5EO/oZwCfdfBEkgp38xq6wN2F4nj+SzofrJIAnjmxt04vaJSeOOeHylHvk6lzuQsw
19 296a0b14a68621f6990c54fdba0083f6f20935bf 0 iEYEABECAAYFAks+jCoACgkQywK+sNU5EO9J8wCeMUGF9E/gS2UBsqIz56WS4HMPRPUAoI5J95mwEIK8Clrl7qFRidNI6APq
19 296a0b14a68621f6990c54fdba0083f6f20935bf 0 iEYEABECAAYFAks+jCoACgkQywK+sNU5EO9J8wCeMUGF9E/gS2UBsqIz56WS4HMPRPUAoI5J95mwEIK8Clrl7qFRidNI6APq
20 4aa619c4c2c09907034d9824ebb1dd0e878206eb 0 iEYEABECAAYFAktm9IsACgkQywK+sNU5EO9XGgCgk4HclRQhexEtooPE5GcUCdB6M8EAn2ptOhMVbIoO+JncA+tNACPFXh0O
20 4aa619c4c2c09907034d9824ebb1dd0e878206eb 0 iEYEABECAAYFAktm9IsACgkQywK+sNU5EO9XGgCgk4HclRQhexEtooPE5GcUCdB6M8EAn2ptOhMVbIoO+JncA+tNACPFXh0O
21 ff2704a8ded37fbebd8b6eb5ec733731d725da8a 0 iEYEABECAAYFAkuRoSQACgkQywK+sNU5EO//3QCeJDc5r2uFyFCtAlpSA27DEE5rrxAAn2FSwTy9fhrB3QAdDQlwkEZcQzDh
21 ff2704a8ded37fbebd8b6eb5ec733731d725da8a 0 iEYEABECAAYFAkuRoSQACgkQywK+sNU5EO//3QCeJDc5r2uFyFCtAlpSA27DEE5rrxAAn2FSwTy9fhrB3QAdDQlwkEZcQzDh
22 2b01dab594167bc0dd33331dbaa6dca3dca1b3aa 0 iEYEABECAAYFAku1IwIACgkQywK+sNU5EO9MjgCdHLVwkTZlNHxhcznZKBL1rjN+J7cAoLLWi9LTL6f/TgBaPSKOy1ublbaW
22 2b01dab594167bc0dd33331dbaa6dca3dca1b3aa 0 iEYEABECAAYFAku1IwIACgkQywK+sNU5EO9MjgCdHLVwkTZlNHxhcznZKBL1rjN+J7cAoLLWi9LTL6f/TgBaPSKOy1ublbaW
23 39f725929f0c48c5fb3b90c071fc3066012456ca 0 iEYEABECAAYFAkvclvsACgkQywK+sNU5EO9FSwCeL9i5x8ALW/LE5+lCX6MFEAe4MhwAn1ev5o6SX6GrNdDfKweiemfO2VBk
23 39f725929f0c48c5fb3b90c071fc3066012456ca 0 iEYEABECAAYFAkvclvsACgkQywK+sNU5EO9FSwCeL9i5x8ALW/LE5+lCX6MFEAe4MhwAn1ev5o6SX6GrNdDfKweiemfO2VBk
24 fdcf80f26604f233dc4d8f0a5ef9d7470e317e8a 0 iEYEABECAAYFAkvsKTkACgkQywK+sNU5EO9qEACgiSiRGvTG2vXGJ65tUSOIYihTuFAAnRzRIqEVSw8M8/RGeUXRps0IzaCO
24 fdcf80f26604f233dc4d8f0a5ef9d7470e317e8a 0 iEYEABECAAYFAkvsKTkACgkQywK+sNU5EO9qEACgiSiRGvTG2vXGJ65tUSOIYihTuFAAnRzRIqEVSw8M8/RGeUXRps0IzaCO
25 24fe2629c6fd0c74c90bd066e77387c2b02e8437 0 iEYEABECAAYFAkwFLRsACgkQywK+sNU5EO+pJACgp13tPI+pbwKZV+LeMjcQ4H6tCZYAoJebzhd6a8yYx6qiwpJxA9BXZNXy
25 24fe2629c6fd0c74c90bd066e77387c2b02e8437 0 iEYEABECAAYFAkwFLRsACgkQywK+sNU5EO+pJACgp13tPI+pbwKZV+LeMjcQ4H6tCZYAoJebzhd6a8yYx6qiwpJxA9BXZNXy
26 f786fc4b8764cd2a5526d259cf2f94d8a66924d9 0 iEYEABECAAYFAkwsyxcACgkQywK+sNU5EO+crACfUpNAF57PmClkSri9nJcBjb2goN4AniPCNaKvnki7TnUsi1u2oxltpKKL
26 f786fc4b8764cd2a5526d259cf2f94d8a66924d9 0 iEYEABECAAYFAkwsyxcACgkQywK+sNU5EO+crACfUpNAF57PmClkSri9nJcBjb2goN4AniPCNaKvnki7TnUsi1u2oxltpKKL
27 bf1774d95bde614af3956d92b20e2a0c68c5fec7 0 iEYEABECAAYFAkxVwccACgkQywK+sNU5EO+oFQCeJzwZ+we1fIIyBGCddHceOUAN++cAnjvT6A8ZWW0zV21NXIFF1qQmjxJd
27 bf1774d95bde614af3956d92b20e2a0c68c5fec7 0 iEYEABECAAYFAkxVwccACgkQywK+sNU5EO+oFQCeJzwZ+we1fIIyBGCddHceOUAN++cAnjvT6A8ZWW0zV21NXIFF1qQmjxJd
28 c00f03a4982e467fb6b6bd45908767db6df4771d 0 iEYEABECAAYFAkxXDqsACgkQywK+sNU5EO/GJACfT9Rz4hZOxPQEs91JwtmfjevO84gAmwSmtfo5mmWSm8gtTUebCcdTv0Kf
28 c00f03a4982e467fb6b6bd45908767db6df4771d 0 iEYEABECAAYFAkxXDqsACgkQywK+sNU5EO/GJACfT9Rz4hZOxPQEs91JwtmfjevO84gAmwSmtfo5mmWSm8gtTUebCcdTv0Kf
29 ff5cec76b1c5b6be9c3bb923aae8c3c6d079d6b9 0 iD8DBQBMdo+qywK+sNU5EO8RAqQpAJ975BL2CCAiWMz9SXthNQ9xG181IwCgp4O+KViHPkufZVFn2aTKMNvcr1A=
29 ff5cec76b1c5b6be9c3bb923aae8c3c6d079d6b9 0 iD8DBQBMdo+qywK+sNU5EO8RAqQpAJ975BL2CCAiWMz9SXthNQ9xG181IwCgp4O+KViHPkufZVFn2aTKMNvcr1A=
30 93d8bff78c96fe7e33237b257558ee97290048a4 0 iD8DBQBMpfvdywK+sNU5EO8RAsxVAJ0UaL1XB51C76JUBhafc9GBefuMxwCdEWkTOzwvE0SarJBe9i008jhbqW4=
30 93d8bff78c96fe7e33237b257558ee97290048a4 0 iD8DBQBMpfvdywK+sNU5EO8RAsxVAJ0UaL1XB51C76JUBhafc9GBefuMxwCdEWkTOzwvE0SarJBe9i008jhbqW4=
31 333421b9e0f96c7bc788e5667c146a58a9440a55 0 iD8DBQBMz0HOywK+sNU5EO8RAlsEAJ0USh6yOG7OrWkADGunVt9QimBQnwCbBqeMnKgSbwEw8jZwE3Iz1mdrYlo=
31 333421b9e0f96c7bc788e5667c146a58a9440a55 0 iD8DBQBMz0HOywK+sNU5EO8RAlsEAJ0USh6yOG7OrWkADGunVt9QimBQnwCbBqeMnKgSbwEw8jZwE3Iz1mdrYlo=
32 4438875ec01bd0fc32be92b0872eb6daeed4d44f 0 iD8DBQBM4WYUywK+sNU5EO8RAhCVAJ0dJswachwFAHALmk1x0RJehxzqPQCbBNskP9n/X689jB+btNTZTyKU/fw=
32 4438875ec01bd0fc32be92b0872eb6daeed4d44f 0 iD8DBQBM4WYUywK+sNU5EO8RAhCVAJ0dJswachwFAHALmk1x0RJehxzqPQCbBNskP9n/X689jB+btNTZTyKU/fw=
33 6aff4f144ad356311318b0011df0bb21f2c97429 0 iD8DBQBM9uxXywK+sNU5EO8RAv+4AKCDj4qKP16GdPaq1tP6BUwpM/M1OACfRyzLPp/qiiN8xJTWoWYSe/XjJug=
33 6aff4f144ad356311318b0011df0bb21f2c97429 0 iD8DBQBM9uxXywK+sNU5EO8RAv+4AKCDj4qKP16GdPaq1tP6BUwpM/M1OACfRyzLPp/qiiN8xJTWoWYSe/XjJug=
34 e3bf16703e2601de99e563cdb3a5d50b64e6d320 0 iD8DBQBNH8WqywK+sNU5EO8RAiQTAJ9sBO+TeiGro4si77VVaQaA6jcRUgCfSA28dBbjj0oFoQwvPoZjANiZBH8=
34 e3bf16703e2601de99e563cdb3a5d50b64e6d320 0 iD8DBQBNH8WqywK+sNU5EO8RAiQTAJ9sBO+TeiGro4si77VVaQaA6jcRUgCfSA28dBbjj0oFoQwvPoZjANiZBH8=
35 a6c855c32ea081da3c3b8ff628f1847ff271482f 0 iD8DBQBNSJJ+ywK+sNU5EO8RAoJaAKCweDEF70fu+r1Zn7pYDXdlk5RuSgCeO9gK/eit8Lin/1n3pO7aYguFLok=
35 a6c855c32ea081da3c3b8ff628f1847ff271482f 0 iD8DBQBNSJJ+ywK+sNU5EO8RAoJaAKCweDEF70fu+r1Zn7pYDXdlk5RuSgCeO9gK/eit8Lin/1n3pO7aYguFLok=
36 2b2155623ee2559caf288fd333f30475966c4525 0 iD8DBQBNSJeBywK+sNU5EO8RAm1KAJ4hW9Cm9nHaaGJguchBaPLlAr+O3wCgqgmMok8bdAS06N6PL60PSTM//Gg=
36 2b2155623ee2559caf288fd333f30475966c4525 0 iD8DBQBNSJeBywK+sNU5EO8RAm1KAJ4hW9Cm9nHaaGJguchBaPLlAr+O3wCgqgmMok8bdAS06N6PL60PSTM//Gg=
37 2616325766e3504c8ae7c84bd15ee610901fe91d 0 iD8DBQBNbWy9ywK+sNU5EO8RAlWCAJ4mW8HbzjJj9GpK98muX7k+7EvEHwCfaTLbC/DH3QEsZBhEP+M8tzL6RU4=
37 2616325766e3504c8ae7c84bd15ee610901fe91d 0 iD8DBQBNbWy9ywK+sNU5EO8RAlWCAJ4mW8HbzjJj9GpK98muX7k+7EvEHwCfaTLbC/DH3QEsZBhEP+M8tzL6RU4=
38 aa1f3be38ab127280761889d2dca906ca465b5f4 0 iD8DBQBNeQq7ywK+sNU5EO8RAlEOAJ4tlEDdetE9lKfjGgjbkcR8PrC3egCfXCfF3qNVvU/2YYjpgvRwevjvDy0=
38 aa1f3be38ab127280761889d2dca906ca465b5f4 0 iD8DBQBNeQq7ywK+sNU5EO8RAlEOAJ4tlEDdetE9lKfjGgjbkcR8PrC3egCfXCfF3qNVvU/2YYjpgvRwevjvDy0=
39 b032bec2c0a651ca0ddecb65714bfe6770f67d70 0 iD8DBQBNlg5kywK+sNU5EO8RAnGEAJ9gmEx6MfaR4XcG2m/93vwtfyzs3gCgltzx8/YdHPwqDwRX/WbpYgi33is=
39 b032bec2c0a651ca0ddecb65714bfe6770f67d70 0 iD8DBQBNlg5kywK+sNU5EO8RAnGEAJ9gmEx6MfaR4XcG2m/93vwtfyzs3gCgltzx8/YdHPwqDwRX/WbpYgi33is=
40 3cb1e95676ad089596bd81d0937cad37d6e3b7fb 0 iD8DBQBNvTy4ywK+sNU5EO8RAmp8AJ9QnxK4jTJ7G722MyeBxf0UXEdGwACgtlM7BKtNQfbEH/fOW5y+45W88VI=
40 3cb1e95676ad089596bd81d0937cad37d6e3b7fb 0 iD8DBQBNvTy4ywK+sNU5EO8RAmp8AJ9QnxK4jTJ7G722MyeBxf0UXEdGwACgtlM7BKtNQfbEH/fOW5y+45W88VI=
41 733af5d9f6b22387913e1d11350fb8cb7c1487dd 0 iD8DBQBN5q/8ywK+sNU5EO8RArRGAKCNGT94GKIYtSuwZ57z1sQbcw6uLACfffpbMV4NAPMl8womAwg+7ZPKnIU=
41 733af5d9f6b22387913e1d11350fb8cb7c1487dd 0 iD8DBQBN5q/8ywK+sNU5EO8RArRGAKCNGT94GKIYtSuwZ57z1sQbcw6uLACfffpbMV4NAPMl8womAwg+7ZPKnIU=
42 de9eb6b1da4fc522b1cab16d86ca166204c24f25 0 iD8DBQBODhfhywK+sNU5EO8RAr2+AJ4ugbAj8ae8/K0bYZzx3sascIAg1QCeK3b+zbbVVqd3b7CDpwFnaX8kTd4=
42 de9eb6b1da4fc522b1cab16d86ca166204c24f25 0 iD8DBQBODhfhywK+sNU5EO8RAr2+AJ4ugbAj8ae8/K0bYZzx3sascIAg1QCeK3b+zbbVVqd3b7CDpwFnaX8kTd4=
43 4a43e23b8c55b4566b8200bf69fe2158485a2634 0 iD8DBQBONzIMywK+sNU5EO8RAj5SAJ0aPS3+JHnyI6bHB2Fl0LImbDmagwCdGbDLp1S7TFobxXudOH49bX45Iik=
43 4a43e23b8c55b4566b8200bf69fe2158485a2634 0 iD8DBQBONzIMywK+sNU5EO8RAj5SAJ0aPS3+JHnyI6bHB2Fl0LImbDmagwCdGbDLp1S7TFobxXudOH49bX45Iik=
44 d629f1e89021103f1753addcef6b310e4435b184 0 iD8DBQBOWAsBywK+sNU5EO8RAht4AJwJl9oNFopuGkj5m8aKuf7bqPkoAQCeNrEm7UhFsZKYT5iUOjnMV7s2LaM=
44 d629f1e89021103f1753addcef6b310e4435b184 0 iD8DBQBOWAsBywK+sNU5EO8RAht4AJwJl9oNFopuGkj5m8aKuf7bqPkoAQCeNrEm7UhFsZKYT5iUOjnMV7s2LaM=
45 351a9292e430e35766c552066ed3e87c557b803b 0 iD8DBQBOh3zUywK+sNU5EO8RApFMAKCD3Y/u3avDFndznwqfG5UeTHMlvACfUivPIVQZyDZnhZMq0UhC6zhCEQg=
45 351a9292e430e35766c552066ed3e87c557b803b 0 iD8DBQBOh3zUywK+sNU5EO8RApFMAKCD3Y/u3avDFndznwqfG5UeTHMlvACfUivPIVQZyDZnhZMq0UhC6zhCEQg=
46 384082750f2c51dc917d85a7145748330fa6ef4d 0 iD8DBQBOmd+OywK+sNU5EO8RAgDgAJ9V/X+G7VLwhTpHrZNiOHabzSyzYQCdE2kKfIevJUYB9QLAWCWP6DPwrwI=
46 384082750f2c51dc917d85a7145748330fa6ef4d 0 iD8DBQBOmd+OywK+sNU5EO8RAgDgAJ9V/X+G7VLwhTpHrZNiOHabzSyzYQCdE2kKfIevJUYB9QLAWCWP6DPwrwI=
47 41453d55b481ddfcc1dacb445179649e24ca861d 0 iD8DBQBOsFhpywK+sNU5EO8RAqM6AKCyfxUae3/zLuiLdQz+JR78690eMACfQ6JTBQib4AbE+rUDdkeFYg9K/+4=
47 41453d55b481ddfcc1dacb445179649e24ca861d 0 iD8DBQBOsFhpywK+sNU5EO8RAqM6AKCyfxUae3/zLuiLdQz+JR78690eMACfQ6JTBQib4AbE+rUDdkeFYg9K/+4=
48 195dbd1cef0c2f9f8bcf4ea303238105f716bda3 0 iD8DBQBO1/fWywK+sNU5EO8RAmoPAKCR5lpv1D6JLURHD8KVLSV4GRVEBgCgnd0Sy78ligNfqAMafmACRDvj7vo=
48 195dbd1cef0c2f9f8bcf4ea303238105f716bda3 0 iD8DBQBO1/fWywK+sNU5EO8RAmoPAKCR5lpv1D6JLURHD8KVLSV4GRVEBgCgnd0Sy78ligNfqAMafmACRDvj7vo=
49 6344043924497cd06d781d9014c66802285072e4 0 iD8DBQBPALgmywK+sNU5EO8RAlfhAJ9nYOdWnhfVDHYtDTJAyJtXBAQS9wCgnefoSQt7QABkbGxM+Q85UYEBuD0=
49 6344043924497cd06d781d9014c66802285072e4 0 iD8DBQBPALgmywK+sNU5EO8RAlfhAJ9nYOdWnhfVDHYtDTJAyJtXBAQS9wCgnefoSQt7QABkbGxM+Q85UYEBuD0=
50 db33555eafeaf9df1e18950e29439eaa706d399b 0 iD8DBQBPGdzxywK+sNU5EO8RAppkAJ9jOXhUVE/97CPgiMA0pMGiIYnesQCfengAszcBiSiKGugiI8Okc9ghU+Y=
50 db33555eafeaf9df1e18950e29439eaa706d399b 0 iD8DBQBPGdzxywK+sNU5EO8RAppkAJ9jOXhUVE/97CPgiMA0pMGiIYnesQCfengAszcBiSiKGugiI8Okc9ghU+Y=
51 2aa5b51f310fb3befd26bed99c02267f5c12c734 0 iD8DBQBPKZ9bywK+sNU5EO8RAt1TAJ45r1eJ0YqSkInzrrayg4TVCh0SnQCgm0GA/Ua74jnnDwVQ60lAwROuz1Q=
51 2aa5b51f310fb3befd26bed99c02267f5c12c734 0 iD8DBQBPKZ9bywK+sNU5EO8RAt1TAJ45r1eJ0YqSkInzrrayg4TVCh0SnQCgm0GA/Ua74jnnDwVQ60lAwROuz1Q=
52 53e2cd303ecf8ca7c7eeebd785c34e5ed6b0f4a4 0 iD8DBQBPT/fvywK+sNU5EO8RAnfYAKCn7d0vwqIb100YfWm1F7nFD5B+FACeM02YHpQLSNsztrBCObtqcnfod7Q=
52 53e2cd303ecf8ca7c7eeebd785c34e5ed6b0f4a4 0 iD8DBQBPT/fvywK+sNU5EO8RAnfYAKCn7d0vwqIb100YfWm1F7nFD5B+FACeM02YHpQLSNsztrBCObtqcnfod7Q=
53 b9bd95e61b49c221c4cca24e6da7c946fc02f992 0 iD8DBQBPeLsIywK+sNU5EO8RAvpNAKCtKe2gitz8dYn52IRF0hFOPCR7AQCfRJL/RWCFweu2T1vH/mUOCf8SXXc=
53 b9bd95e61b49c221c4cca24e6da7c946fc02f992 0 iD8DBQBPeLsIywK+sNU5EO8RAvpNAKCtKe2gitz8dYn52IRF0hFOPCR7AQCfRJL/RWCFweu2T1vH/mUOCf8SXXc=
54 d9e2f09d5488c395ae9ddbb320ceacd24757e055 0 iD8DBQBPju/dywK+sNU5EO8RArBYAJ9xtifdbk+hCOJO8OZa4JfHX8OYZQCeKPMBaBWiT8N/WHoOm1XU0q+iono=
54 d9e2f09d5488c395ae9ddbb320ceacd24757e055 0 iD8DBQBPju/dywK+sNU5EO8RArBYAJ9xtifdbk+hCOJO8OZa4JfHX8OYZQCeKPMBaBWiT8N/WHoOm1XU0q+iono=
55 00182b3d087909e3c3ae44761efecdde8f319ef3 0 iD8DBQBPoFhIywK+sNU5EO8RAhzhAKCBj1n2jxPTkZNJJ5pSp3soa+XHIgCgsZZpAQxOpXwCp0eCdNGe0+pmxmg=
55 00182b3d087909e3c3ae44761efecdde8f319ef3 0 iD8DBQBPoFhIywK+sNU5EO8RAhzhAKCBj1n2jxPTkZNJJ5pSp3soa+XHIgCgsZZpAQxOpXwCp0eCdNGe0+pmxmg=
56 5983de86462c5a9f42a3ad0f5e90ce5b1d221d25 0 iD8DBQBPovNWywK+sNU5EO8RAhgiAJ980T91FdPTRMmVONDhpkMsZwVIMACgg3bKvoWSeuCW28llUhAJtUjrMv0=
56 5983de86462c5a9f42a3ad0f5e90ce5b1d221d25 0 iD8DBQBPovNWywK+sNU5EO8RAhgiAJ980T91FdPTRMmVONDhpkMsZwVIMACgg3bKvoWSeuCW28llUhAJtUjrMv0=
57 85a358df5bbbe404ca25730c9c459b34263441dc 0 iD8DBQBPyZsWywK+sNU5EO8RAnpLAJ48qrGDJRT+pteS0mSQ11haqHstPwCdG4ccGbk+0JHb7aNy8/NRGAOqn9w=
57 85a358df5bbbe404ca25730c9c459b34263441dc 0 iD8DBQBPyZsWywK+sNU5EO8RAnpLAJ48qrGDJRT+pteS0mSQ11haqHstPwCdG4ccGbk+0JHb7aNy8/NRGAOqn9w=
58 b013baa3898e117959984fc64c29d8c784d2f28b 0 iD8DBQBP8QOPywK+sNU5EO8RAqimAKCFRSx0lvG6y8vne2IhNG062Hn0dACeMLI5/zhpWpHBIVeAAquYfx2XFeA=
58 b013baa3898e117959984fc64c29d8c784d2f28b 0 iD8DBQBP8QOPywK+sNU5EO8RAqimAKCFRSx0lvG6y8vne2IhNG062Hn0dACeMLI5/zhpWpHBIVeAAquYfx2XFeA=
59 7f5094bb3f423fc799e471aac2aee81a7ce57a0b 0 iD8DBQBQGiL8ywK+sNU5EO8RAq5oAJ4rMMCPx6O+OuzNXVOexogedWz/QgCeIiIxLd76I4pXO48tdXhr0hQcBuM=
59 7f5094bb3f423fc799e471aac2aee81a7ce57a0b 0 iD8DBQBQGiL8ywK+sNU5EO8RAq5oAJ4rMMCPx6O+OuzNXVOexogedWz/QgCeIiIxLd76I4pXO48tdXhr0hQcBuM=
60 072209ae4ddb654eb2d5fd35bff358c738414432 0 iD8DBQBQQkq0ywK+sNU5EO8RArDTAJ9nk5CySnNAjAXYvqvx4uWCw9ThZwCgqmFRehH/l+oTwj3f8nw8u8qTCdc=
60 072209ae4ddb654eb2d5fd35bff358c738414432 0 iD8DBQBQQkq0ywK+sNU5EO8RArDTAJ9nk5CySnNAjAXYvqvx4uWCw9ThZwCgqmFRehH/l+oTwj3f8nw8u8qTCdc=
61 b3f0f9a39c4e1d0250048cd803ab03542d6f140a 0 iD8DBQBQamltywK+sNU5EO8RAlsqAJ4qF/m6aFu4mJCOKTiAP5RvZFK02ACfawYShUZO6OXEFfveU0aAxDR0M1k=
61 b3f0f9a39c4e1d0250048cd803ab03542d6f140a 0 iD8DBQBQamltywK+sNU5EO8RAlsqAJ4qF/m6aFu4mJCOKTiAP5RvZFK02ACfawYShUZO6OXEFfveU0aAxDR0M1k=
62 d118a4f4fd16d9b558ec3f3e87bfee772861d2b7 0 iD8DBQBQgPV5ywK+sNU5EO8RArylAJ0abcx5NlDjyv3ZDWpAfRIHyRsJtQCgn4TMuEayqgxzrvadQZHdTEU2g38=
62 d118a4f4fd16d9b558ec3f3e87bfee772861d2b7 0 iD8DBQBQgPV5ywK+sNU5EO8RArylAJ0abcx5NlDjyv3ZDWpAfRIHyRsJtQCgn4TMuEayqgxzrvadQZHdTEU2g38=
63 195ad823b5d58c68903a6153a25e3fb4ed25239d 0 iD8DBQBQkuT9ywK+sNU5EO8RAhB4AKCeerItoK2Jipm2cVf4euGofAa/WACeJj3TVd4pFILpb+ogj7ebweFLJi0=
63 195ad823b5d58c68903a6153a25e3fb4ed25239d 0 iD8DBQBQkuT9ywK+sNU5EO8RAhB4AKCeerItoK2Jipm2cVf4euGofAa/WACeJj3TVd4pFILpb+ogj7ebweFLJi0=
64 0c10cf8191469e7c3c8844922e17e71a176cb7cb 0 iD8DBQBQvQWoywK+sNU5EO8RAnq3AJoCn98u4geFx5YaQaeh99gFhCd7bQCgjoBwBSUyOvGd0yBy60E3Vv3VZhM=
64 0c10cf8191469e7c3c8844922e17e71a176cb7cb 0 iD8DBQBQvQWoywK+sNU5EO8RAnq3AJoCn98u4geFx5YaQaeh99gFhCd7bQCgjoBwBSUyOvGd0yBy60E3Vv3VZhM=
65 a4765077b65e6ae29ba42bab7834717b5072d5ba 0 iD8DBQBQ486sywK+sNU5EO8RAhmJAJ90aLfLKZhmcZN7kqphigQJxiFOQACeJ5IUZxjGKH4xzi3MrgIcx9n+dB0=
65 a4765077b65e6ae29ba42bab7834717b5072d5ba 0 iD8DBQBQ486sywK+sNU5EO8RAhmJAJ90aLfLKZhmcZN7kqphigQJxiFOQACeJ5IUZxjGKH4xzi3MrgIcx9n+dB0=
66 f5fbe15ca7449f2c9a3cf817c86d0ae68b307214 0 iD8DBQBQ+yuYywK+sNU5EO8RAm9JAJoD/UciWvpGeKBcpGtZJBFJVcL/HACghDXSgQ+xQDjB+6uGrdgAQsRR1Lg=
66 f5fbe15ca7449f2c9a3cf817c86d0ae68b307214 0 iD8DBQBQ+yuYywK+sNU5EO8RAm9JAJoD/UciWvpGeKBcpGtZJBFJVcL/HACghDXSgQ+xQDjB+6uGrdgAQsRR1Lg=
67 a6088c05e43a8aee0472ca3a4f6f8d7dd914ebbf 0 iD8DBQBRDDROywK+sNU5EO8RAh75AJ9uJCGoCWnP0Lv/+XuYs4hvUl+sAgCcD36QgAnuw8IQXrvv684BAXAnHcA=
67 a6088c05e43a8aee0472ca3a4f6f8d7dd914ebbf 0 iD8DBQBRDDROywK+sNU5EO8RAh75AJ9uJCGoCWnP0Lv/+XuYs4hvUl+sAgCcD36QgAnuw8IQXrvv684BAXAnHcA=
68 7511d4df752e61fe7ae4f3682e0a0008573b0402 0 iD8DBQBRFYaoywK+sNU5EO8RAuErAJoDyhXn+lptU3+AevVdwAIeNFyR2gCdHzPHyWd+JDeWCUR+pSOBi8O2ppM=
68 7511d4df752e61fe7ae4f3682e0a0008573b0402 0 iD8DBQBRFYaoywK+sNU5EO8RAuErAJoDyhXn+lptU3+AevVdwAIeNFyR2gCdHzPHyWd+JDeWCUR+pSOBi8O2ppM=
69 5b7175377babacce80a6c1e12366d8032a6d4340 0 iD8DBQBRMCYgywK+sNU5EO8RAq1/AKCWKlt9ysibyQgYwoxxIOZv5J8rpwCcDSHQaaf1fFZUTnQsOePwcM2Y/Sg=
69 5b7175377babacce80a6c1e12366d8032a6d4340 0 iD8DBQBRMCYgywK+sNU5EO8RAq1/AKCWKlt9ysibyQgYwoxxIOZv5J8rpwCcDSHQaaf1fFZUTnQsOePwcM2Y/Sg=
70 50c922c1b5145dab8baefefb0437d363b6a6c21c 0 iD8DBQBRWnUnywK+sNU5EO8RAuQRAJwM42cJqJPeqJ0jVNdMqKMDqr4dSACeP0cRVGz1gitMuV0x8f3mrZrqc7I=
70 50c922c1b5145dab8baefefb0437d363b6a6c21c 0 iD8DBQBRWnUnywK+sNU5EO8RAuQRAJwM42cJqJPeqJ0jVNdMqKMDqr4dSACeP0cRVGz1gitMuV0x8f3mrZrqc7I=
71 8a7bd2dccd44ed571afe7424cd7f95594f27c092 0 iD8DBQBRXfBvywK+sNU5EO8RAn+LAKCsMmflbuXjYRxlzFwId5ptm8TZcwCdGkyLbZcASBOkzQUm/WW1qfknJHU=
71 8a7bd2dccd44ed571afe7424cd7f95594f27c092 0 iD8DBQBRXfBvywK+sNU5EO8RAn+LAKCsMmflbuXjYRxlzFwId5ptm8TZcwCdGkyLbZcASBOkzQUm/WW1qfknJHU=
72 292cd385856d98bacb2c3086f8897bc660c2beea 0 iD8DBQBRcM0BywK+sNU5EO8RAjp4AKCJBykQbvXhKuvLSMxKx3a2TBiXcACfbr/kLg5GlZTF/XDPmY+PyHgI/GM=
72 292cd385856d98bacb2c3086f8897bc660c2beea 0 iD8DBQBRcM0BywK+sNU5EO8RAjp4AKCJBykQbvXhKuvLSMxKx3a2TBiXcACfbr/kLg5GlZTF/XDPmY+PyHgI/GM=
73 23f785b38af38d2fca6b8f3db56b8007a84cd73a 0 iD8DBQBRgZwNywK+sNU5EO8RAmO4AJ4u2ILGuimRP6MJgE2t65LZ5dAdkACgiENEstIdrlFC80p+sWKD81kKIYI=
73 23f785b38af38d2fca6b8f3db56b8007a84cd73a 0 iD8DBQBRgZwNywK+sNU5EO8RAmO4AJ4u2ILGuimRP6MJgE2t65LZ5dAdkACgiENEstIdrlFC80p+sWKD81kKIYI=
74 ddc7a6be20212d18f3e27d9d7e6f079a66d96f21 0 iD8DBQBRkswvywK+sNU5EO8RAiYYAJsHTHyHbJeAgmGvBTmDrfcKu4doUgCeLm7eGBjx7yAPUvEtxef8rAkQmXI=
74 ddc7a6be20212d18f3e27d9d7e6f079a66d96f21 0 iD8DBQBRkswvywK+sNU5EO8RAiYYAJsHTHyHbJeAgmGvBTmDrfcKu4doUgCeLm7eGBjx7yAPUvEtxef8rAkQmXI=
75 cceaf7af4c9e9e6fa2dbfdcfe9856c5da69c4ffd 0 iD8DBQBRqnFLywK+sNU5EO8RAsWNAJ9RR6t+y1DLFc2HeH0eN9VfZAKF9gCeJ8ezvhtKq/LMs0/nvcgKQc/d5jk=
75 cceaf7af4c9e9e6fa2dbfdcfe9856c5da69c4ffd 0 iD8DBQBRqnFLywK+sNU5EO8RAsWNAJ9RR6t+y1DLFc2HeH0eN9VfZAKF9gCeJ8ezvhtKq/LMs0/nvcgKQc/d5jk=
76 009794acc6e37a650f0fae37872e733382ac1c0c 0 iD8DBQBR0guxywK+sNU5EO8RArNkAKCq9pMihVzP8Os5kCmgbWpe5C37wgCgqzuPZTHvAsXF5wTyaSTMVa9Ccq4=
76 009794acc6e37a650f0fae37872e733382ac1c0c 0 iD8DBQBR0guxywK+sNU5EO8RArNkAKCq9pMihVzP8Os5kCmgbWpe5C37wgCgqzuPZTHvAsXF5wTyaSTMVa9Ccq4=
77 f0d7721d7322dcfb5af33599c2543f27335334bb 0 iD8DBQBR8taaywK+sNU5EO8RAqeEAJ4idDhhDuEsgsUjeQgWNj498matHACfT67gSF5w0ylsrBx1Hb52HkGXDm0=
77 f0d7721d7322dcfb5af33599c2543f27335334bb 0 iD8DBQBR8taaywK+sNU5EO8RAqeEAJ4idDhhDuEsgsUjeQgWNj498matHACfT67gSF5w0ylsrBx1Hb52HkGXDm0=
78 f37b5a17e6a0ee17afde2cdde5393dd74715fb58 0 iD8DBQBR+ymFywK+sNU5EO8RAuSdAJkBMcd9DAZ3rWE9WGKPm2YZ8LBoXACfXn/wbEsVy7ZgJoUwiWmHSnQaWCI=
78 f37b5a17e6a0ee17afde2cdde5393dd74715fb58 0 iD8DBQBR+ymFywK+sNU5EO8RAuSdAJkBMcd9DAZ3rWE9WGKPm2YZ8LBoXACfXn/wbEsVy7ZgJoUwiWmHSnQaWCI=
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=
79 335a558f81dc73afeab4d7be63617392b130117f 0 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
@@ -1,198 +1,199 b''
1 d40cc5aacc31ed673d9b5b24f98bee78c283062c 0.4f
1 d40cc5aacc31ed673d9b5b24f98bee78c283062c 0.4f
2 1c590d34bf61e2ea12c71738e5a746cd74586157 0.4e
2 1c590d34bf61e2ea12c71738e5a746cd74586157 0.4e
3 7eca4cfa8aad5fce9a04f7d8acadcd0452e2f34e 0.4d
3 7eca4cfa8aad5fce9a04f7d8acadcd0452e2f34e 0.4d
4 b4d0c3786ad3e47beacf8412157326a32b6d25a4 0.4c
4 b4d0c3786ad3e47beacf8412157326a32b6d25a4 0.4c
5 f40273b0ad7b3a6d3012fd37736d0611f41ecf54 0.5
5 f40273b0ad7b3a6d3012fd37736d0611f41ecf54 0.5
6 0a28dfe59f8fab54a5118c5be4f40da34a53cdb7 0.5b
6 0a28dfe59f8fab54a5118c5be4f40da34a53cdb7 0.5b
7 12e0fdbc57a0be78f0e817fd1d170a3615cd35da 0.6
7 12e0fdbc57a0be78f0e817fd1d170a3615cd35da 0.6
8 4ccf3de52989b14c3d84e1097f59e39a992e00bd 0.6b
8 4ccf3de52989b14c3d84e1097f59e39a992e00bd 0.6b
9 eac9c8efcd9bd8244e72fb6821f769f450457a32 0.6c
9 eac9c8efcd9bd8244e72fb6821f769f450457a32 0.6c
10 979c049974485125e1f9357f6bbe9c1b548a64c3 0.7
10 979c049974485125e1f9357f6bbe9c1b548a64c3 0.7
11 3a56574f329a368d645853e0f9e09472aee62349 0.8
11 3a56574f329a368d645853e0f9e09472aee62349 0.8
12 6a03cff2b0f5d30281e6addefe96b993582f2eac 0.8.1
12 6a03cff2b0f5d30281e6addefe96b993582f2eac 0.8.1
13 35fb62a3a673d5322f6274a44ba6456e5e4b3b37 0.9
13 35fb62a3a673d5322f6274a44ba6456e5e4b3b37 0.9
14 2be3001847cb18a23c403439d9e7d0ace30804e9 0.9.1
14 2be3001847cb18a23c403439d9e7d0ace30804e9 0.9.1
15 36a957364b1b89c150f2d0e60a99befe0ee08bd3 0.9.2
15 36a957364b1b89c150f2d0e60a99befe0ee08bd3 0.9.2
16 27230c29bfec36d5540fbe1c976810aefecfd1d2 0.9.3
16 27230c29bfec36d5540fbe1c976810aefecfd1d2 0.9.3
17 fb4b6d5fe100b0886f8bc3d6731ec0e5ed5c4694 0.9.4
17 fb4b6d5fe100b0886f8bc3d6731ec0e5ed5c4694 0.9.4
18 23889160905a1b09fffe1c07378e9fc1827606eb 0.9.5
18 23889160905a1b09fffe1c07378e9fc1827606eb 0.9.5
19 bae2e9c838e90a393bae3973a7850280413e091a 1.0
19 bae2e9c838e90a393bae3973a7850280413e091a 1.0
20 d5cbbe2c49cee22a9fbeb9ea41daa0ac4e26b846 1.0.1
20 d5cbbe2c49cee22a9fbeb9ea41daa0ac4e26b846 1.0.1
21 d2375bbee6d47e62ba8e415c86e83a465dc4dce9 1.0.2
21 d2375bbee6d47e62ba8e415c86e83a465dc4dce9 1.0.2
22 2a67430f92f15ea5159c26b09ec4839a0c549a26 1.1
22 2a67430f92f15ea5159c26b09ec4839a0c549a26 1.1
23 3773e510d433969e277b1863c317b674cbee2065 1.1.1
23 3773e510d433969e277b1863c317b674cbee2065 1.1.1
24 11a4eb81fb4f4742451591489e2797dc47903277 1.1.2
24 11a4eb81fb4f4742451591489e2797dc47903277 1.1.2
25 11efa41037e280d08cfb07c09ad485df30fb0ea8 1.2
25 11efa41037e280d08cfb07c09ad485df30fb0ea8 1.2
26 02981000012e3adf40c4849bd7b3d5618f9ce82d 1.2.1
26 02981000012e3adf40c4849bd7b3d5618f9ce82d 1.2.1
27 196d40e7c885fa6e95f89134809b3ec7bdbca34b 1.3
27 196d40e7c885fa6e95f89134809b3ec7bdbca34b 1.3
28 3ef6c14a1e8e83a31226f5881b7fe6095bbfa6f6 1.3.1
28 3ef6c14a1e8e83a31226f5881b7fe6095bbfa6f6 1.3.1
29 31ec469f9b556f11819937cf68ee53f2be927ebf 1.4
29 31ec469f9b556f11819937cf68ee53f2be927ebf 1.4
30 439d7ea6fe3aa4ab9ec274a68846779153789de9 1.4.1
30 439d7ea6fe3aa4ab9ec274a68846779153789de9 1.4.1
31 296a0b14a68621f6990c54fdba0083f6f20935bf 1.4.2
31 296a0b14a68621f6990c54fdba0083f6f20935bf 1.4.2
32 4aa619c4c2c09907034d9824ebb1dd0e878206eb 1.4.3
32 4aa619c4c2c09907034d9824ebb1dd0e878206eb 1.4.3
33 ff2704a8ded37fbebd8b6eb5ec733731d725da8a 1.5
33 ff2704a8ded37fbebd8b6eb5ec733731d725da8a 1.5
34 2b01dab594167bc0dd33331dbaa6dca3dca1b3aa 1.5.1
34 2b01dab594167bc0dd33331dbaa6dca3dca1b3aa 1.5.1
35 39f725929f0c48c5fb3b90c071fc3066012456ca 1.5.2
35 39f725929f0c48c5fb3b90c071fc3066012456ca 1.5.2
36 fdcf80f26604f233dc4d8f0a5ef9d7470e317e8a 1.5.3
36 fdcf80f26604f233dc4d8f0a5ef9d7470e317e8a 1.5.3
37 24fe2629c6fd0c74c90bd066e77387c2b02e8437 1.5.4
37 24fe2629c6fd0c74c90bd066e77387c2b02e8437 1.5.4
38 f786fc4b8764cd2a5526d259cf2f94d8a66924d9 1.6
38 f786fc4b8764cd2a5526d259cf2f94d8a66924d9 1.6
39 bf1774d95bde614af3956d92b20e2a0c68c5fec7 1.6.1
39 bf1774d95bde614af3956d92b20e2a0c68c5fec7 1.6.1
40 c00f03a4982e467fb6b6bd45908767db6df4771d 1.6.2
40 c00f03a4982e467fb6b6bd45908767db6df4771d 1.6.2
41 ff5cec76b1c5b6be9c3bb923aae8c3c6d079d6b9 1.6.3
41 ff5cec76b1c5b6be9c3bb923aae8c3c6d079d6b9 1.6.3
42 93d8bff78c96fe7e33237b257558ee97290048a4 1.6.4
42 93d8bff78c96fe7e33237b257558ee97290048a4 1.6.4
43 333421b9e0f96c7bc788e5667c146a58a9440a55 1.7
43 333421b9e0f96c7bc788e5667c146a58a9440a55 1.7
44 4438875ec01bd0fc32be92b0872eb6daeed4d44f 1.7.1
44 4438875ec01bd0fc32be92b0872eb6daeed4d44f 1.7.1
45 6aff4f144ad356311318b0011df0bb21f2c97429 1.7.2
45 6aff4f144ad356311318b0011df0bb21f2c97429 1.7.2
46 e3bf16703e2601de99e563cdb3a5d50b64e6d320 1.7.3
46 e3bf16703e2601de99e563cdb3a5d50b64e6d320 1.7.3
47 a6c855c32ea081da3c3b8ff628f1847ff271482f 1.7.4
47 a6c855c32ea081da3c3b8ff628f1847ff271482f 1.7.4
48 2b2155623ee2559caf288fd333f30475966c4525 1.7.5
48 2b2155623ee2559caf288fd333f30475966c4525 1.7.5
49 2616325766e3504c8ae7c84bd15ee610901fe91d 1.8
49 2616325766e3504c8ae7c84bd15ee610901fe91d 1.8
50 aa1f3be38ab127280761889d2dca906ca465b5f4 1.8.1
50 aa1f3be38ab127280761889d2dca906ca465b5f4 1.8.1
51 b032bec2c0a651ca0ddecb65714bfe6770f67d70 1.8.2
51 b032bec2c0a651ca0ddecb65714bfe6770f67d70 1.8.2
52 3cb1e95676ad089596bd81d0937cad37d6e3b7fb 1.8.3
52 3cb1e95676ad089596bd81d0937cad37d6e3b7fb 1.8.3
53 733af5d9f6b22387913e1d11350fb8cb7c1487dd 1.8.4
53 733af5d9f6b22387913e1d11350fb8cb7c1487dd 1.8.4
54 de9eb6b1da4fc522b1cab16d86ca166204c24f25 1.9
54 de9eb6b1da4fc522b1cab16d86ca166204c24f25 1.9
55 4a43e23b8c55b4566b8200bf69fe2158485a2634 1.9.1
55 4a43e23b8c55b4566b8200bf69fe2158485a2634 1.9.1
56 d629f1e89021103f1753addcef6b310e4435b184 1.9.2
56 d629f1e89021103f1753addcef6b310e4435b184 1.9.2
57 351a9292e430e35766c552066ed3e87c557b803b 1.9.3
57 351a9292e430e35766c552066ed3e87c557b803b 1.9.3
58 384082750f2c51dc917d85a7145748330fa6ef4d 2.0-rc
58 384082750f2c51dc917d85a7145748330fa6ef4d 2.0-rc
59 41453d55b481ddfcc1dacb445179649e24ca861d 2.0
59 41453d55b481ddfcc1dacb445179649e24ca861d 2.0
60 195dbd1cef0c2f9f8bcf4ea303238105f716bda3 2.0.1
60 195dbd1cef0c2f9f8bcf4ea303238105f716bda3 2.0.1
61 6344043924497cd06d781d9014c66802285072e4 2.0.2
61 6344043924497cd06d781d9014c66802285072e4 2.0.2
62 db33555eafeaf9df1e18950e29439eaa706d399b 2.1-rc
62 db33555eafeaf9df1e18950e29439eaa706d399b 2.1-rc
63 2aa5b51f310fb3befd26bed99c02267f5c12c734 2.1
63 2aa5b51f310fb3befd26bed99c02267f5c12c734 2.1
64 53e2cd303ecf8ca7c7eeebd785c34e5ed6b0f4a4 2.1.1
64 53e2cd303ecf8ca7c7eeebd785c34e5ed6b0f4a4 2.1.1
65 b9bd95e61b49c221c4cca24e6da7c946fc02f992 2.1.2
65 b9bd95e61b49c221c4cca24e6da7c946fc02f992 2.1.2
66 d9e2f09d5488c395ae9ddbb320ceacd24757e055 2.2-rc
66 d9e2f09d5488c395ae9ddbb320ceacd24757e055 2.2-rc
67 00182b3d087909e3c3ae44761efecdde8f319ef3 2.2
67 00182b3d087909e3c3ae44761efecdde8f319ef3 2.2
68 5983de86462c5a9f42a3ad0f5e90ce5b1d221d25 2.2.1
68 5983de86462c5a9f42a3ad0f5e90ce5b1d221d25 2.2.1
69 85a358df5bbbe404ca25730c9c459b34263441dc 2.2.2
69 85a358df5bbbe404ca25730c9c459b34263441dc 2.2.2
70 b013baa3898e117959984fc64c29d8c784d2f28b 2.2.3
70 b013baa3898e117959984fc64c29d8c784d2f28b 2.2.3
71 a06e2681dd1786e2354d84a5fa9c1c88dd4fa3e0 2.3-rc
71 a06e2681dd1786e2354d84a5fa9c1c88dd4fa3e0 2.3-rc
72 7f5094bb3f423fc799e471aac2aee81a7ce57a0b 2.3
72 7f5094bb3f423fc799e471aac2aee81a7ce57a0b 2.3
73 072209ae4ddb654eb2d5fd35bff358c738414432 2.3.1
73 072209ae4ddb654eb2d5fd35bff358c738414432 2.3.1
74 b3f0f9a39c4e1d0250048cd803ab03542d6f140a 2.3.2
74 b3f0f9a39c4e1d0250048cd803ab03542d6f140a 2.3.2
75 d118a4f4fd16d9b558ec3f3e87bfee772861d2b7 2.4-rc
75 d118a4f4fd16d9b558ec3f3e87bfee772861d2b7 2.4-rc
76 195ad823b5d58c68903a6153a25e3fb4ed25239d 2.4
76 195ad823b5d58c68903a6153a25e3fb4ed25239d 2.4
77 0c10cf8191469e7c3c8844922e17e71a176cb7cb 2.4.1
77 0c10cf8191469e7c3c8844922e17e71a176cb7cb 2.4.1
78 a4765077b65e6ae29ba42bab7834717b5072d5ba 2.4.2
78 a4765077b65e6ae29ba42bab7834717b5072d5ba 2.4.2
79 f5fbe15ca7449f2c9a3cf817c86d0ae68b307214 2.5-rc
79 f5fbe15ca7449f2c9a3cf817c86d0ae68b307214 2.5-rc
80 a6088c05e43a8aee0472ca3a4f6f8d7dd914ebbf 2.5
80 a6088c05e43a8aee0472ca3a4f6f8d7dd914ebbf 2.5
81 7511d4df752e61fe7ae4f3682e0a0008573b0402 2.5.1
81 7511d4df752e61fe7ae4f3682e0a0008573b0402 2.5.1
82 5b7175377babacce80a6c1e12366d8032a6d4340 2.5.2
82 5b7175377babacce80a6c1e12366d8032a6d4340 2.5.2
83 50c922c1b5145dab8baefefb0437d363b6a6c21c 2.5.3
83 50c922c1b5145dab8baefefb0437d363b6a6c21c 2.5.3
84 8a7bd2dccd44ed571afe7424cd7f95594f27c092 2.5.4
84 8a7bd2dccd44ed571afe7424cd7f95594f27c092 2.5.4
85 292cd385856d98bacb2c3086f8897bc660c2beea 2.6-rc
85 292cd385856d98bacb2c3086f8897bc660c2beea 2.6-rc
86 23f785b38af38d2fca6b8f3db56b8007a84cd73a 2.6
86 23f785b38af38d2fca6b8f3db56b8007a84cd73a 2.6
87 ddc7a6be20212d18f3e27d9d7e6f079a66d96f21 2.6.1
87 ddc7a6be20212d18f3e27d9d7e6f079a66d96f21 2.6.1
88 cceaf7af4c9e9e6fa2dbfdcfe9856c5da69c4ffd 2.6.2
88 cceaf7af4c9e9e6fa2dbfdcfe9856c5da69c4ffd 2.6.2
89 009794acc6e37a650f0fae37872e733382ac1c0c 2.6.3
89 009794acc6e37a650f0fae37872e733382ac1c0c 2.6.3
90 f0d7721d7322dcfb5af33599c2543f27335334bb 2.7-rc
90 f0d7721d7322dcfb5af33599c2543f27335334bb 2.7-rc
91 f37b5a17e6a0ee17afde2cdde5393dd74715fb58 2.7
91 f37b5a17e6a0ee17afde2cdde5393dd74715fb58 2.7
92 335a558f81dc73afeab4d7be63617392b130117f 2.7.1
92 335a558f81dc73afeab4d7be63617392b130117f 2.7.1
93 e7fa36d2ad3a7944a52dca126458d6f482db3524 2.7.2
93 e7fa36d2ad3a7944a52dca126458d6f482db3524 2.7.2
94 1596f2d8f2421314b1ddead8f7d0c91009358994 2.8-rc
94 1596f2d8f2421314b1ddead8f7d0c91009358994 2.8-rc
95 d825e4025e39d1c39db943cdc89818abd0a87c27 2.8
95 d825e4025e39d1c39db943cdc89818abd0a87c27 2.8
96 209e04a06467e2969c0cc6501335be0406d46ef0 2.8.1
96 209e04a06467e2969c0cc6501335be0406d46ef0 2.8.1
97 ca387377df7a3a67dbb90b6336b781cdadc3ef41 2.8.2
97 ca387377df7a3a67dbb90b6336b781cdadc3ef41 2.8.2
98 8862469e16f9236208581b20de5f96bd13cc039d 2.9-rc
98 8862469e16f9236208581b20de5f96bd13cc039d 2.9-rc
99 3cec5134e9c4bceab6a00c60f52a4f80677a78f2 2.9
99 3cec5134e9c4bceab6a00c60f52a4f80677a78f2 2.9
100 b96cb15ec9e04d8ac5ee08b34fcbbe4200588965 2.9.1
100 b96cb15ec9e04d8ac5ee08b34fcbbe4200588965 2.9.1
101 3f83fc5cfe715d292069ee8417c83804f6c6c1e4 2.9.2
101 3f83fc5cfe715d292069ee8417c83804f6c6c1e4 2.9.2
102 564f55b251224f16508dd1311452db7780dafe2b 3.0-rc
102 564f55b251224f16508dd1311452db7780dafe2b 3.0-rc
103 2195ac506c6ababe86985b932f4948837c0891b5 3.0
103 2195ac506c6ababe86985b932f4948837c0891b5 3.0
104 269c80ee5b3cb3684fa8edc61501b3506d02eb10 3.0.1
104 269c80ee5b3cb3684fa8edc61501b3506d02eb10 3.0.1
105 2d8cd3d0e83c7336c0cb45a9f88638363f993848 3.0.2
105 2d8cd3d0e83c7336c0cb45a9f88638363f993848 3.0.2
106 6c36dc6cd61a0e1b563f1d51e55bdf4dacf12162 3.1-rc
106 6c36dc6cd61a0e1b563f1d51e55bdf4dacf12162 3.1-rc
107 3178e49892020336491cdc6945885c4de26ffa8b 3.1
107 3178e49892020336491cdc6945885c4de26ffa8b 3.1
108 5dc91146f35369949ea56b40172308158b59063a 3.1.1
108 5dc91146f35369949ea56b40172308158b59063a 3.1.1
109 f768c888aaa68d12dd7f509dcc7f01c9584357d0 3.1.2
109 f768c888aaa68d12dd7f509dcc7f01c9584357d0 3.1.2
110 7f8d16af8cae246fa5a48e723d48d58b015aed94 3.2-rc
110 7f8d16af8cae246fa5a48e723d48d58b015aed94 3.2-rc
111 ced632394371a36953ce4d394f86278ae51a2aae 3.2
111 ced632394371a36953ce4d394f86278ae51a2aae 3.2
112 643c58303fb0ec020907af28b9e486be299ba043 3.2.1
112 643c58303fb0ec020907af28b9e486be299ba043 3.2.1
113 902554884335e5ca3661d63be9978eb4aec3f68a 3.2.2
113 902554884335e5ca3661d63be9978eb4aec3f68a 3.2.2
114 6dad422ecc5adb63d9fa649eeb8e05a5f9bc4900 3.2.3
114 6dad422ecc5adb63d9fa649eeb8e05a5f9bc4900 3.2.3
115 1265a3a71d75396f5d4cf6935ae7d9ba5407a547 3.2.4
115 1265a3a71d75396f5d4cf6935ae7d9ba5407a547 3.2.4
116 db8e3f7948b1fdeb9ad12d448fc3525759908b9f 3.3-rc
116 db8e3f7948b1fdeb9ad12d448fc3525759908b9f 3.3-rc
117 fbdd5195528fae4f41feebc1838215c110b25d6a 3.3
117 fbdd5195528fae4f41feebc1838215c110b25d6a 3.3
118 5b4ed033390bf6e2879c8f5c28c84e1ee3b87231 3.3.1
118 5b4ed033390bf6e2879c8f5c28c84e1ee3b87231 3.3.1
119 07a92bbd02e5e3a625e0820389b47786b02b2cea 3.3.2
119 07a92bbd02e5e3a625e0820389b47786b02b2cea 3.3.2
120 2e2e9a0750f91a6fe0ad88e4de34f8efefdcab08 3.3.3
120 2e2e9a0750f91a6fe0ad88e4de34f8efefdcab08 3.3.3
121 e89f909edffad558b56f4affa8239e4832f88de0 3.4-rc
121 e89f909edffad558b56f4affa8239e4832f88de0 3.4-rc
122 8cc6036bca532e06681c5a8fa37efaa812de67b5 3.4
122 8cc6036bca532e06681c5a8fa37efaa812de67b5 3.4
123 ed18f4acf435a2824c6f49fba40f42b9df5da7ad 3.4.1
123 ed18f4acf435a2824c6f49fba40f42b9df5da7ad 3.4.1
124 540cd0ddac49c1125b2e013aa2ff18ecbd4dd954 3.4.2
124 540cd0ddac49c1125b2e013aa2ff18ecbd4dd954 3.4.2
125 96a38d44ba093bd1d1ecfd34119e94056030278b 3.5-rc
125 96a38d44ba093bd1d1ecfd34119e94056030278b 3.5-rc
126 21aa1c313b05b1a85f8ffa1120d51579ddf6bf24 3.5
126 21aa1c313b05b1a85f8ffa1120d51579ddf6bf24 3.5
127 1a45e49a6bed023deb229102a8903234d18054d3 3.5.1
127 1a45e49a6bed023deb229102a8903234d18054d3 3.5.1
128 9a466b9f9792e3ad7ae3fc6c43c3ff2e136b718d 3.5.2
128 9a466b9f9792e3ad7ae3fc6c43c3ff2e136b718d 3.5.2
129 b66e3ca0b90c3095ea28dfd39aa24247bebf5c20 3.6-rc
129 b66e3ca0b90c3095ea28dfd39aa24247bebf5c20 3.6-rc
130 47dd34f2e7272be9e3b2a5a83cd0d20be44293f4 3.6
130 47dd34f2e7272be9e3b2a5a83cd0d20be44293f4 3.6
131 1aa5083cbebbe7575c88f3402ab377539b484897 3.6.1
131 1aa5083cbebbe7575c88f3402ab377539b484897 3.6.1
132 2d437a0f3355834a9485bbbeb30a52a052c98f19 3.6.2
132 2d437a0f3355834a9485bbbeb30a52a052c98f19 3.6.2
133 ea389970c08449440587712117f178d33bab3f1e 3.6.3
133 ea389970c08449440587712117f178d33bab3f1e 3.6.3
134 158bdc8965720ca4061f8f8d806563cfc7cdb62e 3.7-rc
134 158bdc8965720ca4061f8f8d806563cfc7cdb62e 3.7-rc
135 2408645de650d8a29a6ce9e7dce601d8dd0d1474 3.7
135 2408645de650d8a29a6ce9e7dce601d8dd0d1474 3.7
136 b698abf971e7377d9b7ec7fc8c52df45255b0329 3.7.1
136 b698abf971e7377d9b7ec7fc8c52df45255b0329 3.7.1
137 d493d64757eb45ada99fcb3693e479a51b7782da 3.7.2
137 d493d64757eb45ada99fcb3693e479a51b7782da 3.7.2
138 ae279d4a19e9683214cbd1fe8298cf0b50571432 3.7.3
138 ae279d4a19e9683214cbd1fe8298cf0b50571432 3.7.3
139 740156eedf2c450aee58b1a90b0e826f47c5da64 3.8-rc
139 740156eedf2c450aee58b1a90b0e826f47c5da64 3.8-rc
140 f85de28eae32e7d3064b1a1321309071bbaaa069 3.8
140 f85de28eae32e7d3064b1a1321309071bbaaa069 3.8
141 a56296f55a5e1038ea5016dace2076b693c28a56 3.8.1
141 a56296f55a5e1038ea5016dace2076b693c28a56 3.8.1
142 aaabed77791a75968a12b8c43ad263631a23ee81 3.8.2
142 aaabed77791a75968a12b8c43ad263631a23ee81 3.8.2
143 a9764ab80e11bcf6a37255db7dd079011f767c6c 3.8.3
143 a9764ab80e11bcf6a37255db7dd079011f767c6c 3.8.3
144 26a5d605b8683a292bb89aea11f37a81b06ac016 3.8.4
144 26a5d605b8683a292bb89aea11f37a81b06ac016 3.8.4
145 519bb4f9d3a47a6e83c2b414d58811ed38f503c2 3.9-rc
145 519bb4f9d3a47a6e83c2b414d58811ed38f503c2 3.9-rc
146 299546f84e68dbb9bd026f0f3a974ce4bdb93686 3.9
146 299546f84e68dbb9bd026f0f3a974ce4bdb93686 3.9
147 ccd436f7db6d5d7b9af89715179b911d031d44f1 3.9.1
147 ccd436f7db6d5d7b9af89715179b911d031d44f1 3.9.1
148 149433e68974eb5c63ccb03f794d8b57339a80c4 3.9.2
148 149433e68974eb5c63ccb03f794d8b57339a80c4 3.9.2
149 438173c415874f6ac653efc1099dec9c9150e90f 4.0-rc
149 438173c415874f6ac653efc1099dec9c9150e90f 4.0-rc
150 eab27446995210c334c3d06f1a659e3b9b5da769 4.0
150 eab27446995210c334c3d06f1a659e3b9b5da769 4.0
151 b3b1ae98f6a0e14c1e1ba806a6c18e193b6dae5c 4.0.1
151 b3b1ae98f6a0e14c1e1ba806a6c18e193b6dae5c 4.0.1
152 e69874dc1f4e142746ff3df91e678a09c6fc208c 4.0.2
152 e69874dc1f4e142746ff3df91e678a09c6fc208c 4.0.2
153 a1dd2c0c479e0550040542e392e87bc91262517e 4.1-rc
153 a1dd2c0c479e0550040542e392e87bc91262517e 4.1-rc
154 e1526da1e6d84e03146151c9b6e6950fe9a83d7d 4.1
154 e1526da1e6d84e03146151c9b6e6950fe9a83d7d 4.1
155 25703b624d27e3917d978af56d6ad59331e0464a 4.1.1
155 25703b624d27e3917d978af56d6ad59331e0464a 4.1.1
156 ed5b25874d998ababb181a939dd37a16ea644435 4.1.2
156 ed5b25874d998ababb181a939dd37a16ea644435 4.1.2
157 77eaf9539499a1b8be259ffe7ada787d07857f80 4.1.3
157 77eaf9539499a1b8be259ffe7ada787d07857f80 4.1.3
158 616e788321cc4ae9975b7f0c54c849f36d82182b 4.2-rc
158 616e788321cc4ae9975b7f0c54c849f36d82182b 4.2-rc
159 bb96d4a497432722623ae60d9bc734a1e360179e 4.2
159 bb96d4a497432722623ae60d9bc734a1e360179e 4.2
160 c850f0ed54c1d42f9aa079ad528f8127e5775217 4.2.1
160 c850f0ed54c1d42f9aa079ad528f8127e5775217 4.2.1
161 26c49ed51a698ec016d2b4c6b44ca3c3f73cc788 4.2.2
161 26c49ed51a698ec016d2b4c6b44ca3c3f73cc788 4.2.2
162 857876ebaed4e315f63157bd157d6ce553c7ab73 4.3-rc
162 857876ebaed4e315f63157bd157d6ce553c7ab73 4.3-rc
163 5544af8622863796a0027566f6b646e10d522c4c 4.3
163 5544af8622863796a0027566f6b646e10d522c4c 4.3
164 943c91326b23954e6e1c6960d0239511f9530258 4.2.3
164 943c91326b23954e6e1c6960d0239511f9530258 4.2.3
165 3fee7f7d2da04226914c2258cc2884dc27384fd7 4.3.1
165 3fee7f7d2da04226914c2258cc2884dc27384fd7 4.3.1
166 920977f72c7b70acfdaf56ab35360584d7845827 4.3.2
166 920977f72c7b70acfdaf56ab35360584d7845827 4.3.2
167 2f427b57bf9019c6dc3750baa539dc22c1be50f6 4.3.3
167 2f427b57bf9019c6dc3750baa539dc22c1be50f6 4.3.3
168 1e2454b60e5936f5e77498cab2648db469504487 4.4-rc
168 1e2454b60e5936f5e77498cab2648db469504487 4.4-rc
169 0ccb43d4cf01d013ae05917ec4f305509f851b2d 4.4
169 0ccb43d4cf01d013ae05917ec4f305509f851b2d 4.4
170 cabc840ffdee8a72f3689fb77dd74d04fdc2bc04 4.4.1
170 cabc840ffdee8a72f3689fb77dd74d04fdc2bc04 4.4.1
171 a92b9f8e11ba330614cdfd6af0e03b15c1ff3797 4.4.2
171 a92b9f8e11ba330614cdfd6af0e03b15c1ff3797 4.4.2
172 27b6df1b5adbdf647cf5c6675b40575e1b197c60 4.5-rc
172 27b6df1b5adbdf647cf5c6675b40575e1b197c60 4.5-rc
173 d334afc585e29577f271c5eda03378736a16ca6b 4.5
173 d334afc585e29577f271c5eda03378736a16ca6b 4.5
174 369aadf7a3264b03c8b09efce715bc41e6ab4a9b 4.5.1
174 369aadf7a3264b03c8b09efce715bc41e6ab4a9b 4.5.1
175 8bba684efde7f45add05f737952093bb2aa07155 4.5.2
175 8bba684efde7f45add05f737952093bb2aa07155 4.5.2
176 7de7bd407251af2bc98e5b809c8598ee95830daf 4.5.3
176 7de7bd407251af2bc98e5b809c8598ee95830daf 4.5.3
177 ed5448edcbfa747b9154099e18630e49024fd47b 4.6rc0
177 ed5448edcbfa747b9154099e18630e49024fd47b 4.6rc0
178 1ec874717d8a93b19e0d50628443e0ee5efab3a9 4.6rc1
178 1ec874717d8a93b19e0d50628443e0ee5efab3a9 4.6rc1
179 6614cac550aea66d19c601e45efd1b7bd08d7c40 4.6
179 6614cac550aea66d19c601e45efd1b7bd08d7c40 4.6
180 9c5ced5276d6e7d54f7c3dadf5247b7ee98ec79c 4.6.1
180 9c5ced5276d6e7d54f7c3dadf5247b7ee98ec79c 4.6.1
181 0b63a6743010dfdbf8a8154186e119949bdaa1cc 4.6.2
181 0b63a6743010dfdbf8a8154186e119949bdaa1cc 4.6.2
182 e90130af47ce8dd53a3109aed9d15876b3e7dee8 4.7rc0
182 e90130af47ce8dd53a3109aed9d15876b3e7dee8 4.7rc0
183 33ac6a72308a215e6086fbced347ec10aa963b0a 4.7
183 33ac6a72308a215e6086fbced347ec10aa963b0a 4.7
184 ede3bf31fe63677fdf5bd8db687977d4e3d792ed 4.7.1
184 ede3bf31fe63677fdf5bd8db687977d4e3d792ed 4.7.1
185 5405cb1a79010ac50c58cd84e6f50c4556bf2a4c 4.7.2
185 5405cb1a79010ac50c58cd84e6f50c4556bf2a4c 4.7.2
186 956ec6f1320df26f3133ec40f3de866ea0695fd7 4.8rc0
186 956ec6f1320df26f3133ec40f3de866ea0695fd7 4.8rc0
187 a91a2837150bdcb27ae76b3646e6c93cd6a15904 4.8
187 a91a2837150bdcb27ae76b3646e6c93cd6a15904 4.8
188 1c8c54cf97256f4468da2eb4dbee24f7f3888e71 4.8.1
188 1c8c54cf97256f4468da2eb4dbee24f7f3888e71 4.8.1
189 197f092b2cd9691e2a55d198f717b231af9be6f9 4.8.2
189 197f092b2cd9691e2a55d198f717b231af9be6f9 4.8.2
190 593718ff5844cad7a27ee3eb5adad89ac8550949 4.9rc0
190 593718ff5844cad7a27ee3eb5adad89ac8550949 4.9rc0
191 83377b4b4ae0e9a6b8e579f7b0a693b8cf5c3b10 4.9
191 83377b4b4ae0e9a6b8e579f7b0a693b8cf5c3b10 4.9
192 4ea21df312ec7159c5b3633096b6ecf68750b0dd 4.9.1
192 4ea21df312ec7159c5b3633096b6ecf68750b0dd 4.9.1
193 4a8d9ed864754837a185a642170cde24392f9abf 5.0rc0
193 4a8d9ed864754837a185a642170cde24392f9abf 5.0rc0
194 07e479ef7c9639be0029f00e6a722b96dcc05fee 5.0
194 07e479ef7c9639be0029f00e6a722b96dcc05fee 5.0
195 c3484ddbdb9621256d597ed86b90d229c59c2af9 5.0.1
195 c3484ddbdb9621256d597ed86b90d229c59c2af9 5.0.1
196 97ada9b8d51bef24c5cb4cdca4243f0db694ab6e 5.0.2
196 97ada9b8d51bef24c5cb4cdca4243f0db694ab6e 5.0.2
197 e386b5f4f8360dbb43a576dd9b1368e386fefa5b 5.1rc0
197 e386b5f4f8360dbb43a576dd9b1368e386fefa5b 5.1rc0
198 e91930d712e8507d1bc1b2dffd96c83edc4cbed3 5.1
198 e91930d712e8507d1bc1b2dffd96c83edc4cbed3 5.1
199 a4e32fd539ab41489a51b2aa88bda9a73b839562 5.1.1
@@ -1,1089 +1,1094 b''
1 # phabricator.py - simple Phabricator integration
1 # phabricator.py - simple Phabricator integration
2 #
2 #
3 # Copyright 2017 Facebook, Inc.
3 # Copyright 2017 Facebook, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 """simple Phabricator integration (EXPERIMENTAL)
7 """simple Phabricator integration (EXPERIMENTAL)
8
8
9 This extension provides a ``phabsend`` command which sends a stack of
9 This extension provides a ``phabsend`` command which sends a stack of
10 changesets to Phabricator, and a ``phabread`` command which prints a stack of
10 changesets to Phabricator, and a ``phabread`` command which prints a stack of
11 revisions in a format suitable for :hg:`import`, and a ``phabupdate`` command
11 revisions in a format suitable for :hg:`import`, and a ``phabupdate`` command
12 to update statuses in batch.
12 to update statuses in batch.
13
13
14 By default, Phabricator requires ``Test Plan`` which might prevent some
14 By default, Phabricator requires ``Test Plan`` which might prevent some
15 changeset from being sent. The requirement could be disabled by changing
15 changeset from being sent. The requirement could be disabled by changing
16 ``differential.require-test-plan-field`` config server side.
16 ``differential.require-test-plan-field`` config server side.
17
17
18 Config::
18 Config::
19
19
20 [phabricator]
20 [phabricator]
21 # Phabricator URL
21 # Phabricator URL
22 url = https://phab.example.com/
22 url = https://phab.example.com/
23
23
24 # Repo callsign. If a repo has a URL https://$HOST/diffusion/FOO, then its
24 # Repo callsign. If a repo has a URL https://$HOST/diffusion/FOO, then its
25 # callsign is "FOO".
25 # callsign is "FOO".
26 callsign = FOO
26 callsign = FOO
27
27
28 # curl command to use. If not set (default), use builtin HTTP library to
28 # curl command to use. If not set (default), use builtin HTTP library to
29 # communicate. If set, use the specified curl command. This could be useful
29 # communicate. If set, use the specified curl command. This could be useful
30 # if you need to specify advanced options that is not easily supported by
30 # if you need to specify advanced options that is not easily supported by
31 # the internal library.
31 # the internal library.
32 curlcmd = curl --connect-timeout 2 --retry 3 --silent
32 curlcmd = curl --connect-timeout 2 --retry 3 --silent
33
33
34 [auth]
34 [auth]
35 example.schemes = https
35 example.schemes = https
36 example.prefix = phab.example.com
36 example.prefix = phab.example.com
37
37
38 # API token. Get it from https://$HOST/conduit/login/
38 # API token. Get it from https://$HOST/conduit/login/
39 example.phabtoken = cli-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
39 example.phabtoken = cli-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
40 """
40 """
41
41
42 from __future__ import absolute_import
42 from __future__ import absolute_import
43
43
44 import contextlib
44 import contextlib
45 import itertools
45 import itertools
46 import json
46 import json
47 import operator
47 import operator
48 import re
48 import re
49
49
50 from mercurial.node import bin, nullid
50 from mercurial.node import bin, nullid
51 from mercurial.i18n import _
51 from mercurial.i18n import _
52 from mercurial import (
52 from mercurial import (
53 cmdutil,
53 cmdutil,
54 context,
54 context,
55 encoding,
55 encoding,
56 error,
56 error,
57 httpconnection as httpconnectionmod,
57 httpconnection as httpconnectionmod,
58 mdiff,
58 mdiff,
59 obsutil,
59 obsutil,
60 parser,
60 parser,
61 patch,
61 patch,
62 phases,
62 phases,
63 pycompat,
63 pycompat,
64 registrar,
64 registrar,
65 scmutil,
65 scmutil,
66 smartset,
66 smartset,
67 tags,
67 tags,
68 templatefilters,
68 templatefilters,
69 templateutil,
69 templateutil,
70 url as urlmod,
70 url as urlmod,
71 util,
71 util,
72 )
72 )
73 from mercurial.utils import (
73 from mercurial.utils import (
74 procutil,
74 procutil,
75 stringutil,
75 stringutil,
76 )
76 )
77
77
78 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
78 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
79 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
79 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
80 # be specifying the version(s) of Mercurial they are tested with, or
80 # be specifying the version(s) of Mercurial they are tested with, or
81 # leave the attribute unspecified.
81 # leave the attribute unspecified.
82 testedwith = 'ships-with-hg-core'
82 testedwith = 'ships-with-hg-core'
83
83
84 cmdtable = {}
84 cmdtable = {}
85 command = registrar.command(cmdtable)
85 command = registrar.command(cmdtable)
86
86
87 configtable = {}
87 configtable = {}
88 configitem = registrar.configitem(configtable)
88 configitem = registrar.configitem(configtable)
89
89
90 # developer config: phabricator.batchsize
90 # developer config: phabricator.batchsize
91 configitem(b'phabricator', b'batchsize',
91 configitem(b'phabricator', b'batchsize',
92 default=12,
92 default=12,
93 )
93 )
94 configitem(b'phabricator', b'callsign',
94 configitem(b'phabricator', b'callsign',
95 default=None,
95 default=None,
96 )
96 )
97 configitem(b'phabricator', b'curlcmd',
97 configitem(b'phabricator', b'curlcmd',
98 default=None,
98 default=None,
99 )
99 )
100 # developer config: phabricator.repophid
100 # developer config: phabricator.repophid
101 configitem(b'phabricator', b'repophid',
101 configitem(b'phabricator', b'repophid',
102 default=None,
102 default=None,
103 )
103 )
104 configitem(b'phabricator', b'url',
104 configitem(b'phabricator', b'url',
105 default=None,
105 default=None,
106 )
106 )
107 configitem(b'phabsend', b'confirm',
107 configitem(b'phabsend', b'confirm',
108 default=False,
108 default=False,
109 )
109 )
110
110
111 colortable = {
111 colortable = {
112 b'phabricator.action.created': b'green',
112 b'phabricator.action.created': b'green',
113 b'phabricator.action.skipped': b'magenta',
113 b'phabricator.action.skipped': b'magenta',
114 b'phabricator.action.updated': b'magenta',
114 b'phabricator.action.updated': b'magenta',
115 b'phabricator.desc': b'',
115 b'phabricator.desc': b'',
116 b'phabricator.drev': b'bold',
116 b'phabricator.drev': b'bold',
117 b'phabricator.node': b'',
117 b'phabricator.node': b'',
118 }
118 }
119
119
120 _VCR_FLAGS = [
120 _VCR_FLAGS = [
121 (b'', b'test-vcr', b'',
121 (b'', b'test-vcr', b'',
122 _(b'Path to a vcr file. If nonexistent, will record a new vcr transcript'
122 _(b'Path to a vcr file. If nonexistent, will record a new vcr transcript'
123 b', otherwise will mock all http requests using the specified vcr file.'
123 b', otherwise will mock all http requests using the specified vcr file.'
124 b' (ADVANCED)'
124 b' (ADVANCED)'
125 )),
125 )),
126 ]
126 ]
127
127
128 def vcrcommand(name, flags, spec, helpcategory=None, optionalrepo=False):
128 def vcrcommand(name, flags, spec, helpcategory=None, optionalrepo=False):
129 fullflags = flags + _VCR_FLAGS
129 fullflags = flags + _VCR_FLAGS
130 def hgmatcher(r1, r2):
130 def hgmatcher(r1, r2):
131 if r1.uri != r2.uri or r1.method != r2.method:
131 if r1.uri != r2.uri or r1.method != r2.method:
132 return False
132 return False
133 r1params = r1.body.split(b'&')
133 r1params = r1.body.split(b'&')
134 r2params = r2.body.split(b'&')
134 r2params = r2.body.split(b'&')
135 return set(r1params) == set(r2params)
135 return set(r1params) == set(r2params)
136
136
137 def sanitiserequest(request):
137 def sanitiserequest(request):
138 request.body = re.sub(
138 request.body = re.sub(
139 r'cli-[a-z0-9]+',
139 r'cli-[a-z0-9]+',
140 r'cli-hahayouwish',
140 r'cli-hahayouwish',
141 request.body
141 request.body
142 )
142 )
143 return request
143 return request
144
144
145 def sanitiseresponse(response):
145 def sanitiseresponse(response):
146 if r'set-cookie' in response[r'headers']:
146 if r'set-cookie' in response[r'headers']:
147 del response[r'headers'][r'set-cookie']
147 del response[r'headers'][r'set-cookie']
148 return response
148 return response
149
149
150 def decorate(fn):
150 def decorate(fn):
151 def inner(*args, **kwargs):
151 def inner(*args, **kwargs):
152 cassette = pycompat.fsdecode(kwargs.pop(r'test_vcr', None))
152 cassette = pycompat.fsdecode(kwargs.pop(r'test_vcr', None))
153 if cassette:
153 if cassette:
154 import hgdemandimport
154 import hgdemandimport
155 with hgdemandimport.deactivated():
155 with hgdemandimport.deactivated():
156 import vcr as vcrmod
156 import vcr as vcrmod
157 import vcr.stubs as stubs
157 import vcr.stubs as stubs
158 vcr = vcrmod.VCR(
158 vcr = vcrmod.VCR(
159 serializer=r'json',
159 serializer=r'json',
160 before_record_request=sanitiserequest,
160 before_record_request=sanitiserequest,
161 before_record_response=sanitiseresponse,
161 before_record_response=sanitiseresponse,
162 custom_patches=[
162 custom_patches=[
163 (urlmod, r'httpconnection',
163 (urlmod, r'httpconnection',
164 stubs.VCRHTTPConnection),
164 stubs.VCRHTTPConnection),
165 (urlmod, r'httpsconnection',
165 (urlmod, r'httpsconnection',
166 stubs.VCRHTTPSConnection),
166 stubs.VCRHTTPSConnection),
167 ])
167 ])
168 vcr.register_matcher(r'hgmatcher', hgmatcher)
168 vcr.register_matcher(r'hgmatcher', hgmatcher)
169 with vcr.use_cassette(cassette, match_on=[r'hgmatcher']):
169 with vcr.use_cassette(cassette, match_on=[r'hgmatcher']):
170 return fn(*args, **kwargs)
170 return fn(*args, **kwargs)
171 return fn(*args, **kwargs)
171 return fn(*args, **kwargs)
172 inner.__name__ = fn.__name__
172 inner.__name__ = fn.__name__
173 inner.__doc__ = fn.__doc__
173 inner.__doc__ = fn.__doc__
174 return command(name, fullflags, spec, helpcategory=helpcategory,
174 return command(name, fullflags, spec, helpcategory=helpcategory,
175 optionalrepo=optionalrepo)(inner)
175 optionalrepo=optionalrepo)(inner)
176 return decorate
176 return decorate
177
177
178 def urlencodenested(params):
178 def urlencodenested(params):
179 """like urlencode, but works with nested parameters.
179 """like urlencode, but works with nested parameters.
180
180
181 For example, if params is {'a': ['b', 'c'], 'd': {'e': 'f'}}, it will be
181 For example, if params is {'a': ['b', 'c'], 'd': {'e': 'f'}}, it will be
182 flattened to {'a[0]': 'b', 'a[1]': 'c', 'd[e]': 'f'} and then passed to
182 flattened to {'a[0]': 'b', 'a[1]': 'c', 'd[e]': 'f'} and then passed to
183 urlencode. Note: the encoding is consistent with PHP's http_build_query.
183 urlencode. Note: the encoding is consistent with PHP's http_build_query.
184 """
184 """
185 flatparams = util.sortdict()
185 flatparams = util.sortdict()
186 def process(prefix, obj):
186 def process(prefix, obj):
187 if isinstance(obj, bool):
187 if isinstance(obj, bool):
188 obj = {True: b'true', False: b'false'}[obj] # Python -> PHP form
188 obj = {True: b'true', False: b'false'}[obj] # Python -> PHP form
189 lister = lambda l: [(b'%d' % k, v) for k, v in enumerate(l)]
189 lister = lambda l: [(b'%d' % k, v) for k, v in enumerate(l)]
190 items = {list: lister, dict: lambda x: x.items()}.get(type(obj))
190 items = {list: lister, dict: lambda x: x.items()}.get(type(obj))
191 if items is None:
191 if items is None:
192 flatparams[prefix] = obj
192 flatparams[prefix] = obj
193 else:
193 else:
194 for k, v in items(obj):
194 for k, v in items(obj):
195 if prefix:
195 if prefix:
196 process(b'%s[%s]' % (prefix, k), v)
196 process(b'%s[%s]' % (prefix, k), v)
197 else:
197 else:
198 process(k, v)
198 process(k, v)
199 process(b'', params)
199 process(b'', params)
200 return util.urlreq.urlencode(flatparams)
200 return util.urlreq.urlencode(flatparams)
201
201
202 def readurltoken(ui):
202 def readurltoken(ui):
203 """return conduit url, token and make sure they exist
203 """return conduit url, token and make sure they exist
204
204
205 Currently read from [auth] config section. In the future, it might
205 Currently read from [auth] config section. In the future, it might
206 make sense to read from .arcconfig and .arcrc as well.
206 make sense to read from .arcconfig and .arcrc as well.
207 """
207 """
208 url = ui.config(b'phabricator', b'url')
208 url = ui.config(b'phabricator', b'url')
209 if not url:
209 if not url:
210 raise error.Abort(_(b'config %s.%s is required')
210 raise error.Abort(_(b'config %s.%s is required')
211 % (b'phabricator', b'url'))
211 % (b'phabricator', b'url'))
212
212
213 res = httpconnectionmod.readauthforuri(ui, url, util.url(url).user)
213 res = httpconnectionmod.readauthforuri(ui, url, util.url(url).user)
214 token = None
214 token = None
215
215
216 if res:
216 if res:
217 group, auth = res
217 group, auth = res
218
218
219 ui.debug(b"using auth.%s.* for authentication\n" % group)
219 ui.debug(b"using auth.%s.* for authentication\n" % group)
220
220
221 token = auth.get(b'phabtoken')
221 token = auth.get(b'phabtoken')
222
222
223 if not token:
223 if not token:
224 raise error.Abort(_(b'Can\'t find conduit token associated to %s')
224 raise error.Abort(_(b'Can\'t find conduit token associated to %s')
225 % (url,))
225 % (url,))
226
226
227 return url, token
227 return url, token
228
228
229 def callconduit(ui, name, params):
229 def callconduit(ui, name, params):
230 """call Conduit API, params is a dict. return json.loads result, or None"""
230 """call Conduit API, params is a dict. return json.loads result, or None"""
231 host, token = readurltoken(ui)
231 host, token = readurltoken(ui)
232 url, authinfo = util.url(b'/'.join([host, b'api', name])).authinfo()
232 url, authinfo = util.url(b'/'.join([host, b'api', name])).authinfo()
233 ui.debug(b'Conduit Call: %s %s\n' % (url, pycompat.byterepr(params)))
233 ui.debug(b'Conduit Call: %s %s\n' % (url, pycompat.byterepr(params)))
234 params = params.copy()
234 params = params.copy()
235 params[b'api.token'] = token
235 params[b'api.token'] = token
236 data = urlencodenested(params)
236 data = urlencodenested(params)
237 curlcmd = ui.config(b'phabricator', b'curlcmd')
237 curlcmd = ui.config(b'phabricator', b'curlcmd')
238 if curlcmd:
238 if curlcmd:
239 sin, sout = procutil.popen2(b'%s -d @- %s'
239 sin, sout = procutil.popen2(b'%s -d @- %s'
240 % (curlcmd, procutil.shellquote(url)))
240 % (curlcmd, procutil.shellquote(url)))
241 sin.write(data)
241 sin.write(data)
242 sin.close()
242 sin.close()
243 body = sout.read()
243 body = sout.read()
244 else:
244 else:
245 urlopener = urlmod.opener(ui, authinfo)
245 urlopener = urlmod.opener(ui, authinfo)
246 request = util.urlreq.request(pycompat.strurl(url), data=data)
246 request = util.urlreq.request(pycompat.strurl(url), data=data)
247 with contextlib.closing(urlopener.open(request)) as rsp:
247 with contextlib.closing(urlopener.open(request)) as rsp:
248 body = rsp.read()
248 body = rsp.read()
249 ui.debug(b'Conduit Response: %s\n' % body)
249 ui.debug(b'Conduit Response: %s\n' % body)
250 parsed = pycompat.rapply(
250 parsed = pycompat.rapply(
251 lambda x: encoding.unitolocal(x) if isinstance(x, pycompat.unicode)
251 lambda x: encoding.unitolocal(x) if isinstance(x, pycompat.unicode)
252 else x,
252 else x,
253 json.loads(body)
253 json.loads(body)
254 )
254 )
255 if parsed.get(b'error_code'):
255 if parsed.get(b'error_code'):
256 msg = (_(b'Conduit Error (%s): %s')
256 msg = (_(b'Conduit Error (%s): %s')
257 % (parsed[b'error_code'], parsed[b'error_info']))
257 % (parsed[b'error_code'], parsed[b'error_info']))
258 raise error.Abort(msg)
258 raise error.Abort(msg)
259 return parsed[b'result']
259 return parsed[b'result']
260
260
261 @vcrcommand(b'debugcallconduit', [], _(b'METHOD'), optionalrepo=True)
261 @vcrcommand(b'debugcallconduit', [], _(b'METHOD'), optionalrepo=True)
262 def debugcallconduit(ui, repo, name):
262 def debugcallconduit(ui, repo, name):
263 """call Conduit API
263 """call Conduit API
264
264
265 Call parameters are read from stdin as a JSON blob. Result will be written
265 Call parameters are read from stdin as a JSON blob. Result will be written
266 to stdout as a JSON blob.
266 to stdout as a JSON blob.
267 """
267 """
268 # json.loads only accepts bytes from 3.6+
268 # json.loads only accepts bytes from 3.6+
269 rawparams = encoding.unifromlocal(ui.fin.read())
269 rawparams = encoding.unifromlocal(ui.fin.read())
270 # json.loads only returns unicode strings
270 # json.loads only returns unicode strings
271 params = pycompat.rapply(lambda x:
271 params = pycompat.rapply(lambda x:
272 encoding.unitolocal(x) if isinstance(x, pycompat.unicode) else x,
272 encoding.unitolocal(x) if isinstance(x, pycompat.unicode) else x,
273 json.loads(rawparams)
273 json.loads(rawparams)
274 )
274 )
275 # json.dumps only accepts unicode strings
275 # json.dumps only accepts unicode strings
276 result = pycompat.rapply(lambda x:
276 result = pycompat.rapply(lambda x:
277 encoding.unifromlocal(x) if isinstance(x, bytes) else x,
277 encoding.unifromlocal(x) if isinstance(x, bytes) else x,
278 callconduit(ui, name, params)
278 callconduit(ui, name, params)
279 )
279 )
280 s = json.dumps(result, sort_keys=True, indent=2, separators=(u',', u': '))
280 s = json.dumps(result, sort_keys=True, indent=2, separators=(u',', u': '))
281 ui.write(b'%s\n' % encoding.unitolocal(s))
281 ui.write(b'%s\n' % encoding.unitolocal(s))
282
282
283 def getrepophid(repo):
283 def getrepophid(repo):
284 """given callsign, return repository PHID or None"""
284 """given callsign, return repository PHID or None"""
285 # developer config: phabricator.repophid
285 # developer config: phabricator.repophid
286 repophid = repo.ui.config(b'phabricator', b'repophid')
286 repophid = repo.ui.config(b'phabricator', b'repophid')
287 if repophid:
287 if repophid:
288 return repophid
288 return repophid
289 callsign = repo.ui.config(b'phabricator', b'callsign')
289 callsign = repo.ui.config(b'phabricator', b'callsign')
290 if not callsign:
290 if not callsign:
291 return None
291 return None
292 query = callconduit(repo.ui, b'diffusion.repository.search',
292 query = callconduit(repo.ui, b'diffusion.repository.search',
293 {b'constraints': {b'callsigns': [callsign]}})
293 {b'constraints': {b'callsigns': [callsign]}})
294 if len(query[b'data']) == 0:
294 if len(query[b'data']) == 0:
295 return None
295 return None
296 repophid = query[b'data'][0][b'phid']
296 repophid = query[b'data'][0][b'phid']
297 repo.ui.setconfig(b'phabricator', b'repophid', repophid)
297 repo.ui.setconfig(b'phabricator', b'repophid', repophid)
298 return repophid
298 return repophid
299
299
300 _differentialrevisiontagre = re.compile(br'\AD([1-9][0-9]*)\Z')
300 _differentialrevisiontagre = re.compile(br'\AD([1-9][0-9]*)\Z')
301 _differentialrevisiondescre = re.compile(
301 _differentialrevisiondescre = re.compile(
302 br'^Differential Revision:\s*(?P<url>(?:.*)D(?P<id>[1-9][0-9]*))$', re.M)
302 br'^Differential Revision:\s*(?P<url>(?:.*)D(?P<id>[1-9][0-9]*))$', re.M)
303
303
304 def getoldnodedrevmap(repo, nodelist):
304 def getoldnodedrevmap(repo, nodelist):
305 """find previous nodes that has been sent to Phabricator
305 """find previous nodes that has been sent to Phabricator
306
306
307 return {node: (oldnode, Differential diff, Differential Revision ID)}
307 return {node: (oldnode, Differential diff, Differential Revision ID)}
308 for node in nodelist with known previous sent versions, or associated
308 for node in nodelist with known previous sent versions, or associated
309 Differential Revision IDs. ``oldnode`` and ``Differential diff`` could
309 Differential Revision IDs. ``oldnode`` and ``Differential diff`` could
310 be ``None``.
310 be ``None``.
311
311
312 Examines commit messages like "Differential Revision:" to get the
312 Examines commit messages like "Differential Revision:" to get the
313 association information.
313 association information.
314
314
315 If such commit message line is not found, examines all precursors and their
315 If such commit message line is not found, examines all precursors and their
316 tags. Tags with format like "D1234" are considered a match and the node
316 tags. Tags with format like "D1234" are considered a match and the node
317 with that tag, and the number after "D" (ex. 1234) will be returned.
317 with that tag, and the number after "D" (ex. 1234) will be returned.
318
318
319 The ``old node``, if not None, is guaranteed to be the last diff of
319 The ``old node``, if not None, is guaranteed to be the last diff of
320 corresponding Differential Revision, and exist in the repo.
320 corresponding Differential Revision, and exist in the repo.
321 """
321 """
322 unfi = repo.unfiltered()
322 unfi = repo.unfiltered()
323 nodemap = unfi.changelog.nodemap
323 nodemap = unfi.changelog.nodemap
324
324
325 result = {} # {node: (oldnode?, lastdiff?, drev)}
325 result = {} # {node: (oldnode?, lastdiff?, drev)}
326 toconfirm = {} # {node: (force, {precnode}, drev)}
326 toconfirm = {} # {node: (force, {precnode}, drev)}
327 for node in nodelist:
327 for node in nodelist:
328 ctx = unfi[node]
328 ctx = unfi[node]
329 # For tags like "D123", put them into "toconfirm" to verify later
329 # For tags like "D123", put them into "toconfirm" to verify later
330 precnodes = list(obsutil.allpredecessors(unfi.obsstore, [node]))
330 precnodes = list(obsutil.allpredecessors(unfi.obsstore, [node]))
331 for n in precnodes:
331 for n in precnodes:
332 if n in nodemap:
332 if n in nodemap:
333 for tag in unfi.nodetags(n):
333 for tag in unfi.nodetags(n):
334 m = _differentialrevisiontagre.match(tag)
334 m = _differentialrevisiontagre.match(tag)
335 if m:
335 if m:
336 toconfirm[node] = (0, set(precnodes), int(m.group(1)))
336 toconfirm[node] = (0, set(precnodes), int(m.group(1)))
337 continue
337 continue
338
338
339 # Check commit message
339 # Check commit message
340 m = _differentialrevisiondescre.search(ctx.description())
340 m = _differentialrevisiondescre.search(ctx.description())
341 if m:
341 if m:
342 toconfirm[node] = (1, set(precnodes), int(m.group(r'id')))
342 toconfirm[node] = (1, set(precnodes), int(m.group(r'id')))
343
343
344 # Double check if tags are genuine by collecting all old nodes from
344 # Double check if tags are genuine by collecting all old nodes from
345 # Phabricator, and expect precursors overlap with it.
345 # Phabricator, and expect precursors overlap with it.
346 if toconfirm:
346 if toconfirm:
347 drevs = [drev for force, precs, drev in toconfirm.values()]
347 drevs = [drev for force, precs, drev in toconfirm.values()]
348 alldiffs = callconduit(unfi.ui, b'differential.querydiffs',
348 alldiffs = callconduit(unfi.ui, b'differential.querydiffs',
349 {b'revisionIDs': drevs})
349 {b'revisionIDs': drevs})
350 getnode = lambda d: bin(
350 getnode = lambda d: bin(
351 getdiffmeta(d).get(b'node', b'')) or None
351 getdiffmeta(d).get(b'node', b'')) or None
352 for newnode, (force, precset, drev) in toconfirm.items():
352 for newnode, (force, precset, drev) in toconfirm.items():
353 diffs = [d for d in alldiffs.values()
353 diffs = [d for d in alldiffs.values()
354 if int(d[b'revisionID']) == drev]
354 if int(d[b'revisionID']) == drev]
355
355
356 # "precursors" as known by Phabricator
356 # "precursors" as known by Phabricator
357 phprecset = set(getnode(d) for d in diffs)
357 phprecset = set(getnode(d) for d in diffs)
358
358
359 # Ignore if precursors (Phabricator and local repo) do not overlap,
359 # Ignore if precursors (Phabricator and local repo) do not overlap,
360 # and force is not set (when commit message says nothing)
360 # and force is not set (when commit message says nothing)
361 if not force and not bool(phprecset & precset):
361 if not force and not bool(phprecset & precset):
362 tagname = b'D%d' % drev
362 tagname = b'D%d' % drev
363 tags.tag(repo, tagname, nullid, message=None, user=None,
363 tags.tag(repo, tagname, nullid, message=None, user=None,
364 date=None, local=True)
364 date=None, local=True)
365 unfi.ui.warn(_(b'D%s: local tag removed - does not match '
365 unfi.ui.warn(_(b'D%s: local tag removed - does not match '
366 b'Differential history\n') % drev)
366 b'Differential history\n') % drev)
367 continue
367 continue
368
368
369 # Find the last node using Phabricator metadata, and make sure it
369 # Find the last node using Phabricator metadata, and make sure it
370 # exists in the repo
370 # exists in the repo
371 oldnode = lastdiff = None
371 oldnode = lastdiff = None
372 if diffs:
372 if diffs:
373 lastdiff = max(diffs, key=lambda d: int(d[b'id']))
373 lastdiff = max(diffs, key=lambda d: int(d[b'id']))
374 oldnode = getnode(lastdiff)
374 oldnode = getnode(lastdiff)
375 if oldnode and oldnode not in nodemap:
375 if oldnode and oldnode not in nodemap:
376 oldnode = None
376 oldnode = None
377
377
378 result[newnode] = (oldnode, lastdiff, drev)
378 result[newnode] = (oldnode, lastdiff, drev)
379
379
380 return result
380 return result
381
381
382 def getdiff(ctx, diffopts):
382 def getdiff(ctx, diffopts):
383 """plain-text diff without header (user, commit message, etc)"""
383 """plain-text diff without header (user, commit message, etc)"""
384 output = util.stringio()
384 output = util.stringio()
385 for chunk, _label in patch.diffui(ctx.repo(), ctx.p1().node(), ctx.node(),
385 for chunk, _label in patch.diffui(ctx.repo(), ctx.p1().node(), ctx.node(),
386 None, opts=diffopts):
386 None, opts=diffopts):
387 output.write(chunk)
387 output.write(chunk)
388 return output.getvalue()
388 return output.getvalue()
389
389
390 def creatediff(ctx):
390 def creatediff(ctx):
391 """create a Differential Diff"""
391 """create a Differential Diff"""
392 repo = ctx.repo()
392 repo = ctx.repo()
393 repophid = getrepophid(repo)
393 repophid = getrepophid(repo)
394 # Create a "Differential Diff" via "differential.createrawdiff" API
394 # Create a "Differential Diff" via "differential.createrawdiff" API
395 params = {b'diff': getdiff(ctx, mdiff.diffopts(git=True, context=32767))}
395 params = {b'diff': getdiff(ctx, mdiff.diffopts(git=True, context=32767))}
396 if repophid:
396 if repophid:
397 params[b'repositoryPHID'] = repophid
397 params[b'repositoryPHID'] = repophid
398 diff = callconduit(repo.ui, b'differential.createrawdiff', params)
398 diff = callconduit(repo.ui, b'differential.createrawdiff', params)
399 if not diff:
399 if not diff:
400 raise error.Abort(_(b'cannot create diff for %s') % ctx)
400 raise error.Abort(_(b'cannot create diff for %s') % ctx)
401 return diff
401 return diff
402
402
403 def writediffproperties(ctx, diff):
403 def writediffproperties(ctx, diff):
404 """write metadata to diff so patches could be applied losslessly"""
404 """write metadata to diff so patches could be applied losslessly"""
405 params = {
405 params = {
406 b'diff_id': diff[b'id'],
406 b'diff_id': diff[b'id'],
407 b'name': b'hg:meta',
407 b'name': b'hg:meta',
408 b'data': templatefilters.json({
408 b'data': templatefilters.json({
409 b'user': ctx.user(),
409 b'user': ctx.user(),
410 b'date': b'%d %d' % ctx.date(),
410 b'date': b'%d %d' % ctx.date(),
411 b'branch': ctx.branch(),
411 b'branch': ctx.branch(),
412 b'node': ctx.hex(),
412 b'node': ctx.hex(),
413 b'parent': ctx.p1().hex(),
413 b'parent': ctx.p1().hex(),
414 }),
414 }),
415 }
415 }
416 callconduit(ctx.repo().ui, b'differential.setdiffproperty', params)
416 callconduit(ctx.repo().ui, b'differential.setdiffproperty', params)
417
417
418 params = {
418 params = {
419 b'diff_id': diff[b'id'],
419 b'diff_id': diff[b'id'],
420 b'name': b'local:commits',
420 b'name': b'local:commits',
421 b'data': templatefilters.json({
421 b'data': templatefilters.json({
422 ctx.hex(): {
422 ctx.hex(): {
423 b'author': stringutil.person(ctx.user()),
423 b'author': stringutil.person(ctx.user()),
424 b'authorEmail': stringutil.email(ctx.user()),
424 b'authorEmail': stringutil.email(ctx.user()),
425 b'time': int(ctx.date()[0]),
425 b'time': int(ctx.date()[0]),
426 b'commit': ctx.hex(),
426 b'commit': ctx.hex(),
427 b'parents': [ctx.p1().hex()],
427 b'parents': [ctx.p1().hex()],
428 b'branch': ctx.branch(),
428 b'branch': ctx.branch(),
429 },
429 },
430 }),
430 }),
431 }
431 }
432 callconduit(ctx.repo().ui, b'differential.setdiffproperty', params)
432 callconduit(ctx.repo().ui, b'differential.setdiffproperty', params)
433
433
434 def createdifferentialrevision(ctx, revid=None, parentrevphid=None,
434 def createdifferentialrevision(ctx, revid=None, parentrevphid=None,
435 oldnode=None, olddiff=None, actions=None,
435 oldnode=None, olddiff=None, actions=None,
436 comment=None):
436 comment=None):
437 """create or update a Differential Revision
437 """create or update a Differential Revision
438
438
439 If revid is None, create a new Differential Revision, otherwise update
439 If revid is None, create a new Differential Revision, otherwise update
440 revid. If parentrevphid is not None, set it as a dependency.
440 revid. If parentrevphid is not None, set it as a dependency.
441
441
442 If oldnode is not None, check if the patch content (without commit message
442 If oldnode is not None, check if the patch content (without commit message
443 and metadata) has changed before creating another diff.
443 and metadata) has changed before creating another diff.
444
444
445 If actions is not None, they will be appended to the transaction.
445 If actions is not None, they will be appended to the transaction.
446 """
446 """
447 repo = ctx.repo()
447 repo = ctx.repo()
448 if oldnode:
448 if oldnode:
449 diffopts = mdiff.diffopts(git=True, context=32767)
449 diffopts = mdiff.diffopts(git=True, context=32767)
450 oldctx = repo.unfiltered()[oldnode]
450 oldctx = repo.unfiltered()[oldnode]
451 neednewdiff = (getdiff(ctx, diffopts) != getdiff(oldctx, diffopts))
451 neednewdiff = (getdiff(ctx, diffopts) != getdiff(oldctx, diffopts))
452 else:
452 else:
453 neednewdiff = True
453 neednewdiff = True
454
454
455 transactions = []
455 transactions = []
456 if neednewdiff:
456 if neednewdiff:
457 diff = creatediff(ctx)
457 diff = creatediff(ctx)
458 transactions.append({b'type': b'update', b'value': diff[b'phid']})
458 transactions.append({b'type': b'update', b'value': diff[b'phid']})
459 if comment:
459 if comment:
460 transactions.append({b'type': b'comment', b'value': comment})
460 transactions.append({b'type': b'comment', b'value': comment})
461 else:
461 else:
462 # Even if we don't need to upload a new diff because the patch content
462 # Even if we don't need to upload a new diff because the patch content
463 # does not change. We might still need to update its metadata so
463 # does not change. We might still need to update its metadata so
464 # pushers could know the correct node metadata.
464 # pushers could know the correct node metadata.
465 assert olddiff
465 assert olddiff
466 diff = olddiff
466 diff = olddiff
467 writediffproperties(ctx, diff)
467 writediffproperties(ctx, diff)
468
468
469 # Set the parent Revision every time, so commit re-ordering is picked-up
469 # Set the parent Revision every time, so commit re-ordering is picked-up
470 if parentrevphid:
470 if parentrevphid:
471 transactions.append({b'type': b'parents.set',
471 transactions.append({b'type': b'parents.set',
472 b'value': [parentrevphid]})
472 b'value': [parentrevphid]})
473
473
474 if actions:
474 if actions:
475 transactions += actions
475 transactions += actions
476
476
477 # Parse commit message and update related fields.
477 # Parse commit message and update related fields.
478 desc = ctx.description()
478 desc = ctx.description()
479 info = callconduit(repo.ui, b'differential.parsecommitmessage',
479 info = callconduit(repo.ui, b'differential.parsecommitmessage',
480 {b'corpus': desc})
480 {b'corpus': desc})
481 for k, v in info[b'fields'].items():
481 for k, v in info[b'fields'].items():
482 if k in [b'title', b'summary', b'testPlan']:
482 if k in [b'title', b'summary', b'testPlan']:
483 transactions.append({b'type': k, b'value': v})
483 transactions.append({b'type': k, b'value': v})
484
484
485 params = {b'transactions': transactions}
485 params = {b'transactions': transactions}
486 if revid is not None:
486 if revid is not None:
487 # Update an existing Differential Revision
487 # Update an existing Differential Revision
488 params[b'objectIdentifier'] = revid
488 params[b'objectIdentifier'] = revid
489
489
490 revision = callconduit(repo.ui, b'differential.revision.edit', params)
490 revision = callconduit(repo.ui, b'differential.revision.edit', params)
491 if not revision:
491 if not revision:
492 raise error.Abort(_(b'cannot create revision for %s') % ctx)
492 raise error.Abort(_(b'cannot create revision for %s') % ctx)
493
493
494 return revision, diff
494 return revision, diff
495
495
496 def userphids(repo, names):
496 def userphids(repo, names):
497 """convert user names to PHIDs"""
497 """convert user names to PHIDs"""
498 names = [name.lower() for name in names]
498 names = [name.lower() for name in names]
499 query = {b'constraints': {b'usernames': names}}
499 query = {b'constraints': {b'usernames': names}}
500 result = callconduit(repo.ui, b'user.search', query)
500 result = callconduit(repo.ui, b'user.search', query)
501 # username not found is not an error of the API. So check if we have missed
501 # username not found is not an error of the API. So check if we have missed
502 # some names here.
502 # some names here.
503 data = result[b'data']
503 data = result[b'data']
504 resolved = set(entry[b'fields'][b'username'].lower() for entry in data)
504 resolved = set(entry[b'fields'][b'username'].lower() for entry in data)
505 unresolved = set(names) - resolved
505 unresolved = set(names) - resolved
506 if unresolved:
506 if unresolved:
507 raise error.Abort(_(b'unknown username: %s')
507 raise error.Abort(_(b'unknown username: %s')
508 % b' '.join(sorted(unresolved)))
508 % b' '.join(sorted(unresolved)))
509 return [entry[b'phid'] for entry in data]
509 return [entry[b'phid'] for entry in data]
510
510
511 @vcrcommand(b'phabsend',
511 @vcrcommand(b'phabsend',
512 [(b'r', b'rev', [], _(b'revisions to send'), _(b'REV')),
512 [(b'r', b'rev', [], _(b'revisions to send'), _(b'REV')),
513 (b'', b'amend', True, _(b'update commit messages')),
513 (b'', b'amend', True, _(b'update commit messages')),
514 (b'', b'reviewer', [], _(b'specify reviewers')),
514 (b'', b'reviewer', [], _(b'specify reviewers')),
515 (b'', b'blocker', [], _(b'specify blocking reviewers')),
515 (b'', b'blocker', [], _(b'specify blocking reviewers')),
516 (b'm', b'comment', b'',
516 (b'm', b'comment', b'',
517 _(b'add a comment to Revisions with new/updated Diffs')),
517 _(b'add a comment to Revisions with new/updated Diffs')),
518 (b'', b'confirm', None, _(b'ask for confirmation before sending'))],
518 (b'', b'confirm', None, _(b'ask for confirmation before sending'))],
519 _(b'REV [OPTIONS]'),
519 _(b'REV [OPTIONS]'),
520 helpcategory=command.CATEGORY_IMPORT_EXPORT)
520 helpcategory=command.CATEGORY_IMPORT_EXPORT)
521 def phabsend(ui, repo, *revs, **opts):
521 def phabsend(ui, repo, *revs, **opts):
522 """upload changesets to Phabricator
522 """upload changesets to Phabricator
523
523
524 If there are multiple revisions specified, they will be send as a stack
524 If there are multiple revisions specified, they will be send as a stack
525 with a linear dependencies relationship using the order specified by the
525 with a linear dependencies relationship using the order specified by the
526 revset.
526 revset.
527
527
528 For the first time uploading changesets, local tags will be created to
528 For the first time uploading changesets, local tags will be created to
529 maintain the association. After the first time, phabsend will check
529 maintain the association. After the first time, phabsend will check
530 obsstore and tags information so it can figure out whether to update an
530 obsstore and tags information so it can figure out whether to update an
531 existing Differential Revision, or create a new one.
531 existing Differential Revision, or create a new one.
532
532
533 If --amend is set, update commit messages so they have the
533 If --amend is set, update commit messages so they have the
534 ``Differential Revision`` URL, remove related tags. This is similar to what
534 ``Differential Revision`` URL, remove related tags. This is similar to what
535 arcanist will do, and is more desired in author-push workflows. Otherwise,
535 arcanist will do, and is more desired in author-push workflows. Otherwise,
536 use local tags to record the ``Differential Revision`` association.
536 use local tags to record the ``Differential Revision`` association.
537
537
538 The --confirm option lets you confirm changesets before sending them. You
538 The --confirm option lets you confirm changesets before sending them. You
539 can also add following to your configuration file to make it default
539 can also add following to your configuration file to make it default
540 behaviour::
540 behaviour::
541
541
542 [phabsend]
542 [phabsend]
543 confirm = true
543 confirm = true
544
544
545 phabsend will check obsstore and the above association to decide whether to
545 phabsend will check obsstore and the above association to decide whether to
546 update an existing Differential Revision, or create a new one.
546 update an existing Differential Revision, or create a new one.
547 """
547 """
548 opts = pycompat.byteskwargs(opts)
548 opts = pycompat.byteskwargs(opts)
549 revs = list(revs) + opts.get(b'rev', [])
549 revs = list(revs) + opts.get(b'rev', [])
550 revs = scmutil.revrange(repo, revs)
550 revs = scmutil.revrange(repo, revs)
551
551
552 if not revs:
552 if not revs:
553 raise error.Abort(_(b'phabsend requires at least one changeset'))
553 raise error.Abort(_(b'phabsend requires at least one changeset'))
554 if opts.get(b'amend'):
554 if opts.get(b'amend'):
555 cmdutil.checkunfinished(repo)
555 cmdutil.checkunfinished(repo)
556
556
557 # {newnode: (oldnode, olddiff, olddrev}
557 # {newnode: (oldnode, olddiff, olddrev}
558 oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
558 oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
559
559
560 confirm = ui.configbool(b'phabsend', b'confirm')
560 confirm = ui.configbool(b'phabsend', b'confirm')
561 confirm |= bool(opts.get(b'confirm'))
561 confirm |= bool(opts.get(b'confirm'))
562 if confirm:
562 if confirm:
563 confirmed = _confirmbeforesend(repo, revs, oldmap)
563 confirmed = _confirmbeforesend(repo, revs, oldmap)
564 if not confirmed:
564 if not confirmed:
565 raise error.Abort(_(b'phabsend cancelled'))
565 raise error.Abort(_(b'phabsend cancelled'))
566
566
567 actions = []
567 actions = []
568 reviewers = opts.get(b'reviewer', [])
568 reviewers = opts.get(b'reviewer', [])
569 blockers = opts.get(b'blocker', [])
569 blockers = opts.get(b'blocker', [])
570 phids = []
570 phids = []
571 if reviewers:
571 if reviewers:
572 phids.extend(userphids(repo, reviewers))
572 phids.extend(userphids(repo, reviewers))
573 if blockers:
573 if blockers:
574 phids.extend(map(
574 phids.extend(map(
575 lambda phid: b'blocking(%s)' % phid, userphids(repo, blockers)
575 lambda phid: b'blocking(%s)' % phid, userphids(repo, blockers)
576 ))
576 ))
577 if phids:
577 if phids:
578 actions.append({b'type': b'reviewers.add', b'value': phids})
578 actions.append({b'type': b'reviewers.add', b'value': phids})
579
579
580 drevids = [] # [int]
580 drevids = [] # [int]
581 diffmap = {} # {newnode: diff}
581 diffmap = {} # {newnode: diff}
582
582
583 # Send patches one by one so we know their Differential Revision PHIDs and
583 # Send patches one by one so we know their Differential Revision PHIDs and
584 # can provide dependency relationship
584 # can provide dependency relationship
585 lastrevphid = None
585 lastrevphid = None
586 for rev in revs:
586 for rev in revs:
587 ui.debug(b'sending rev %d\n' % rev)
587 ui.debug(b'sending rev %d\n' % rev)
588 ctx = repo[rev]
588 ctx = repo[rev]
589
589
590 # Get Differential Revision ID
590 # Get Differential Revision ID
591 oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None))
591 oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None))
592 if oldnode != ctx.node() or opts.get(b'amend'):
592 if oldnode != ctx.node() or opts.get(b'amend'):
593 # Create or update Differential Revision
593 # Create or update Differential Revision
594 revision, diff = createdifferentialrevision(
594 revision, diff = createdifferentialrevision(
595 ctx, revid, lastrevphid, oldnode, olddiff, actions,
595 ctx, revid, lastrevphid, oldnode, olddiff, actions,
596 opts.get(b'comment'))
596 opts.get(b'comment'))
597 diffmap[ctx.node()] = diff
597 diffmap[ctx.node()] = diff
598 newrevid = int(revision[b'object'][b'id'])
598 newrevid = int(revision[b'object'][b'id'])
599 newrevphid = revision[b'object'][b'phid']
599 newrevphid = revision[b'object'][b'phid']
600 if revid:
600 if revid:
601 action = b'updated'
601 action = b'updated'
602 else:
602 else:
603 action = b'created'
603 action = b'created'
604
604
605 # Create a local tag to note the association, if commit message
605 # Create a local tag to note the association, if commit message
606 # does not have it already
606 # does not have it already
607 m = _differentialrevisiondescre.search(ctx.description())
607 m = _differentialrevisiondescre.search(ctx.description())
608 if not m or int(m.group(r'id')) != newrevid:
608 if not m or int(m.group(r'id')) != newrevid:
609 tagname = b'D%d' % newrevid
609 tagname = b'D%d' % newrevid
610 tags.tag(repo, tagname, ctx.node(), message=None, user=None,
610 tags.tag(repo, tagname, ctx.node(), message=None, user=None,
611 date=None, local=True)
611 date=None, local=True)
612 else:
612 else:
613 # Nothing changed. But still set "newrevphid" so the next revision
613 # Nothing changed. But still set "newrevphid" so the next revision
614 # could depend on this one and "newrevid" for the summary line.
614 # could depend on this one and "newrevid" for the summary line.
615 newrevphid = querydrev(repo, str(revid))[0][b'phid']
615 newrevphid = querydrev(repo, str(revid))[0][b'phid']
616 newrevid = revid
616 newrevid = revid
617 action = b'skipped'
617 action = b'skipped'
618
618
619 actiondesc = ui.label(
619 actiondesc = ui.label(
620 {b'created': _(b'created'),
620 {b'created': _(b'created'),
621 b'skipped': _(b'skipped'),
621 b'skipped': _(b'skipped'),
622 b'updated': _(b'updated')}[action],
622 b'updated': _(b'updated')}[action],
623 b'phabricator.action.%s' % action)
623 b'phabricator.action.%s' % action)
624 drevdesc = ui.label(b'D%d' % newrevid, b'phabricator.drev')
624 drevdesc = ui.label(b'D%d' % newrevid, b'phabricator.drev')
625 nodedesc = ui.label(bytes(ctx), b'phabricator.node')
625 nodedesc = ui.label(bytes(ctx), b'phabricator.node')
626 desc = ui.label(ctx.description().split(b'\n')[0], b'phabricator.desc')
626 desc = ui.label(ctx.description().split(b'\n')[0], b'phabricator.desc')
627 ui.write(_(b'%s - %s - %s: %s\n') % (drevdesc, actiondesc, nodedesc,
627 ui.write(_(b'%s - %s - %s: %s\n') % (drevdesc, actiondesc, nodedesc,
628 desc))
628 desc))
629 drevids.append(newrevid)
629 drevids.append(newrevid)
630 lastrevphid = newrevphid
630 lastrevphid = newrevphid
631
631
632 # Update commit messages and remove tags
632 # Update commit messages and remove tags
633 if opts.get(b'amend'):
633 if opts.get(b'amend'):
634 unfi = repo.unfiltered()
634 unfi = repo.unfiltered()
635 drevs = callconduit(ui, b'differential.query', {b'ids': drevids})
635 drevs = callconduit(ui, b'differential.query', {b'ids': drevids})
636 with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'):
636 with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'):
637 wnode = unfi[b'.'].node()
637 wnode = unfi[b'.'].node()
638 mapping = {} # {oldnode: [newnode]}
638 mapping = {} # {oldnode: [newnode]}
639 for i, rev in enumerate(revs):
639 for i, rev in enumerate(revs):
640 old = unfi[rev]
640 old = unfi[rev]
641 drevid = drevids[i]
641 drevid = drevids[i]
642 drev = [d for d in drevs if int(d[b'id']) == drevid][0]
642 drev = [d for d in drevs if int(d[b'id']) == drevid][0]
643 newdesc = getdescfromdrev(drev)
643 newdesc = getdescfromdrev(drev)
644 # Make sure commit message contain "Differential Revision"
644 # Make sure commit message contain "Differential Revision"
645 if old.description() != newdesc:
645 if old.description() != newdesc:
646 if old.phase() == phases.public:
646 if old.phase() == phases.public:
647 ui.warn(_("warning: not updating public commit %s\n")
647 ui.warn(_("warning: not updating public commit %s\n")
648 % scmutil.formatchangeid(old))
648 % scmutil.formatchangeid(old))
649 continue
649 continue
650 parents = [
650 parents = [
651 mapping.get(old.p1().node(), (old.p1(),))[0],
651 mapping.get(old.p1().node(), (old.p1(),))[0],
652 mapping.get(old.p2().node(), (old.p2(),))[0],
652 mapping.get(old.p2().node(), (old.p2(),))[0],
653 ]
653 ]
654 new = context.metadataonlyctx(
654 new = context.metadataonlyctx(
655 repo, old, parents=parents, text=newdesc,
655 repo, old, parents=parents, text=newdesc,
656 user=old.user(), date=old.date(), extra=old.extra())
656 user=old.user(), date=old.date(), extra=old.extra())
657
657
658 newnode = new.commit()
658 newnode = new.commit()
659
659
660 mapping[old.node()] = [newnode]
660 mapping[old.node()] = [newnode]
661 # Update diff property
661 # Update diff property
662 writediffproperties(unfi[newnode], diffmap[old.node()])
662 # If it fails just warn and keep going, otherwise the DREV
663 # associations will be lost
664 try:
665 writediffproperties(unfi[newnode], diffmap[old.node()])
666 except util.urlerr.urlerror:
667 ui.warn(b'Failed to update metadata for D%s\n' % drevid)
663 # Remove local tags since it's no longer necessary
668 # Remove local tags since it's no longer necessary
664 tagname = b'D%d' % drevid
669 tagname = b'D%d' % drevid
665 if tagname in repo.tags():
670 if tagname in repo.tags():
666 tags.tag(repo, tagname, nullid, message=None, user=None,
671 tags.tag(repo, tagname, nullid, message=None, user=None,
667 date=None, local=True)
672 date=None, local=True)
668 scmutil.cleanupnodes(repo, mapping, b'phabsend', fixphase=True)
673 scmutil.cleanupnodes(repo, mapping, b'phabsend', fixphase=True)
669 if wnode in mapping:
674 if wnode in mapping:
670 unfi.setparents(mapping[wnode][0])
675 unfi.setparents(mapping[wnode][0])
671
676
672 # Map from "hg:meta" keys to header understood by "hg import". The order is
677 # Map from "hg:meta" keys to header understood by "hg import". The order is
673 # consistent with "hg export" output.
678 # consistent with "hg export" output.
674 _metanamemap = util.sortdict([(b'user', b'User'), (b'date', b'Date'),
679 _metanamemap = util.sortdict([(b'user', b'User'), (b'date', b'Date'),
675 (b'branch', b'Branch'), (b'node', b'Node ID'),
680 (b'branch', b'Branch'), (b'node', b'Node ID'),
676 (b'parent', b'Parent ')])
681 (b'parent', b'Parent ')])
677
682
678 def _confirmbeforesend(repo, revs, oldmap):
683 def _confirmbeforesend(repo, revs, oldmap):
679 url, token = readurltoken(repo.ui)
684 url, token = readurltoken(repo.ui)
680 ui = repo.ui
685 ui = repo.ui
681 for rev in revs:
686 for rev in revs:
682 ctx = repo[rev]
687 ctx = repo[rev]
683 desc = ctx.description().splitlines()[0]
688 desc = ctx.description().splitlines()[0]
684 oldnode, olddiff, drevid = oldmap.get(ctx.node(), (None, None, None))
689 oldnode, olddiff, drevid = oldmap.get(ctx.node(), (None, None, None))
685 if drevid:
690 if drevid:
686 drevdesc = ui.label(b'D%s' % drevid, b'phabricator.drev')
691 drevdesc = ui.label(b'D%s' % drevid, b'phabricator.drev')
687 else:
692 else:
688 drevdesc = ui.label(_(b'NEW'), b'phabricator.drev')
693 drevdesc = ui.label(_(b'NEW'), b'phabricator.drev')
689
694
690 ui.write(_(b'%s - %s: %s\n')
695 ui.write(_(b'%s - %s: %s\n')
691 % (drevdesc,
696 % (drevdesc,
692 ui.label(bytes(ctx), b'phabricator.node'),
697 ui.label(bytes(ctx), b'phabricator.node'),
693 ui.label(desc, b'phabricator.desc')))
698 ui.label(desc, b'phabricator.desc')))
694
699
695 if ui.promptchoice(_(b'Send the above changes to %s (yn)?'
700 if ui.promptchoice(_(b'Send the above changes to %s (yn)?'
696 b'$$ &Yes $$ &No') % url):
701 b'$$ &Yes $$ &No') % url):
697 return False
702 return False
698
703
699 return True
704 return True
700
705
701 _knownstatusnames = {b'accepted', b'needsreview', b'needsrevision', b'closed',
706 _knownstatusnames = {b'accepted', b'needsreview', b'needsrevision', b'closed',
702 b'abandoned'}
707 b'abandoned'}
703
708
704 def _getstatusname(drev):
709 def _getstatusname(drev):
705 """get normalized status name from a Differential Revision"""
710 """get normalized status name from a Differential Revision"""
706 return drev[b'statusName'].replace(b' ', b'').lower()
711 return drev[b'statusName'].replace(b' ', b'').lower()
707
712
708 # Small language to specify differential revisions. Support symbols: (), :X,
713 # Small language to specify differential revisions. Support symbols: (), :X,
709 # +, and -.
714 # +, and -.
710
715
711 _elements = {
716 _elements = {
712 # token-type: binding-strength, primary, prefix, infix, suffix
717 # token-type: binding-strength, primary, prefix, infix, suffix
713 b'(': (12, None, (b'group', 1, b')'), None, None),
718 b'(': (12, None, (b'group', 1, b')'), None, None),
714 b':': (8, None, (b'ancestors', 8), None, None),
719 b':': (8, None, (b'ancestors', 8), None, None),
715 b'&': (5, None, None, (b'and_', 5), None),
720 b'&': (5, None, None, (b'and_', 5), None),
716 b'+': (4, None, None, (b'add', 4), None),
721 b'+': (4, None, None, (b'add', 4), None),
717 b'-': (4, None, None, (b'sub', 4), None),
722 b'-': (4, None, None, (b'sub', 4), None),
718 b')': (0, None, None, None, None),
723 b')': (0, None, None, None, None),
719 b'symbol': (0, b'symbol', None, None, None),
724 b'symbol': (0, b'symbol', None, None, None),
720 b'end': (0, None, None, None, None),
725 b'end': (0, None, None, None, None),
721 }
726 }
722
727
723 def _tokenize(text):
728 def _tokenize(text):
724 view = memoryview(text) # zero-copy slice
729 view = memoryview(text) # zero-copy slice
725 special = b'():+-& '
730 special = b'():+-& '
726 pos = 0
731 pos = 0
727 length = len(text)
732 length = len(text)
728 while pos < length:
733 while pos < length:
729 symbol = b''.join(itertools.takewhile(lambda ch: ch not in special,
734 symbol = b''.join(itertools.takewhile(lambda ch: ch not in special,
730 pycompat.iterbytestr(view[pos:])))
735 pycompat.iterbytestr(view[pos:])))
731 if symbol:
736 if symbol:
732 yield (b'symbol', symbol, pos)
737 yield (b'symbol', symbol, pos)
733 pos += len(symbol)
738 pos += len(symbol)
734 else: # special char, ignore space
739 else: # special char, ignore space
735 if text[pos] != b' ':
740 if text[pos] != b' ':
736 yield (text[pos], None, pos)
741 yield (text[pos], None, pos)
737 pos += 1
742 pos += 1
738 yield (b'end', None, pos)
743 yield (b'end', None, pos)
739
744
740 def _parse(text):
745 def _parse(text):
741 tree, pos = parser.parser(_elements).parse(_tokenize(text))
746 tree, pos = parser.parser(_elements).parse(_tokenize(text))
742 if pos != len(text):
747 if pos != len(text):
743 raise error.ParseError(b'invalid token', pos)
748 raise error.ParseError(b'invalid token', pos)
744 return tree
749 return tree
745
750
746 def _parsedrev(symbol):
751 def _parsedrev(symbol):
747 """str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None"""
752 """str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None"""
748 if symbol.startswith(b'D') and symbol[1:].isdigit():
753 if symbol.startswith(b'D') and symbol[1:].isdigit():
749 return int(symbol[1:])
754 return int(symbol[1:])
750 if symbol.isdigit():
755 if symbol.isdigit():
751 return int(symbol)
756 return int(symbol)
752
757
753 def _prefetchdrevs(tree):
758 def _prefetchdrevs(tree):
754 """return ({single-drev-id}, {ancestor-drev-id}) to prefetch"""
759 """return ({single-drev-id}, {ancestor-drev-id}) to prefetch"""
755 drevs = set()
760 drevs = set()
756 ancestordrevs = set()
761 ancestordrevs = set()
757 op = tree[0]
762 op = tree[0]
758 if op == b'symbol':
763 if op == b'symbol':
759 r = _parsedrev(tree[1])
764 r = _parsedrev(tree[1])
760 if r:
765 if r:
761 drevs.add(r)
766 drevs.add(r)
762 elif op == b'ancestors':
767 elif op == b'ancestors':
763 r, a = _prefetchdrevs(tree[1])
768 r, a = _prefetchdrevs(tree[1])
764 drevs.update(r)
769 drevs.update(r)
765 ancestordrevs.update(r)
770 ancestordrevs.update(r)
766 ancestordrevs.update(a)
771 ancestordrevs.update(a)
767 else:
772 else:
768 for t in tree[1:]:
773 for t in tree[1:]:
769 r, a = _prefetchdrevs(t)
774 r, a = _prefetchdrevs(t)
770 drevs.update(r)
775 drevs.update(r)
771 ancestordrevs.update(a)
776 ancestordrevs.update(a)
772 return drevs, ancestordrevs
777 return drevs, ancestordrevs
773
778
774 def querydrev(repo, spec):
779 def querydrev(repo, spec):
775 """return a list of "Differential Revision" dicts
780 """return a list of "Differential Revision" dicts
776
781
777 spec is a string using a simple query language, see docstring in phabread
782 spec is a string using a simple query language, see docstring in phabread
778 for details.
783 for details.
779
784
780 A "Differential Revision dict" looks like:
785 A "Differential Revision dict" looks like:
781
786
782 {
787 {
783 "id": "2",
788 "id": "2",
784 "phid": "PHID-DREV-672qvysjcczopag46qty",
789 "phid": "PHID-DREV-672qvysjcczopag46qty",
785 "title": "example",
790 "title": "example",
786 "uri": "https://phab.example.com/D2",
791 "uri": "https://phab.example.com/D2",
787 "dateCreated": "1499181406",
792 "dateCreated": "1499181406",
788 "dateModified": "1499182103",
793 "dateModified": "1499182103",
789 "authorPHID": "PHID-USER-tv3ohwc4v4jeu34otlye",
794 "authorPHID": "PHID-USER-tv3ohwc4v4jeu34otlye",
790 "status": "0",
795 "status": "0",
791 "statusName": "Needs Review",
796 "statusName": "Needs Review",
792 "properties": [],
797 "properties": [],
793 "branch": null,
798 "branch": null,
794 "summary": "",
799 "summary": "",
795 "testPlan": "",
800 "testPlan": "",
796 "lineCount": "2",
801 "lineCount": "2",
797 "activeDiffPHID": "PHID-DIFF-xoqnjkobbm6k4dk6hi72",
802 "activeDiffPHID": "PHID-DIFF-xoqnjkobbm6k4dk6hi72",
798 "diffs": [
803 "diffs": [
799 "3",
804 "3",
800 "4",
805 "4",
801 ],
806 ],
802 "commits": [],
807 "commits": [],
803 "reviewers": [],
808 "reviewers": [],
804 "ccs": [],
809 "ccs": [],
805 "hashes": [],
810 "hashes": [],
806 "auxiliary": {
811 "auxiliary": {
807 "phabricator:projects": [],
812 "phabricator:projects": [],
808 "phabricator:depends-on": [
813 "phabricator:depends-on": [
809 "PHID-DREV-gbapp366kutjebt7agcd"
814 "PHID-DREV-gbapp366kutjebt7agcd"
810 ]
815 ]
811 },
816 },
812 "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv",
817 "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv",
813 "sourcePath": null
818 "sourcePath": null
814 }
819 }
815 """
820 """
816 def fetch(params):
821 def fetch(params):
817 """params -> single drev or None"""
822 """params -> single drev or None"""
818 key = (params.get(b'ids') or params.get(b'phids') or [None])[0]
823 key = (params.get(b'ids') or params.get(b'phids') or [None])[0]
819 if key in prefetched:
824 if key in prefetched:
820 return prefetched[key]
825 return prefetched[key]
821 drevs = callconduit(repo.ui, b'differential.query', params)
826 drevs = callconduit(repo.ui, b'differential.query', params)
822 # Fill prefetched with the result
827 # Fill prefetched with the result
823 for drev in drevs:
828 for drev in drevs:
824 prefetched[drev[b'phid']] = drev
829 prefetched[drev[b'phid']] = drev
825 prefetched[int(drev[b'id'])] = drev
830 prefetched[int(drev[b'id'])] = drev
826 if key not in prefetched:
831 if key not in prefetched:
827 raise error.Abort(_(b'cannot get Differential Revision %r')
832 raise error.Abort(_(b'cannot get Differential Revision %r')
828 % params)
833 % params)
829 return prefetched[key]
834 return prefetched[key]
830
835
831 def getstack(topdrevids):
836 def getstack(topdrevids):
832 """given a top, get a stack from the bottom, [id] -> [id]"""
837 """given a top, get a stack from the bottom, [id] -> [id]"""
833 visited = set()
838 visited = set()
834 result = []
839 result = []
835 queue = [{b'ids': [i]} for i in topdrevids]
840 queue = [{b'ids': [i]} for i in topdrevids]
836 while queue:
841 while queue:
837 params = queue.pop()
842 params = queue.pop()
838 drev = fetch(params)
843 drev = fetch(params)
839 if drev[b'id'] in visited:
844 if drev[b'id'] in visited:
840 continue
845 continue
841 visited.add(drev[b'id'])
846 visited.add(drev[b'id'])
842 result.append(int(drev[b'id']))
847 result.append(int(drev[b'id']))
843 auxiliary = drev.get(b'auxiliary', {})
848 auxiliary = drev.get(b'auxiliary', {})
844 depends = auxiliary.get(b'phabricator:depends-on', [])
849 depends = auxiliary.get(b'phabricator:depends-on', [])
845 for phid in depends:
850 for phid in depends:
846 queue.append({b'phids': [phid]})
851 queue.append({b'phids': [phid]})
847 result.reverse()
852 result.reverse()
848 return smartset.baseset(result)
853 return smartset.baseset(result)
849
854
850 # Initialize prefetch cache
855 # Initialize prefetch cache
851 prefetched = {} # {id or phid: drev}
856 prefetched = {} # {id or phid: drev}
852
857
853 tree = _parse(spec)
858 tree = _parse(spec)
854 drevs, ancestordrevs = _prefetchdrevs(tree)
859 drevs, ancestordrevs = _prefetchdrevs(tree)
855
860
856 # developer config: phabricator.batchsize
861 # developer config: phabricator.batchsize
857 batchsize = repo.ui.configint(b'phabricator', b'batchsize')
862 batchsize = repo.ui.configint(b'phabricator', b'batchsize')
858
863
859 # Prefetch Differential Revisions in batch
864 # Prefetch Differential Revisions in batch
860 tofetch = set(drevs)
865 tofetch = set(drevs)
861 for r in ancestordrevs:
866 for r in ancestordrevs:
862 tofetch.update(range(max(1, r - batchsize), r + 1))
867 tofetch.update(range(max(1, r - batchsize), r + 1))
863 if drevs:
868 if drevs:
864 fetch({b'ids': list(tofetch)})
869 fetch({b'ids': list(tofetch)})
865 validids = sorted(set(getstack(list(ancestordrevs))) | set(drevs))
870 validids = sorted(set(getstack(list(ancestordrevs))) | set(drevs))
866
871
867 # Walk through the tree, return smartsets
872 # Walk through the tree, return smartsets
868 def walk(tree):
873 def walk(tree):
869 op = tree[0]
874 op = tree[0]
870 if op == b'symbol':
875 if op == b'symbol':
871 drev = _parsedrev(tree[1])
876 drev = _parsedrev(tree[1])
872 if drev:
877 if drev:
873 return smartset.baseset([drev])
878 return smartset.baseset([drev])
874 elif tree[1] in _knownstatusnames:
879 elif tree[1] in _knownstatusnames:
875 drevs = [r for r in validids
880 drevs = [r for r in validids
876 if _getstatusname(prefetched[r]) == tree[1]]
881 if _getstatusname(prefetched[r]) == tree[1]]
877 return smartset.baseset(drevs)
882 return smartset.baseset(drevs)
878 else:
883 else:
879 raise error.Abort(_(b'unknown symbol: %s') % tree[1])
884 raise error.Abort(_(b'unknown symbol: %s') % tree[1])
880 elif op in {b'and_', b'add', b'sub'}:
885 elif op in {b'and_', b'add', b'sub'}:
881 assert len(tree) == 3
886 assert len(tree) == 3
882 return getattr(operator, op)(walk(tree[1]), walk(tree[2]))
887 return getattr(operator, op)(walk(tree[1]), walk(tree[2]))
883 elif op == b'group':
888 elif op == b'group':
884 return walk(tree[1])
889 return walk(tree[1])
885 elif op == b'ancestors':
890 elif op == b'ancestors':
886 return getstack(walk(tree[1]))
891 return getstack(walk(tree[1]))
887 else:
892 else:
888 raise error.ProgrammingError(b'illegal tree: %r' % tree)
893 raise error.ProgrammingError(b'illegal tree: %r' % tree)
889
894
890 return [prefetched[r] for r in walk(tree)]
895 return [prefetched[r] for r in walk(tree)]
891
896
892 def getdescfromdrev(drev):
897 def getdescfromdrev(drev):
893 """get description (commit message) from "Differential Revision"
898 """get description (commit message) from "Differential Revision"
894
899
895 This is similar to differential.getcommitmessage API. But we only care
900 This is similar to differential.getcommitmessage API. But we only care
896 about limited fields: title, summary, test plan, and URL.
901 about limited fields: title, summary, test plan, and URL.
897 """
902 """
898 title = drev[b'title']
903 title = drev[b'title']
899 summary = drev[b'summary'].rstrip()
904 summary = drev[b'summary'].rstrip()
900 testplan = drev[b'testPlan'].rstrip()
905 testplan = drev[b'testPlan'].rstrip()
901 if testplan:
906 if testplan:
902 testplan = b'Test Plan:\n%s' % testplan
907 testplan = b'Test Plan:\n%s' % testplan
903 uri = b'Differential Revision: %s' % drev[b'uri']
908 uri = b'Differential Revision: %s' % drev[b'uri']
904 return b'\n\n'.join(filter(None, [title, summary, testplan, uri]))
909 return b'\n\n'.join(filter(None, [title, summary, testplan, uri]))
905
910
906 def getdiffmeta(diff):
911 def getdiffmeta(diff):
907 """get commit metadata (date, node, user, p1) from a diff object
912 """get commit metadata (date, node, user, p1) from a diff object
908
913
909 The metadata could be "hg:meta", sent by phabsend, like:
914 The metadata could be "hg:meta", sent by phabsend, like:
910
915
911 "properties": {
916 "properties": {
912 "hg:meta": {
917 "hg:meta": {
913 "date": "1499571514 25200",
918 "date": "1499571514 25200",
914 "node": "98c08acae292b2faf60a279b4189beb6cff1414d",
919 "node": "98c08acae292b2faf60a279b4189beb6cff1414d",
915 "user": "Foo Bar <foo@example.com>",
920 "user": "Foo Bar <foo@example.com>",
916 "parent": "6d0abad76b30e4724a37ab8721d630394070fe16"
921 "parent": "6d0abad76b30e4724a37ab8721d630394070fe16"
917 }
922 }
918 }
923 }
919
924
920 Or converted from "local:commits", sent by "arc", like:
925 Or converted from "local:commits", sent by "arc", like:
921
926
922 "properties": {
927 "properties": {
923 "local:commits": {
928 "local:commits": {
924 "98c08acae292b2faf60a279b4189beb6cff1414d": {
929 "98c08acae292b2faf60a279b4189beb6cff1414d": {
925 "author": "Foo Bar",
930 "author": "Foo Bar",
926 "time": 1499546314,
931 "time": 1499546314,
927 "branch": "default",
932 "branch": "default",
928 "tag": "",
933 "tag": "",
929 "commit": "98c08acae292b2faf60a279b4189beb6cff1414d",
934 "commit": "98c08acae292b2faf60a279b4189beb6cff1414d",
930 "rev": "98c08acae292b2faf60a279b4189beb6cff1414d",
935 "rev": "98c08acae292b2faf60a279b4189beb6cff1414d",
931 "local": "1000",
936 "local": "1000",
932 "parents": ["6d0abad76b30e4724a37ab8721d630394070fe16"],
937 "parents": ["6d0abad76b30e4724a37ab8721d630394070fe16"],
933 "summary": "...",
938 "summary": "...",
934 "message": "...",
939 "message": "...",
935 "authorEmail": "foo@example.com"
940 "authorEmail": "foo@example.com"
936 }
941 }
937 }
942 }
938 }
943 }
939
944
940 Note: metadata extracted from "local:commits" will lose time zone
945 Note: metadata extracted from "local:commits" will lose time zone
941 information.
946 information.
942 """
947 """
943 props = diff.get(b'properties') or {}
948 props = diff.get(b'properties') or {}
944 meta = props.get(b'hg:meta')
949 meta = props.get(b'hg:meta')
945 if not meta:
950 if not meta:
946 if props.get(b'local:commits'):
951 if props.get(b'local:commits'):
947 commit = sorted(props[b'local:commits'].values())[0]
952 commit = sorted(props[b'local:commits'].values())[0]
948 meta = {}
953 meta = {}
949 if b'author' in commit and b'authorEmail' in commit:
954 if b'author' in commit and b'authorEmail' in commit:
950 meta[b'user'] = b'%s <%s>' % (commit[b'author'],
955 meta[b'user'] = b'%s <%s>' % (commit[b'author'],
951 commit[b'authorEmail'])
956 commit[b'authorEmail'])
952 if b'time' in commit:
957 if b'time' in commit:
953 meta[b'date'] = b'%d 0' % int(commit[b'time'])
958 meta[b'date'] = b'%d 0' % int(commit[b'time'])
954 if b'branch' in commit:
959 if b'branch' in commit:
955 meta[b'branch'] = commit[b'branch']
960 meta[b'branch'] = commit[b'branch']
956 node = commit.get(b'commit', commit.get(b'rev'))
961 node = commit.get(b'commit', commit.get(b'rev'))
957 if node:
962 if node:
958 meta[b'node'] = node
963 meta[b'node'] = node
959 if len(commit.get(b'parents', ())) >= 1:
964 if len(commit.get(b'parents', ())) >= 1:
960 meta[b'parent'] = commit[b'parents'][0]
965 meta[b'parent'] = commit[b'parents'][0]
961 else:
966 else:
962 meta = {}
967 meta = {}
963 if b'date' not in meta and b'dateCreated' in diff:
968 if b'date' not in meta and b'dateCreated' in diff:
964 meta[b'date'] = b'%s 0' % diff[b'dateCreated']
969 meta[b'date'] = b'%s 0' % diff[b'dateCreated']
965 if b'branch' not in meta and diff.get(b'branch'):
970 if b'branch' not in meta and diff.get(b'branch'):
966 meta[b'branch'] = diff[b'branch']
971 meta[b'branch'] = diff[b'branch']
967 if b'parent' not in meta and diff.get(b'sourceControlBaseRevision'):
972 if b'parent' not in meta and diff.get(b'sourceControlBaseRevision'):
968 meta[b'parent'] = diff[b'sourceControlBaseRevision']
973 meta[b'parent'] = diff[b'sourceControlBaseRevision']
969 return meta
974 return meta
970
975
971 def readpatch(repo, drevs, write):
976 def readpatch(repo, drevs, write):
972 """generate plain-text patch readable by 'hg import'
977 """generate plain-text patch readable by 'hg import'
973
978
974 write is usually ui.write. drevs is what "querydrev" returns, results of
979 write is usually ui.write. drevs is what "querydrev" returns, results of
975 "differential.query".
980 "differential.query".
976 """
981 """
977 # Prefetch hg:meta property for all diffs
982 # Prefetch hg:meta property for all diffs
978 diffids = sorted(set(max(int(v) for v in drev[b'diffs']) for drev in drevs))
983 diffids = sorted(set(max(int(v) for v in drev[b'diffs']) for drev in drevs))
979 diffs = callconduit(repo.ui, b'differential.querydiffs', {b'ids': diffids})
984 diffs = callconduit(repo.ui, b'differential.querydiffs', {b'ids': diffids})
980
985
981 # Generate patch for each drev
986 # Generate patch for each drev
982 for drev in drevs:
987 for drev in drevs:
983 repo.ui.note(_(b'reading D%s\n') % drev[b'id'])
988 repo.ui.note(_(b'reading D%s\n') % drev[b'id'])
984
989
985 diffid = max(int(v) for v in drev[b'diffs'])
990 diffid = max(int(v) for v in drev[b'diffs'])
986 body = callconduit(repo.ui, b'differential.getrawdiff',
991 body = callconduit(repo.ui, b'differential.getrawdiff',
987 {b'diffID': diffid})
992 {b'diffID': diffid})
988 desc = getdescfromdrev(drev)
993 desc = getdescfromdrev(drev)
989 header = b'# HG changeset patch\n'
994 header = b'# HG changeset patch\n'
990
995
991 # Try to preserve metadata from hg:meta property. Write hg patch
996 # Try to preserve metadata from hg:meta property. Write hg patch
992 # headers that can be read by the "import" command. See patchheadermap
997 # headers that can be read by the "import" command. See patchheadermap
993 # and extract in mercurial/patch.py for supported headers.
998 # and extract in mercurial/patch.py for supported headers.
994 meta = getdiffmeta(diffs[b'%d' % diffid])
999 meta = getdiffmeta(diffs[b'%d' % diffid])
995 for k in _metanamemap.keys():
1000 for k in _metanamemap.keys():
996 if k in meta:
1001 if k in meta:
997 header += b'# %s %s\n' % (_metanamemap[k], meta[k])
1002 header += b'# %s %s\n' % (_metanamemap[k], meta[k])
998
1003
999 content = b'%s%s\n%s' % (header, desc, body)
1004 content = b'%s%s\n%s' % (header, desc, body)
1000 write(content)
1005 write(content)
1001
1006
1002 @vcrcommand(b'phabread',
1007 @vcrcommand(b'phabread',
1003 [(b'', b'stack', False, _(b'read dependencies'))],
1008 [(b'', b'stack', False, _(b'read dependencies'))],
1004 _(b'DREVSPEC [OPTIONS]'),
1009 _(b'DREVSPEC [OPTIONS]'),
1005 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1010 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1006 def phabread(ui, repo, spec, **opts):
1011 def phabread(ui, repo, spec, **opts):
1007 """print patches from Phabricator suitable for importing
1012 """print patches from Phabricator suitable for importing
1008
1013
1009 DREVSPEC could be a Differential Revision identity, like ``D123``, or just
1014 DREVSPEC could be a Differential Revision identity, like ``D123``, or just
1010 the number ``123``. It could also have common operators like ``+``, ``-``,
1015 the number ``123``. It could also have common operators like ``+``, ``-``,
1011 ``&``, ``(``, ``)`` for complex queries. Prefix ``:`` could be used to
1016 ``&``, ``(``, ``)`` for complex queries. Prefix ``:`` could be used to
1012 select a stack.
1017 select a stack.
1013
1018
1014 ``abandoned``, ``accepted``, ``closed``, ``needsreview``, ``needsrevision``
1019 ``abandoned``, ``accepted``, ``closed``, ``needsreview``, ``needsrevision``
1015 could be used to filter patches by status. For performance reason, they
1020 could be used to filter patches by status. For performance reason, they
1016 only represent a subset of non-status selections and cannot be used alone.
1021 only represent a subset of non-status selections and cannot be used alone.
1017
1022
1018 For example, ``:D6+8-(2+D4)`` selects a stack up to D6, plus D8 and exclude
1023 For example, ``:D6+8-(2+D4)`` selects a stack up to D6, plus D8 and exclude
1019 D2 and D4. ``:D9 & needsreview`` selects "Needs Review" revisions in a
1024 D2 and D4. ``:D9 & needsreview`` selects "Needs Review" revisions in a
1020 stack up to D9.
1025 stack up to D9.
1021
1026
1022 If --stack is given, follow dependencies information and read all patches.
1027 If --stack is given, follow dependencies information and read all patches.
1023 It is equivalent to the ``:`` operator.
1028 It is equivalent to the ``:`` operator.
1024 """
1029 """
1025 opts = pycompat.byteskwargs(opts)
1030 opts = pycompat.byteskwargs(opts)
1026 if opts.get(b'stack'):
1031 if opts.get(b'stack'):
1027 spec = b':(%s)' % spec
1032 spec = b':(%s)' % spec
1028 drevs = querydrev(repo, spec)
1033 drevs = querydrev(repo, spec)
1029 readpatch(repo, drevs, ui.write)
1034 readpatch(repo, drevs, ui.write)
1030
1035
1031 @vcrcommand(b'phabupdate',
1036 @vcrcommand(b'phabupdate',
1032 [(b'', b'accept', False, _(b'accept revisions')),
1037 [(b'', b'accept', False, _(b'accept revisions')),
1033 (b'', b'reject', False, _(b'reject revisions')),
1038 (b'', b'reject', False, _(b'reject revisions')),
1034 (b'', b'abandon', False, _(b'abandon revisions')),
1039 (b'', b'abandon', False, _(b'abandon revisions')),
1035 (b'', b'reclaim', False, _(b'reclaim revisions')),
1040 (b'', b'reclaim', False, _(b'reclaim revisions')),
1036 (b'm', b'comment', b'', _(b'comment on the last revision')),
1041 (b'm', b'comment', b'', _(b'comment on the last revision')),
1037 ], _(b'DREVSPEC [OPTIONS]'),
1042 ], _(b'DREVSPEC [OPTIONS]'),
1038 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1043 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1039 def phabupdate(ui, repo, spec, **opts):
1044 def phabupdate(ui, repo, spec, **opts):
1040 """update Differential Revision in batch
1045 """update Differential Revision in batch
1041
1046
1042 DREVSPEC selects revisions. See :hg:`help phabread` for its usage.
1047 DREVSPEC selects revisions. See :hg:`help phabread` for its usage.
1043 """
1048 """
1044 opts = pycompat.byteskwargs(opts)
1049 opts = pycompat.byteskwargs(opts)
1045 flags = [n for n in b'accept reject abandon reclaim'.split() if opts.get(n)]
1050 flags = [n for n in b'accept reject abandon reclaim'.split() if opts.get(n)]
1046 if len(flags) > 1:
1051 if len(flags) > 1:
1047 raise error.Abort(_(b'%s cannot be used together') % b', '.join(flags))
1052 raise error.Abort(_(b'%s cannot be used together') % b', '.join(flags))
1048
1053
1049 actions = []
1054 actions = []
1050 for f in flags:
1055 for f in flags:
1051 actions.append({b'type': f, b'value': b'true'})
1056 actions.append({b'type': f, b'value': b'true'})
1052
1057
1053 drevs = querydrev(repo, spec)
1058 drevs = querydrev(repo, spec)
1054 for i, drev in enumerate(drevs):
1059 for i, drev in enumerate(drevs):
1055 if i + 1 == len(drevs) and opts.get(b'comment'):
1060 if i + 1 == len(drevs) and opts.get(b'comment'):
1056 actions.append({b'type': b'comment', b'value': opts[b'comment']})
1061 actions.append({b'type': b'comment', b'value': opts[b'comment']})
1057 if actions:
1062 if actions:
1058 params = {b'objectIdentifier': drev[b'phid'],
1063 params = {b'objectIdentifier': drev[b'phid'],
1059 b'transactions': actions}
1064 b'transactions': actions}
1060 callconduit(ui, b'differential.revision.edit', params)
1065 callconduit(ui, b'differential.revision.edit', params)
1061
1066
1062 templatekeyword = registrar.templatekeyword()
1067 templatekeyword = registrar.templatekeyword()
1063
1068
1064 @templatekeyword(b'phabreview', requires={b'ctx'})
1069 @templatekeyword(b'phabreview', requires={b'ctx'})
1065 def template_review(context, mapping):
1070 def template_review(context, mapping):
1066 """:phabreview: Object describing the review for this changeset.
1071 """:phabreview: Object describing the review for this changeset.
1067 Has attributes `url` and `id`.
1072 Has attributes `url` and `id`.
1068 """
1073 """
1069 ctx = context.resource(mapping, b'ctx')
1074 ctx = context.resource(mapping, b'ctx')
1070 m = _differentialrevisiondescre.search(ctx.description())
1075 m = _differentialrevisiondescre.search(ctx.description())
1071 if m:
1076 if m:
1072 return templateutil.hybriddict({
1077 return templateutil.hybriddict({
1073 b'url': m.group(r'url'),
1078 b'url': m.group(r'url'),
1074 b'id': b"D%s" % m.group(r'id'),
1079 b'id': b"D%s" % m.group(r'id'),
1075 })
1080 })
1076 else:
1081 else:
1077 tags = ctx.repo().nodetags(ctx.node())
1082 tags = ctx.repo().nodetags(ctx.node())
1078 for t in tags:
1083 for t in tags:
1079 if _differentialrevisiontagre.match(t):
1084 if _differentialrevisiontagre.match(t):
1080 url = ctx.repo().ui.config(b'phabricator', b'url')
1085 url = ctx.repo().ui.config(b'phabricator', b'url')
1081 if not url.endswith(b'/'):
1086 if not url.endswith(b'/'):
1082 url += b'/'
1087 url += b'/'
1083 url += t
1088 url += t
1084
1089
1085 return templateutil.hybriddict({
1090 return templateutil.hybriddict({
1086 b'url': url,
1091 b'url': url,
1087 b'id': t,
1092 b'id': t,
1088 })
1093 })
1089 return None
1094 return None
@@ -1,2335 +1,2341 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import hashlib
11 import hashlib
12 import shutil
12 import shutil
13 import stat
13 import stat
14 import struct
14 import struct
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 addednodeid,
18 addednodeid,
19 bin,
19 bin,
20 hex,
20 hex,
21 modifiednodeid,
21 modifiednodeid,
22 nullhex,
22 nullhex,
23 nullid,
23 nullid,
24 nullrev,
24 nullrev,
25 )
25 )
26 from .thirdparty import (
26 from .thirdparty import (
27 attr,
27 attr,
28 )
28 )
29 from . import (
29 from . import (
30 copies,
30 copies,
31 encoding,
31 encoding,
32 error,
32 error,
33 filemerge,
33 filemerge,
34 match as matchmod,
34 match as matchmod,
35 obsutil,
35 obsutil,
36 pycompat,
36 pycompat,
37 scmutil,
37 scmutil,
38 subrepoutil,
38 subrepoutil,
39 util,
39 util,
40 worker,
40 worker,
41 )
41 )
42
42
43 _pack = struct.pack
43 _pack = struct.pack
44 _unpack = struct.unpack
44 _unpack = struct.unpack
45
45
46 def _droponode(data):
46 def _droponode(data):
47 # used for compatibility for v1
47 # used for compatibility for v1
48 bits = data.split('\0')
48 bits = data.split('\0')
49 bits = bits[:-2] + bits[-1:]
49 bits = bits[:-2] + bits[-1:]
50 return '\0'.join(bits)
50 return '\0'.join(bits)
51
51
52 # Merge state record types. See ``mergestate`` docs for more.
52 # Merge state record types. See ``mergestate`` docs for more.
53 RECORD_LOCAL = b'L'
53 RECORD_LOCAL = b'L'
54 RECORD_OTHER = b'O'
54 RECORD_OTHER = b'O'
55 RECORD_MERGED = b'F'
55 RECORD_MERGED = b'F'
56 RECORD_CHANGEDELETE_CONFLICT = b'C'
56 RECORD_CHANGEDELETE_CONFLICT = b'C'
57 RECORD_MERGE_DRIVER_MERGE = b'D'
57 RECORD_MERGE_DRIVER_MERGE = b'D'
58 RECORD_PATH_CONFLICT = b'P'
58 RECORD_PATH_CONFLICT = b'P'
59 RECORD_MERGE_DRIVER_STATE = b'm'
59 RECORD_MERGE_DRIVER_STATE = b'm'
60 RECORD_FILE_VALUES = b'f'
60 RECORD_FILE_VALUES = b'f'
61 RECORD_LABELS = b'l'
61 RECORD_LABELS = b'l'
62 RECORD_OVERRIDE = b't'
62 RECORD_OVERRIDE = b't'
63 RECORD_UNSUPPORTED_MANDATORY = b'X'
63 RECORD_UNSUPPORTED_MANDATORY = b'X'
64 RECORD_UNSUPPORTED_ADVISORY = b'x'
64 RECORD_UNSUPPORTED_ADVISORY = b'x'
65
65
66 MERGE_DRIVER_STATE_UNMARKED = b'u'
66 MERGE_DRIVER_STATE_UNMARKED = b'u'
67 MERGE_DRIVER_STATE_MARKED = b'm'
67 MERGE_DRIVER_STATE_MARKED = b'm'
68 MERGE_DRIVER_STATE_SUCCESS = b's'
68 MERGE_DRIVER_STATE_SUCCESS = b's'
69
69
70 MERGE_RECORD_UNRESOLVED = b'u'
70 MERGE_RECORD_UNRESOLVED = b'u'
71 MERGE_RECORD_RESOLVED = b'r'
71 MERGE_RECORD_RESOLVED = b'r'
72 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
72 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
73 MERGE_RECORD_RESOLVED_PATH = b'pr'
73 MERGE_RECORD_RESOLVED_PATH = b'pr'
74 MERGE_RECORD_DRIVER_RESOLVED = b'd'
74 MERGE_RECORD_DRIVER_RESOLVED = b'd'
75
75
76 ACTION_FORGET = b'f'
76 ACTION_FORGET = b'f'
77 ACTION_REMOVE = b'r'
77 ACTION_REMOVE = b'r'
78 ACTION_ADD = b'a'
78 ACTION_ADD = b'a'
79 ACTION_GET = b'g'
79 ACTION_GET = b'g'
80 ACTION_PATH_CONFLICT = b'p'
80 ACTION_PATH_CONFLICT = b'p'
81 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
81 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
82 ACTION_ADD_MODIFIED = b'am'
82 ACTION_ADD_MODIFIED = b'am'
83 ACTION_CREATED = b'c'
83 ACTION_CREATED = b'c'
84 ACTION_DELETED_CHANGED = b'dc'
84 ACTION_DELETED_CHANGED = b'dc'
85 ACTION_CHANGED_DELETED = b'cd'
85 ACTION_CHANGED_DELETED = b'cd'
86 ACTION_MERGE = b'm'
86 ACTION_MERGE = b'm'
87 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
87 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
88 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
88 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
89 ACTION_KEEP = b'k'
89 ACTION_KEEP = b'k'
90 ACTION_EXEC = b'e'
90 ACTION_EXEC = b'e'
91 ACTION_CREATED_MERGE = b'cm'
91 ACTION_CREATED_MERGE = b'cm'
92
92
93 class mergestate(object):
93 class mergestate(object):
94 '''track 3-way merge state of individual files
94 '''track 3-way merge state of individual files
95
95
96 The merge state is stored on disk when needed. Two files are used: one with
96 The merge state is stored on disk when needed. Two files are used: one with
97 an old format (version 1), and one with a new format (version 2). Version 2
97 an old format (version 1), and one with a new format (version 2). Version 2
98 stores a superset of the data in version 1, including new kinds of records
98 stores a superset of the data in version 1, including new kinds of records
99 in the future. For more about the new format, see the documentation for
99 in the future. For more about the new format, see the documentation for
100 `_readrecordsv2`.
100 `_readrecordsv2`.
101
101
102 Each record can contain arbitrary content, and has an associated type. This
102 Each record can contain arbitrary content, and has an associated type. This
103 `type` should be a letter. If `type` is uppercase, the record is mandatory:
103 `type` should be a letter. If `type` is uppercase, the record is mandatory:
104 versions of Mercurial that don't support it should abort. If `type` is
104 versions of Mercurial that don't support it should abort. If `type` is
105 lowercase, the record can be safely ignored.
105 lowercase, the record can be safely ignored.
106
106
107 Currently known records:
107 Currently known records:
108
108
109 L: the node of the "local" part of the merge (hexified version)
109 L: the node of the "local" part of the merge (hexified version)
110 O: the node of the "other" part of the merge (hexified version)
110 O: the node of the "other" part of the merge (hexified version)
111 F: a file to be merged entry
111 F: a file to be merged entry
112 C: a change/delete or delete/change conflict
112 C: a change/delete or delete/change conflict
113 D: a file that the external merge driver will merge internally
113 D: a file that the external merge driver will merge internally
114 (experimental)
114 (experimental)
115 P: a path conflict (file vs directory)
115 P: a path conflict (file vs directory)
116 m: the external merge driver defined for this merge plus its run state
116 m: the external merge driver defined for this merge plus its run state
117 (experimental)
117 (experimental)
118 f: a (filename, dictionary) tuple of optional values for a given file
118 f: a (filename, dictionary) tuple of optional values for a given file
119 X: unsupported mandatory record type (used in tests)
119 X: unsupported mandatory record type (used in tests)
120 x: unsupported advisory record type (used in tests)
120 x: unsupported advisory record type (used in tests)
121 l: the labels for the parts of the merge.
121 l: the labels for the parts of the merge.
122
122
123 Merge driver run states (experimental):
123 Merge driver run states (experimental):
124 u: driver-resolved files unmarked -- needs to be run next time we're about
124 u: driver-resolved files unmarked -- needs to be run next time we're about
125 to resolve or commit
125 to resolve or commit
126 m: driver-resolved files marked -- only needs to be run before commit
126 m: driver-resolved files marked -- only needs to be run before commit
127 s: success/skipped -- does not need to be run any more
127 s: success/skipped -- does not need to be run any more
128
128
129 Merge record states (stored in self._state, indexed by filename):
129 Merge record states (stored in self._state, indexed by filename):
130 u: unresolved conflict
130 u: unresolved conflict
131 r: resolved conflict
131 r: resolved conflict
132 pu: unresolved path conflict (file conflicts with directory)
132 pu: unresolved path conflict (file conflicts with directory)
133 pr: resolved path conflict
133 pr: resolved path conflict
134 d: driver-resolved conflict
134 d: driver-resolved conflict
135
135
136 The resolve command transitions between 'u' and 'r' for conflicts and
136 The resolve command transitions between 'u' and 'r' for conflicts and
137 'pu' and 'pr' for path conflicts.
137 'pu' and 'pr' for path conflicts.
138 '''
138 '''
139 statepathv1 = 'merge/state'
139 statepathv1 = 'merge/state'
140 statepathv2 = 'merge/state2'
140 statepathv2 = 'merge/state2'
141
141
142 @staticmethod
142 @staticmethod
143 def clean(repo, node=None, other=None, labels=None):
143 def clean(repo, node=None, other=None, labels=None):
144 """Initialize a brand new merge state, removing any existing state on
144 """Initialize a brand new merge state, removing any existing state on
145 disk."""
145 disk."""
146 ms = mergestate(repo)
146 ms = mergestate(repo)
147 ms.reset(node, other, labels)
147 ms.reset(node, other, labels)
148 return ms
148 return ms
149
149
150 @staticmethod
150 @staticmethod
151 def read(repo):
151 def read(repo):
152 """Initialize the merge state, reading it from disk."""
152 """Initialize the merge state, reading it from disk."""
153 ms = mergestate(repo)
153 ms = mergestate(repo)
154 ms._read()
154 ms._read()
155 return ms
155 return ms
156
156
157 def __init__(self, repo):
157 def __init__(self, repo):
158 """Initialize the merge state.
158 """Initialize the merge state.
159
159
160 Do not use this directly! Instead call read() or clean()."""
160 Do not use this directly! Instead call read() or clean()."""
161 self._repo = repo
161 self._repo = repo
162 self._dirty = False
162 self._dirty = False
163 self._labels = None
163 self._labels = None
164
164
165 def reset(self, node=None, other=None, labels=None):
165 def reset(self, node=None, other=None, labels=None):
166 self._state = {}
166 self._state = {}
167 self._stateextras = {}
167 self._stateextras = {}
168 self._local = None
168 self._local = None
169 self._other = None
169 self._other = None
170 self._labels = labels
170 self._labels = labels
171 for var in ('localctx', 'otherctx'):
171 for var in ('localctx', 'otherctx'):
172 if var in vars(self):
172 if var in vars(self):
173 delattr(self, var)
173 delattr(self, var)
174 if node:
174 if node:
175 self._local = node
175 self._local = node
176 self._other = other
176 self._other = other
177 self._readmergedriver = None
177 self._readmergedriver = None
178 if self.mergedriver:
178 if self.mergedriver:
179 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
179 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
180 else:
180 else:
181 self._mdstate = MERGE_DRIVER_STATE_UNMARKED
181 self._mdstate = MERGE_DRIVER_STATE_UNMARKED
182 shutil.rmtree(self._repo.vfs.join('merge'), True)
182 shutil.rmtree(self._repo.vfs.join('merge'), True)
183 self._results = {}
183 self._results = {}
184 self._dirty = False
184 self._dirty = False
185
185
186 def _read(self):
186 def _read(self):
187 """Analyse each record content to restore a serialized state from disk
187 """Analyse each record content to restore a serialized state from disk
188
188
189 This function process "record" entry produced by the de-serialization
189 This function process "record" entry produced by the de-serialization
190 of on disk file.
190 of on disk file.
191 """
191 """
192 self._state = {}
192 self._state = {}
193 self._stateextras = {}
193 self._stateextras = {}
194 self._local = None
194 self._local = None
195 self._other = None
195 self._other = None
196 for var in ('localctx', 'otherctx'):
196 for var in ('localctx', 'otherctx'):
197 if var in vars(self):
197 if var in vars(self):
198 delattr(self, var)
198 delattr(self, var)
199 self._readmergedriver = None
199 self._readmergedriver = None
200 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
200 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
201 unsupported = set()
201 unsupported = set()
202 records = self._readrecords()
202 records = self._readrecords()
203 for rtype, record in records:
203 for rtype, record in records:
204 if rtype == RECORD_LOCAL:
204 if rtype == RECORD_LOCAL:
205 self._local = bin(record)
205 self._local = bin(record)
206 elif rtype == RECORD_OTHER:
206 elif rtype == RECORD_OTHER:
207 self._other = bin(record)
207 self._other = bin(record)
208 elif rtype == RECORD_MERGE_DRIVER_STATE:
208 elif rtype == RECORD_MERGE_DRIVER_STATE:
209 bits = record.split('\0', 1)
209 bits = record.split('\0', 1)
210 mdstate = bits[1]
210 mdstate = bits[1]
211 if len(mdstate) != 1 or mdstate not in (
211 if len(mdstate) != 1 or mdstate not in (
212 MERGE_DRIVER_STATE_UNMARKED, MERGE_DRIVER_STATE_MARKED,
212 MERGE_DRIVER_STATE_UNMARKED, MERGE_DRIVER_STATE_MARKED,
213 MERGE_DRIVER_STATE_SUCCESS):
213 MERGE_DRIVER_STATE_SUCCESS):
214 # the merge driver should be idempotent, so just rerun it
214 # the merge driver should be idempotent, so just rerun it
215 mdstate = MERGE_DRIVER_STATE_UNMARKED
215 mdstate = MERGE_DRIVER_STATE_UNMARKED
216
216
217 self._readmergedriver = bits[0]
217 self._readmergedriver = bits[0]
218 self._mdstate = mdstate
218 self._mdstate = mdstate
219 elif rtype in (RECORD_MERGED, RECORD_CHANGEDELETE_CONFLICT,
219 elif rtype in (RECORD_MERGED, RECORD_CHANGEDELETE_CONFLICT,
220 RECORD_PATH_CONFLICT, RECORD_MERGE_DRIVER_MERGE):
220 RECORD_PATH_CONFLICT, RECORD_MERGE_DRIVER_MERGE):
221 bits = record.split('\0')
221 bits = record.split('\0')
222 self._state[bits[0]] = bits[1:]
222 self._state[bits[0]] = bits[1:]
223 elif rtype == RECORD_FILE_VALUES:
223 elif rtype == RECORD_FILE_VALUES:
224 filename, rawextras = record.split('\0', 1)
224 filename, rawextras = record.split('\0', 1)
225 extraparts = rawextras.split('\0')
225 extraparts = rawextras.split('\0')
226 extras = {}
226 extras = {}
227 i = 0
227 i = 0
228 while i < len(extraparts):
228 while i < len(extraparts):
229 extras[extraparts[i]] = extraparts[i + 1]
229 extras[extraparts[i]] = extraparts[i + 1]
230 i += 2
230 i += 2
231
231
232 self._stateextras[filename] = extras
232 self._stateextras[filename] = extras
233 elif rtype == RECORD_LABELS:
233 elif rtype == RECORD_LABELS:
234 labels = record.split('\0', 2)
234 labels = record.split('\0', 2)
235 self._labels = [l for l in labels if len(l) > 0]
235 self._labels = [l for l in labels if len(l) > 0]
236 elif not rtype.islower():
236 elif not rtype.islower():
237 unsupported.add(rtype)
237 unsupported.add(rtype)
238 self._results = {}
238 self._results = {}
239 self._dirty = False
239 self._dirty = False
240
240
241 if unsupported:
241 if unsupported:
242 raise error.UnsupportedMergeRecords(unsupported)
242 raise error.UnsupportedMergeRecords(unsupported)
243
243
244 def _readrecords(self):
244 def _readrecords(self):
245 """Read merge state from disk and return a list of record (TYPE, data)
245 """Read merge state from disk and return a list of record (TYPE, data)
246
246
247 We read data from both v1 and v2 files and decide which one to use.
247 We read data from both v1 and v2 files and decide which one to use.
248
248
249 V1 has been used by version prior to 2.9.1 and contains less data than
249 V1 has been used by version prior to 2.9.1 and contains less data than
250 v2. We read both versions and check if no data in v2 contradicts
250 v2. We read both versions and check if no data in v2 contradicts
251 v1. If there is not contradiction we can safely assume that both v1
251 v1. If there is not contradiction we can safely assume that both v1
252 and v2 were written at the same time and use the extract data in v2. If
252 and v2 were written at the same time and use the extract data in v2. If
253 there is contradiction we ignore v2 content as we assume an old version
253 there is contradiction we ignore v2 content as we assume an old version
254 of Mercurial has overwritten the mergestate file and left an old v2
254 of Mercurial has overwritten the mergestate file and left an old v2
255 file around.
255 file around.
256
256
257 returns list of record [(TYPE, data), ...]"""
257 returns list of record [(TYPE, data), ...]"""
258 v1records = self._readrecordsv1()
258 v1records = self._readrecordsv1()
259 v2records = self._readrecordsv2()
259 v2records = self._readrecordsv2()
260 if self._v1v2match(v1records, v2records):
260 if self._v1v2match(v1records, v2records):
261 return v2records
261 return v2records
262 else:
262 else:
263 # v1 file is newer than v2 file, use it
263 # v1 file is newer than v2 file, use it
264 # we have to infer the "other" changeset of the merge
264 # we have to infer the "other" changeset of the merge
265 # we cannot do better than that with v1 of the format
265 # we cannot do better than that with v1 of the format
266 mctx = self._repo[None].parents()[-1]
266 mctx = self._repo[None].parents()[-1]
267 v1records.append((RECORD_OTHER, mctx.hex()))
267 v1records.append((RECORD_OTHER, mctx.hex()))
268 # add place holder "other" file node information
268 # add place holder "other" file node information
269 # nobody is using it yet so we do no need to fetch the data
269 # nobody is using it yet so we do no need to fetch the data
270 # if mctx was wrong `mctx[bits[-2]]` may fails.
270 # if mctx was wrong `mctx[bits[-2]]` may fails.
271 for idx, r in enumerate(v1records):
271 for idx, r in enumerate(v1records):
272 if r[0] == RECORD_MERGED:
272 if r[0] == RECORD_MERGED:
273 bits = r[1].split('\0')
273 bits = r[1].split('\0')
274 bits.insert(-2, '')
274 bits.insert(-2, '')
275 v1records[idx] = (r[0], '\0'.join(bits))
275 v1records[idx] = (r[0], '\0'.join(bits))
276 return v1records
276 return v1records
277
277
278 def _v1v2match(self, v1records, v2records):
278 def _v1v2match(self, v1records, v2records):
279 oldv2 = set() # old format version of v2 record
279 oldv2 = set() # old format version of v2 record
280 for rec in v2records:
280 for rec in v2records:
281 if rec[0] == RECORD_LOCAL:
281 if rec[0] == RECORD_LOCAL:
282 oldv2.add(rec)
282 oldv2.add(rec)
283 elif rec[0] == RECORD_MERGED:
283 elif rec[0] == RECORD_MERGED:
284 # drop the onode data (not contained in v1)
284 # drop the onode data (not contained in v1)
285 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
285 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
286 for rec in v1records:
286 for rec in v1records:
287 if rec not in oldv2:
287 if rec not in oldv2:
288 return False
288 return False
289 else:
289 else:
290 return True
290 return True
291
291
292 def _readrecordsv1(self):
292 def _readrecordsv1(self):
293 """read on disk merge state for version 1 file
293 """read on disk merge state for version 1 file
294
294
295 returns list of record [(TYPE, data), ...]
295 returns list of record [(TYPE, data), ...]
296
296
297 Note: the "F" data from this file are one entry short
297 Note: the "F" data from this file are one entry short
298 (no "other file node" entry)
298 (no "other file node" entry)
299 """
299 """
300 records = []
300 records = []
301 try:
301 try:
302 f = self._repo.vfs(self.statepathv1)
302 f = self._repo.vfs(self.statepathv1)
303 for i, l in enumerate(f):
303 for i, l in enumerate(f):
304 if i == 0:
304 if i == 0:
305 records.append((RECORD_LOCAL, l[:-1]))
305 records.append((RECORD_LOCAL, l[:-1]))
306 else:
306 else:
307 records.append((RECORD_MERGED, l[:-1]))
307 records.append((RECORD_MERGED, l[:-1]))
308 f.close()
308 f.close()
309 except IOError as err:
309 except IOError as err:
310 if err.errno != errno.ENOENT:
310 if err.errno != errno.ENOENT:
311 raise
311 raise
312 return records
312 return records
313
313
314 def _readrecordsv2(self):
314 def _readrecordsv2(self):
315 """read on disk merge state for version 2 file
315 """read on disk merge state for version 2 file
316
316
317 This format is a list of arbitrary records of the form:
317 This format is a list of arbitrary records of the form:
318
318
319 [type][length][content]
319 [type][length][content]
320
320
321 `type` is a single character, `length` is a 4 byte integer, and
321 `type` is a single character, `length` is a 4 byte integer, and
322 `content` is an arbitrary byte sequence of length `length`.
322 `content` is an arbitrary byte sequence of length `length`.
323
323
324 Mercurial versions prior to 3.7 have a bug where if there are
324 Mercurial versions prior to 3.7 have a bug where if there are
325 unsupported mandatory merge records, attempting to clear out the merge
325 unsupported mandatory merge records, attempting to clear out the merge
326 state with hg update --clean or similar aborts. The 't' record type
326 state with hg update --clean or similar aborts. The 't' record type
327 works around that by writing out what those versions treat as an
327 works around that by writing out what those versions treat as an
328 advisory record, but later versions interpret as special: the first
328 advisory record, but later versions interpret as special: the first
329 character is the 'real' record type and everything onwards is the data.
329 character is the 'real' record type and everything onwards is the data.
330
330
331 Returns list of records [(TYPE, data), ...]."""
331 Returns list of records [(TYPE, data), ...]."""
332 records = []
332 records = []
333 try:
333 try:
334 f = self._repo.vfs(self.statepathv2)
334 f = self._repo.vfs(self.statepathv2)
335 data = f.read()
335 data = f.read()
336 off = 0
336 off = 0
337 end = len(data)
337 end = len(data)
338 while off < end:
338 while off < end:
339 rtype = data[off:off + 1]
339 rtype = data[off:off + 1]
340 off += 1
340 off += 1
341 length = _unpack('>I', data[off:(off + 4)])[0]
341 length = _unpack('>I', data[off:(off + 4)])[0]
342 off += 4
342 off += 4
343 record = data[off:(off + length)]
343 record = data[off:(off + length)]
344 off += length
344 off += length
345 if rtype == RECORD_OVERRIDE:
345 if rtype == RECORD_OVERRIDE:
346 rtype, record = record[0:1], record[1:]
346 rtype, record = record[0:1], record[1:]
347 records.append((rtype, record))
347 records.append((rtype, record))
348 f.close()
348 f.close()
349 except IOError as err:
349 except IOError as err:
350 if err.errno != errno.ENOENT:
350 if err.errno != errno.ENOENT:
351 raise
351 raise
352 return records
352 return records
353
353
354 @util.propertycache
354 @util.propertycache
355 def mergedriver(self):
355 def mergedriver(self):
356 # protect against the following:
356 # protect against the following:
357 # - A configures a malicious merge driver in their hgrc, then
357 # - A configures a malicious merge driver in their hgrc, then
358 # pauses the merge
358 # pauses the merge
359 # - A edits their hgrc to remove references to the merge driver
359 # - A edits their hgrc to remove references to the merge driver
360 # - A gives a copy of their entire repo, including .hg, to B
360 # - A gives a copy of their entire repo, including .hg, to B
361 # - B inspects .hgrc and finds it to be clean
361 # - B inspects .hgrc and finds it to be clean
362 # - B then continues the merge and the malicious merge driver
362 # - B then continues the merge and the malicious merge driver
363 # gets invoked
363 # gets invoked
364 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
364 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
365 if (self._readmergedriver is not None
365 if (self._readmergedriver is not None
366 and self._readmergedriver != configmergedriver):
366 and self._readmergedriver != configmergedriver):
367 raise error.ConfigError(
367 raise error.ConfigError(
368 _("merge driver changed since merge started"),
368 _("merge driver changed since merge started"),
369 hint=_("revert merge driver change or abort merge"))
369 hint=_("revert merge driver change or abort merge"))
370
370
371 return configmergedriver
371 return configmergedriver
372
372
373 @util.propertycache
373 @util.propertycache
374 def localctx(self):
374 def localctx(self):
375 if self._local is None:
375 if self._local is None:
376 msg = "localctx accessed but self._local isn't set"
376 msg = "localctx accessed but self._local isn't set"
377 raise error.ProgrammingError(msg)
377 raise error.ProgrammingError(msg)
378 return self._repo[self._local]
378 return self._repo[self._local]
379
379
380 @util.propertycache
380 @util.propertycache
381 def otherctx(self):
381 def otherctx(self):
382 if self._other is None:
382 if self._other is None:
383 msg = "otherctx accessed but self._other isn't set"
383 msg = "otherctx accessed but self._other isn't set"
384 raise error.ProgrammingError(msg)
384 raise error.ProgrammingError(msg)
385 return self._repo[self._other]
385 return self._repo[self._other]
386
386
387 def active(self):
387 def active(self):
388 """Whether mergestate is active.
388 """Whether mergestate is active.
389
389
390 Returns True if there appears to be mergestate. This is a rough proxy
390 Returns True if there appears to be mergestate. This is a rough proxy
391 for "is a merge in progress."
391 for "is a merge in progress."
392 """
392 """
393 # Check local variables before looking at filesystem for performance
393 # Check local variables before looking at filesystem for performance
394 # reasons.
394 # reasons.
395 return (bool(self._local) or bool(self._state) or
395 return (bool(self._local) or bool(self._state) or
396 self._repo.vfs.exists(self.statepathv1) or
396 self._repo.vfs.exists(self.statepathv1) or
397 self._repo.vfs.exists(self.statepathv2))
397 self._repo.vfs.exists(self.statepathv2))
398
398
399 def commit(self):
399 def commit(self):
400 """Write current state on disk (if necessary)"""
400 """Write current state on disk (if necessary)"""
401 if self._dirty:
401 if self._dirty:
402 records = self._makerecords()
402 records = self._makerecords()
403 self._writerecords(records)
403 self._writerecords(records)
404 self._dirty = False
404 self._dirty = False
405
405
406 def _makerecords(self):
406 def _makerecords(self):
407 records = []
407 records = []
408 records.append((RECORD_LOCAL, hex(self._local)))
408 records.append((RECORD_LOCAL, hex(self._local)))
409 records.append((RECORD_OTHER, hex(self._other)))
409 records.append((RECORD_OTHER, hex(self._other)))
410 if self.mergedriver:
410 if self.mergedriver:
411 records.append((RECORD_MERGE_DRIVER_STATE, '\0'.join([
411 records.append((RECORD_MERGE_DRIVER_STATE, '\0'.join([
412 self.mergedriver, self._mdstate])))
412 self.mergedriver, self._mdstate])))
413 # Write out state items. In all cases, the value of the state map entry
413 # Write out state items. In all cases, the value of the state map entry
414 # is written as the contents of the record. The record type depends on
414 # is written as the contents of the record. The record type depends on
415 # the type of state that is stored, and capital-letter records are used
415 # the type of state that is stored, and capital-letter records are used
416 # to prevent older versions of Mercurial that do not support the feature
416 # to prevent older versions of Mercurial that do not support the feature
417 # from loading them.
417 # from loading them.
418 for filename, v in self._state.iteritems():
418 for filename, v in self._state.iteritems():
419 if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
419 if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
420 # Driver-resolved merge. These are stored in 'D' records.
420 # Driver-resolved merge. These are stored in 'D' records.
421 records.append((RECORD_MERGE_DRIVER_MERGE,
421 records.append((RECORD_MERGE_DRIVER_MERGE,
422 '\0'.join([filename] + v)))
422 '\0'.join([filename] + v)))
423 elif v[0] in (MERGE_RECORD_UNRESOLVED_PATH,
423 elif v[0] in (MERGE_RECORD_UNRESOLVED_PATH,
424 MERGE_RECORD_RESOLVED_PATH):
424 MERGE_RECORD_RESOLVED_PATH):
425 # Path conflicts. These are stored in 'P' records. The current
425 # Path conflicts. These are stored in 'P' records. The current
426 # resolution state ('pu' or 'pr') is stored within the record.
426 # resolution state ('pu' or 'pr') is stored within the record.
427 records.append((RECORD_PATH_CONFLICT,
427 records.append((RECORD_PATH_CONFLICT,
428 '\0'.join([filename] + v)))
428 '\0'.join([filename] + v)))
429 elif v[1] == nullhex or v[6] == nullhex:
429 elif v[1] == nullhex or v[6] == nullhex:
430 # Change/Delete or Delete/Change conflicts. These are stored in
430 # Change/Delete or Delete/Change conflicts. These are stored in
431 # 'C' records. v[1] is the local file, and is nullhex when the
431 # 'C' records. v[1] is the local file, and is nullhex when the
432 # file is deleted locally ('dc'). v[6] is the remote file, and
432 # file is deleted locally ('dc'). v[6] is the remote file, and
433 # is nullhex when the file is deleted remotely ('cd').
433 # is nullhex when the file is deleted remotely ('cd').
434 records.append((RECORD_CHANGEDELETE_CONFLICT,
434 records.append((RECORD_CHANGEDELETE_CONFLICT,
435 '\0'.join([filename] + v)))
435 '\0'.join([filename] + v)))
436 else:
436 else:
437 # Normal files. These are stored in 'F' records.
437 # Normal files. These are stored in 'F' records.
438 records.append((RECORD_MERGED,
438 records.append((RECORD_MERGED,
439 '\0'.join([filename] + v)))
439 '\0'.join([filename] + v)))
440 for filename, extras in sorted(self._stateextras.iteritems()):
440 for filename, extras in sorted(self._stateextras.iteritems()):
441 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
441 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
442 extras.iteritems())
442 extras.iteritems())
443 records.append((RECORD_FILE_VALUES,
443 records.append((RECORD_FILE_VALUES,
444 '%s\0%s' % (filename, rawextras)))
444 '%s\0%s' % (filename, rawextras)))
445 if self._labels is not None:
445 if self._labels is not None:
446 labels = '\0'.join(self._labels)
446 labels = '\0'.join(self._labels)
447 records.append((RECORD_LABELS, labels))
447 records.append((RECORD_LABELS, labels))
448 return records
448 return records
449
449
450 def _writerecords(self, records):
450 def _writerecords(self, records):
451 """Write current state on disk (both v1 and v2)"""
451 """Write current state on disk (both v1 and v2)"""
452 self._writerecordsv1(records)
452 self._writerecordsv1(records)
453 self._writerecordsv2(records)
453 self._writerecordsv2(records)
454
454
455 def _writerecordsv1(self, records):
455 def _writerecordsv1(self, records):
456 """Write current state on disk in a version 1 file"""
456 """Write current state on disk in a version 1 file"""
457 f = self._repo.vfs(self.statepathv1, 'wb')
457 f = self._repo.vfs(self.statepathv1, 'wb')
458 irecords = iter(records)
458 irecords = iter(records)
459 lrecords = next(irecords)
459 lrecords = next(irecords)
460 assert lrecords[0] == RECORD_LOCAL
460 assert lrecords[0] == RECORD_LOCAL
461 f.write(hex(self._local) + '\n')
461 f.write(hex(self._local) + '\n')
462 for rtype, data in irecords:
462 for rtype, data in irecords:
463 if rtype == RECORD_MERGED:
463 if rtype == RECORD_MERGED:
464 f.write('%s\n' % _droponode(data))
464 f.write('%s\n' % _droponode(data))
465 f.close()
465 f.close()
466
466
467 def _writerecordsv2(self, records):
467 def _writerecordsv2(self, records):
468 """Write current state on disk in a version 2 file
468 """Write current state on disk in a version 2 file
469
469
470 See the docstring for _readrecordsv2 for why we use 't'."""
470 See the docstring for _readrecordsv2 for why we use 't'."""
471 # these are the records that all version 2 clients can read
471 # these are the records that all version 2 clients can read
472 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
472 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
473 f = self._repo.vfs(self.statepathv2, 'wb')
473 f = self._repo.vfs(self.statepathv2, 'wb')
474 for key, data in records:
474 for key, data in records:
475 assert len(key) == 1
475 assert len(key) == 1
476 if key not in allowlist:
476 if key not in allowlist:
477 key, data = RECORD_OVERRIDE, '%s%s' % (key, data)
477 key, data = RECORD_OVERRIDE, '%s%s' % (key, data)
478 format = '>sI%is' % len(data)
478 format = '>sI%is' % len(data)
479 f.write(_pack(format, key, len(data), data))
479 f.write(_pack(format, key, len(data), data))
480 f.close()
480 f.close()
481
481
482 @staticmethod
482 @staticmethod
483 def getlocalkey(path):
483 def getlocalkey(path):
484 """hash the path of a local file context for storage in the .hg/merge
484 """hash the path of a local file context for storage in the .hg/merge
485 directory."""
485 directory."""
486
486
487 return hex(hashlib.sha1(path).digest())
487 return hex(hashlib.sha1(path).digest())
488
488
489 def add(self, fcl, fco, fca, fd):
489 def add(self, fcl, fco, fca, fd):
490 """add a new (potentially?) conflicting file the merge state
490 """add a new (potentially?) conflicting file the merge state
491 fcl: file context for local,
491 fcl: file context for local,
492 fco: file context for remote,
492 fco: file context for remote,
493 fca: file context for ancestors,
493 fca: file context for ancestors,
494 fd: file path of the resulting merge.
494 fd: file path of the resulting merge.
495
495
496 note: also write the local version to the `.hg/merge` directory.
496 note: also write the local version to the `.hg/merge` directory.
497 """
497 """
498 if fcl.isabsent():
498 if fcl.isabsent():
499 localkey = nullhex
499 localkey = nullhex
500 else:
500 else:
501 localkey = mergestate.getlocalkey(fcl.path())
501 localkey = mergestate.getlocalkey(fcl.path())
502 self._repo.vfs.write('merge/' + localkey, fcl.data())
502 self._repo.vfs.write('merge/' + localkey, fcl.data())
503 self._state[fd] = [MERGE_RECORD_UNRESOLVED, localkey, fcl.path(),
503 self._state[fd] = [MERGE_RECORD_UNRESOLVED, localkey, fcl.path(),
504 fca.path(), hex(fca.filenode()),
504 fca.path(), hex(fca.filenode()),
505 fco.path(), hex(fco.filenode()),
505 fco.path(), hex(fco.filenode()),
506 fcl.flags()]
506 fcl.flags()]
507 self._stateextras[fd] = {'ancestorlinknode': hex(fca.node())}
507 self._stateextras[fd] = {'ancestorlinknode': hex(fca.node())}
508 self._dirty = True
508 self._dirty = True
509
509
510 def addpath(self, path, frename, forigin):
510 def addpath(self, path, frename, forigin):
511 """add a new conflicting path to the merge state
511 """add a new conflicting path to the merge state
512 path: the path that conflicts
512 path: the path that conflicts
513 frename: the filename the conflicting file was renamed to
513 frename: the filename the conflicting file was renamed to
514 forigin: origin of the file ('l' or 'r' for local/remote)
514 forigin: origin of the file ('l' or 'r' for local/remote)
515 """
515 """
516 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
516 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
517 self._dirty = True
517 self._dirty = True
518
518
519 def __contains__(self, dfile):
519 def __contains__(self, dfile):
520 return dfile in self._state
520 return dfile in self._state
521
521
522 def __getitem__(self, dfile):
522 def __getitem__(self, dfile):
523 return self._state[dfile][0]
523 return self._state[dfile][0]
524
524
525 def __iter__(self):
525 def __iter__(self):
526 return iter(sorted(self._state))
526 return iter(sorted(self._state))
527
527
528 def files(self):
528 def files(self):
529 return self._state.keys()
529 return self._state.keys()
530
530
531 def mark(self, dfile, state):
531 def mark(self, dfile, state):
532 self._state[dfile][0] = state
532 self._state[dfile][0] = state
533 self._dirty = True
533 self._dirty = True
534
534
535 def mdstate(self):
535 def mdstate(self):
536 return self._mdstate
536 return self._mdstate
537
537
538 def unresolved(self):
538 def unresolved(self):
539 """Obtain the paths of unresolved files."""
539 """Obtain the paths of unresolved files."""
540
540
541 for f, entry in self._state.iteritems():
541 for f, entry in self._state.iteritems():
542 if entry[0] in (MERGE_RECORD_UNRESOLVED,
542 if entry[0] in (MERGE_RECORD_UNRESOLVED,
543 MERGE_RECORD_UNRESOLVED_PATH):
543 MERGE_RECORD_UNRESOLVED_PATH):
544 yield f
544 yield f
545
545
546 def driverresolved(self):
546 def driverresolved(self):
547 """Obtain the paths of driver-resolved files."""
547 """Obtain the paths of driver-resolved files."""
548
548
549 for f, entry in self._state.items():
549 for f, entry in self._state.items():
550 if entry[0] == MERGE_RECORD_DRIVER_RESOLVED:
550 if entry[0] == MERGE_RECORD_DRIVER_RESOLVED:
551 yield f
551 yield f
552
552
553 def extras(self, filename):
553 def extras(self, filename):
554 return self._stateextras.setdefault(filename, {})
554 return self._stateextras.setdefault(filename, {})
555
555
556 def _resolve(self, preresolve, dfile, wctx):
556 def _resolve(self, preresolve, dfile, wctx):
557 """rerun merge process for file path `dfile`"""
557 """rerun merge process for file path `dfile`"""
558 if self[dfile] in (MERGE_RECORD_RESOLVED,
558 if self[dfile] in (MERGE_RECORD_RESOLVED,
559 MERGE_RECORD_DRIVER_RESOLVED):
559 MERGE_RECORD_DRIVER_RESOLVED):
560 return True, 0
560 return True, 0
561 stateentry = self._state[dfile]
561 stateentry = self._state[dfile]
562 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
562 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
563 octx = self._repo[self._other]
563 octx = self._repo[self._other]
564 extras = self.extras(dfile)
564 extras = self.extras(dfile)
565 anccommitnode = extras.get('ancestorlinknode')
565 anccommitnode = extras.get('ancestorlinknode')
566 if anccommitnode:
566 if anccommitnode:
567 actx = self._repo[anccommitnode]
567 actx = self._repo[anccommitnode]
568 else:
568 else:
569 actx = None
569 actx = None
570 fcd = self._filectxorabsent(localkey, wctx, dfile)
570 fcd = self._filectxorabsent(localkey, wctx, dfile)
571 fco = self._filectxorabsent(onode, octx, ofile)
571 fco = self._filectxorabsent(onode, octx, ofile)
572 # TODO: move this to filectxorabsent
572 # TODO: move this to filectxorabsent
573 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
573 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
574 # "premerge" x flags
574 # "premerge" x flags
575 flo = fco.flags()
575 flo = fco.flags()
576 fla = fca.flags()
576 fla = fca.flags()
577 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
577 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
578 if fca.node() == nullid and flags != flo:
578 if fca.node() == nullid and flags != flo:
579 if preresolve:
579 if preresolve:
580 self._repo.ui.warn(
580 self._repo.ui.warn(
581 _('warning: cannot merge flags for %s '
581 _('warning: cannot merge flags for %s '
582 'without common ancestor - keeping local flags\n')
582 'without common ancestor - keeping local flags\n')
583 % afile)
583 % afile)
584 elif flags == fla:
584 elif flags == fla:
585 flags = flo
585 flags = flo
586 if preresolve:
586 if preresolve:
587 # restore local
587 # restore local
588 if localkey != nullhex:
588 if localkey != nullhex:
589 f = self._repo.vfs('merge/' + localkey)
589 f = self._repo.vfs('merge/' + localkey)
590 wctx[dfile].write(f.read(), flags)
590 wctx[dfile].write(f.read(), flags)
591 f.close()
591 f.close()
592 else:
592 else:
593 wctx[dfile].remove(ignoremissing=True)
593 wctx[dfile].remove(ignoremissing=True)
594 complete, r, deleted = filemerge.premerge(self._repo, wctx,
594 complete, r, deleted = filemerge.premerge(self._repo, wctx,
595 self._local, lfile, fcd,
595 self._local, lfile, fcd,
596 fco, fca,
596 fco, fca,
597 labels=self._labels)
597 labels=self._labels)
598 else:
598 else:
599 complete, r, deleted = filemerge.filemerge(self._repo, wctx,
599 complete, r, deleted = filemerge.filemerge(self._repo, wctx,
600 self._local, lfile, fcd,
600 self._local, lfile, fcd,
601 fco, fca,
601 fco, fca,
602 labels=self._labels)
602 labels=self._labels)
603 if r is None:
603 if r is None:
604 # no real conflict
604 # no real conflict
605 del self._state[dfile]
605 del self._state[dfile]
606 self._stateextras.pop(dfile, None)
606 self._stateextras.pop(dfile, None)
607 self._dirty = True
607 self._dirty = True
608 elif not r:
608 elif not r:
609 self.mark(dfile, MERGE_RECORD_RESOLVED)
609 self.mark(dfile, MERGE_RECORD_RESOLVED)
610
610
611 if complete:
611 if complete:
612 action = None
612 action = None
613 if deleted:
613 if deleted:
614 if fcd.isabsent():
614 if fcd.isabsent():
615 # dc: local picked. Need to drop if present, which may
615 # dc: local picked. Need to drop if present, which may
616 # happen on re-resolves.
616 # happen on re-resolves.
617 action = ACTION_FORGET
617 action = ACTION_FORGET
618 else:
618 else:
619 # cd: remote picked (or otherwise deleted)
619 # cd: remote picked (or otherwise deleted)
620 action = ACTION_REMOVE
620 action = ACTION_REMOVE
621 else:
621 else:
622 if fcd.isabsent(): # dc: remote picked
622 if fcd.isabsent(): # dc: remote picked
623 action = ACTION_GET
623 action = ACTION_GET
624 elif fco.isabsent(): # cd: local picked
624 elif fco.isabsent(): # cd: local picked
625 if dfile in self.localctx:
625 if dfile in self.localctx:
626 action = ACTION_ADD_MODIFIED
626 action = ACTION_ADD_MODIFIED
627 else:
627 else:
628 action = ACTION_ADD
628 action = ACTION_ADD
629 # else: regular merges (no action necessary)
629 # else: regular merges (no action necessary)
630 self._results[dfile] = r, action
630 self._results[dfile] = r, action
631
631
632 return complete, r
632 return complete, r
633
633
634 def _filectxorabsent(self, hexnode, ctx, f):
634 def _filectxorabsent(self, hexnode, ctx, f):
635 if hexnode == nullhex:
635 if hexnode == nullhex:
636 return filemerge.absentfilectx(ctx, f)
636 return filemerge.absentfilectx(ctx, f)
637 else:
637 else:
638 return ctx[f]
638 return ctx[f]
639
639
640 def preresolve(self, dfile, wctx):
640 def preresolve(self, dfile, wctx):
641 """run premerge process for dfile
641 """run premerge process for dfile
642
642
643 Returns whether the merge is complete, and the exit code."""
643 Returns whether the merge is complete, and the exit code."""
644 return self._resolve(True, dfile, wctx)
644 return self._resolve(True, dfile, wctx)
645
645
646 def resolve(self, dfile, wctx):
646 def resolve(self, dfile, wctx):
647 """run merge process (assuming premerge was run) for dfile
647 """run merge process (assuming premerge was run) for dfile
648
648
649 Returns the exit code of the merge."""
649 Returns the exit code of the merge."""
650 return self._resolve(False, dfile, wctx)[1]
650 return self._resolve(False, dfile, wctx)[1]
651
651
652 def counts(self):
652 def counts(self):
653 """return counts for updated, merged and removed files in this
653 """return counts for updated, merged and removed files in this
654 session"""
654 session"""
655 updated, merged, removed = 0, 0, 0
655 updated, merged, removed = 0, 0, 0
656 for r, action in self._results.itervalues():
656 for r, action in self._results.itervalues():
657 if r is None:
657 if r is None:
658 updated += 1
658 updated += 1
659 elif r == 0:
659 elif r == 0:
660 if action == ACTION_REMOVE:
660 if action == ACTION_REMOVE:
661 removed += 1
661 removed += 1
662 else:
662 else:
663 merged += 1
663 merged += 1
664 return updated, merged, removed
664 return updated, merged, removed
665
665
666 def unresolvedcount(self):
666 def unresolvedcount(self):
667 """get unresolved count for this merge (persistent)"""
667 """get unresolved count for this merge (persistent)"""
668 return len(list(self.unresolved()))
668 return len(list(self.unresolved()))
669
669
670 def actions(self):
670 def actions(self):
671 """return lists of actions to perform on the dirstate"""
671 """return lists of actions to perform on the dirstate"""
672 actions = {
672 actions = {
673 ACTION_REMOVE: [],
673 ACTION_REMOVE: [],
674 ACTION_FORGET: [],
674 ACTION_FORGET: [],
675 ACTION_ADD: [],
675 ACTION_ADD: [],
676 ACTION_ADD_MODIFIED: [],
676 ACTION_ADD_MODIFIED: [],
677 ACTION_GET: [],
677 ACTION_GET: [],
678 }
678 }
679 for f, (r, action) in self._results.iteritems():
679 for f, (r, action) in self._results.iteritems():
680 if action is not None:
680 if action is not None:
681 actions[action].append((f, None, "merge result"))
681 actions[action].append((f, None, "merge result"))
682 return actions
682 return actions
683
683
684 def recordactions(self):
684 def recordactions(self):
685 """record remove/add/get actions in the dirstate"""
685 """record remove/add/get actions in the dirstate"""
686 branchmerge = self._repo.dirstate.p2() != nullid
686 branchmerge = self._repo.dirstate.p2() != nullid
687 recordupdates(self._repo, self.actions(), branchmerge, None)
687 recordupdates(self._repo, self.actions(), branchmerge, None)
688
688
689 def queueremove(self, f):
689 def queueremove(self, f):
690 """queues a file to be removed from the dirstate
690 """queues a file to be removed from the dirstate
691
691
692 Meant for use by custom merge drivers."""
692 Meant for use by custom merge drivers."""
693 self._results[f] = 0, ACTION_REMOVE
693 self._results[f] = 0, ACTION_REMOVE
694
694
695 def queueadd(self, f):
695 def queueadd(self, f):
696 """queues a file to be added to the dirstate
696 """queues a file to be added to the dirstate
697
697
698 Meant for use by custom merge drivers."""
698 Meant for use by custom merge drivers."""
699 self._results[f] = 0, ACTION_ADD
699 self._results[f] = 0, ACTION_ADD
700
700
701 def queueget(self, f):
701 def queueget(self, f):
702 """queues a file to be marked modified in the dirstate
702 """queues a file to be marked modified in the dirstate
703
703
704 Meant for use by custom merge drivers."""
704 Meant for use by custom merge drivers."""
705 self._results[f] = 0, ACTION_GET
705 self._results[f] = 0, ACTION_GET
706
706
707 def _getcheckunknownconfig(repo, section, name):
707 def _getcheckunknownconfig(repo, section, name):
708 config = repo.ui.config(section, name)
708 config = repo.ui.config(section, name)
709 valid = ['abort', 'ignore', 'warn']
709 valid = ['abort', 'ignore', 'warn']
710 if config not in valid:
710 if config not in valid:
711 validstr = ', '.join(["'" + v + "'" for v in valid])
711 validstr = ', '.join(["'" + v + "'" for v in valid])
712 raise error.ConfigError(_("%s.%s not valid "
712 raise error.ConfigError(_("%s.%s not valid "
713 "('%s' is none of %s)")
713 "('%s' is none of %s)")
714 % (section, name, config, validstr))
714 % (section, name, config, validstr))
715 return config
715 return config
716
716
717 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
717 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
718 if wctx.isinmemory():
718 if wctx.isinmemory():
719 # Nothing to do in IMM because nothing in the "working copy" can be an
719 # Nothing to do in IMM because nothing in the "working copy" can be an
720 # unknown file.
720 # unknown file.
721 #
721 #
722 # Note that we should bail out here, not in ``_checkunknownfiles()``,
722 # Note that we should bail out here, not in ``_checkunknownfiles()``,
723 # because that function does other useful work.
723 # because that function does other useful work.
724 return False
724 return False
725
725
726 if f2 is None:
726 if f2 is None:
727 f2 = f
727 f2 = f
728 return (repo.wvfs.audit.check(f)
728 return (repo.wvfs.audit.check(f)
729 and repo.wvfs.isfileorlink(f)
729 and repo.wvfs.isfileorlink(f)
730 and repo.dirstate.normalize(f) not in repo.dirstate
730 and repo.dirstate.normalize(f) not in repo.dirstate
731 and mctx[f2].cmp(wctx[f]))
731 and mctx[f2].cmp(wctx[f]))
732
732
733 class _unknowndirschecker(object):
733 class _unknowndirschecker(object):
734 """
734 """
735 Look for any unknown files or directories that may have a path conflict
735 Look for any unknown files or directories that may have a path conflict
736 with a file. If any path prefix of the file exists as a file or link,
736 with a file. If any path prefix of the file exists as a file or link,
737 then it conflicts. If the file itself is a directory that contains any
737 then it conflicts. If the file itself is a directory that contains any
738 file that is not tracked, then it conflicts.
738 file that is not tracked, then it conflicts.
739
739
740 Returns the shortest path at which a conflict occurs, or None if there is
740 Returns the shortest path at which a conflict occurs, or None if there is
741 no conflict.
741 no conflict.
742 """
742 """
743 def __init__(self):
743 def __init__(self):
744 # A set of paths known to be good. This prevents repeated checking of
744 # A set of paths known to be good. This prevents repeated checking of
745 # dirs. It will be updated with any new dirs that are checked and found
745 # dirs. It will be updated with any new dirs that are checked and found
746 # to be safe.
746 # to be safe.
747 self._unknowndircache = set()
747 self._unknowndircache = set()
748
748
749 # A set of paths that are known to be absent. This prevents repeated
749 # A set of paths that are known to be absent. This prevents repeated
750 # checking of subdirectories that are known not to exist. It will be
750 # checking of subdirectories that are known not to exist. It will be
751 # updated with any new dirs that are checked and found to be absent.
751 # updated with any new dirs that are checked and found to be absent.
752 self._missingdircache = set()
752 self._missingdircache = set()
753
753
754 def __call__(self, repo, wctx, f):
754 def __call__(self, repo, wctx, f):
755 if wctx.isinmemory():
755 if wctx.isinmemory():
756 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
756 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
757 return False
757 return False
758
758
759 # Check for path prefixes that exist as unknown files.
759 # Check for path prefixes that exist as unknown files.
760 for p in reversed(list(util.finddirs(f))):
760 for p in reversed(list(util.finddirs(f))):
761 if p in self._missingdircache:
761 if p in self._missingdircache:
762 return
762 return
763 if p in self._unknowndircache:
763 if p in self._unknowndircache:
764 continue
764 continue
765 if repo.wvfs.audit.check(p):
765 if repo.wvfs.audit.check(p):
766 if (repo.wvfs.isfileorlink(p)
766 if (repo.wvfs.isfileorlink(p)
767 and repo.dirstate.normalize(p) not in repo.dirstate):
767 and repo.dirstate.normalize(p) not in repo.dirstate):
768 return p
768 return p
769 if not repo.wvfs.lexists(p):
769 if not repo.wvfs.lexists(p):
770 self._missingdircache.add(p)
770 self._missingdircache.add(p)
771 return
771 return
772 self._unknowndircache.add(p)
772 self._unknowndircache.add(p)
773
773
774 # Check if the file conflicts with a directory containing unknown files.
774 # Check if the file conflicts with a directory containing unknown files.
775 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
775 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
776 # Does the directory contain any files that are not in the dirstate?
776 # Does the directory contain any files that are not in the dirstate?
777 for p, dirs, files in repo.wvfs.walk(f):
777 for p, dirs, files in repo.wvfs.walk(f):
778 for fn in files:
778 for fn in files:
779 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
779 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
780 relf = repo.dirstate.normalize(relf, isknown=True)
780 relf = repo.dirstate.normalize(relf, isknown=True)
781 if relf not in repo.dirstate:
781 if relf not in repo.dirstate:
782 return f
782 return f
783 return None
783 return None
784
784
785 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
785 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
786 """
786 """
787 Considers any actions that care about the presence of conflicting unknown
787 Considers any actions that care about the presence of conflicting unknown
788 files. For some actions, the result is to abort; for others, it is to
788 files. For some actions, the result is to abort; for others, it is to
789 choose a different action.
789 choose a different action.
790 """
790 """
791 fileconflicts = set()
791 fileconflicts = set()
792 pathconflicts = set()
792 pathconflicts = set()
793 warnconflicts = set()
793 warnconflicts = set()
794 abortconflicts = set()
794 abortconflicts = set()
795 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
795 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
796 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
796 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
797 pathconfig = repo.ui.configbool('experimental', 'merge.checkpathconflicts')
797 pathconfig = repo.ui.configbool('experimental', 'merge.checkpathconflicts')
798 if not force:
798 if not force:
799 def collectconflicts(conflicts, config):
799 def collectconflicts(conflicts, config):
800 if config == 'abort':
800 if config == 'abort':
801 abortconflicts.update(conflicts)
801 abortconflicts.update(conflicts)
802 elif config == 'warn':
802 elif config == 'warn':
803 warnconflicts.update(conflicts)
803 warnconflicts.update(conflicts)
804
804
805 checkunknowndirs = _unknowndirschecker()
805 checkunknowndirs = _unknowndirschecker()
806 for f, (m, args, msg) in actions.iteritems():
806 for f, (m, args, msg) in actions.iteritems():
807 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED):
807 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED):
808 if _checkunknownfile(repo, wctx, mctx, f):
808 if _checkunknownfile(repo, wctx, mctx, f):
809 fileconflicts.add(f)
809 fileconflicts.add(f)
810 elif pathconfig and f not in wctx:
810 elif pathconfig and f not in wctx:
811 path = checkunknowndirs(repo, wctx, f)
811 path = checkunknowndirs(repo, wctx, f)
812 if path is not None:
812 if path is not None:
813 pathconflicts.add(path)
813 pathconflicts.add(path)
814 elif m == ACTION_LOCAL_DIR_RENAME_GET:
814 elif m == ACTION_LOCAL_DIR_RENAME_GET:
815 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
815 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
816 fileconflicts.add(f)
816 fileconflicts.add(f)
817
817
818 allconflicts = fileconflicts | pathconflicts
818 allconflicts = fileconflicts | pathconflicts
819 ignoredconflicts = {c for c in allconflicts
819 ignoredconflicts = {c for c in allconflicts
820 if repo.dirstate._ignore(c)}
820 if repo.dirstate._ignore(c)}
821 unknownconflicts = allconflicts - ignoredconflicts
821 unknownconflicts = allconflicts - ignoredconflicts
822 collectconflicts(ignoredconflicts, ignoredconfig)
822 collectconflicts(ignoredconflicts, ignoredconfig)
823 collectconflicts(unknownconflicts, unknownconfig)
823 collectconflicts(unknownconflicts, unknownconfig)
824 else:
824 else:
825 for f, (m, args, msg) in actions.iteritems():
825 for f, (m, args, msg) in actions.iteritems():
826 if m == ACTION_CREATED_MERGE:
826 if m == ACTION_CREATED_MERGE:
827 fl2, anc = args
827 fl2, anc = args
828 different = _checkunknownfile(repo, wctx, mctx, f)
828 different = _checkunknownfile(repo, wctx, mctx, f)
829 if repo.dirstate._ignore(f):
829 if repo.dirstate._ignore(f):
830 config = ignoredconfig
830 config = ignoredconfig
831 else:
831 else:
832 config = unknownconfig
832 config = unknownconfig
833
833
834 # The behavior when force is True is described by this table:
834 # The behavior when force is True is described by this table:
835 # config different mergeforce | action backup
835 # config different mergeforce | action backup
836 # * n * | get n
836 # * n * | get n
837 # * y y | merge -
837 # * y y | merge -
838 # abort y n | merge - (1)
838 # abort y n | merge - (1)
839 # warn y n | warn + get y
839 # warn y n | warn + get y
840 # ignore y n | get y
840 # ignore y n | get y
841 #
841 #
842 # (1) this is probably the wrong behavior here -- we should
842 # (1) this is probably the wrong behavior here -- we should
843 # probably abort, but some actions like rebases currently
843 # probably abort, but some actions like rebases currently
844 # don't like an abort happening in the middle of
844 # don't like an abort happening in the middle of
845 # merge.update.
845 # merge.update.
846 if not different:
846 if not different:
847 actions[f] = (ACTION_GET, (fl2, False), 'remote created')
847 actions[f] = (ACTION_GET, (fl2, False), 'remote created')
848 elif mergeforce or config == 'abort':
848 elif mergeforce or config == 'abort':
849 actions[f] = (ACTION_MERGE, (f, f, None, False, anc),
849 actions[f] = (ACTION_MERGE, (f, f, None, False, anc),
850 'remote differs from untracked local')
850 'remote differs from untracked local')
851 elif config == 'abort':
851 elif config == 'abort':
852 abortconflicts.add(f)
852 abortconflicts.add(f)
853 else:
853 else:
854 if config == 'warn':
854 if config == 'warn':
855 warnconflicts.add(f)
855 warnconflicts.add(f)
856 actions[f] = (ACTION_GET, (fl2, True), 'remote created')
856 actions[f] = (ACTION_GET, (fl2, True), 'remote created')
857
857
858 for f in sorted(abortconflicts):
858 for f in sorted(abortconflicts):
859 warn = repo.ui.warn
859 warn = repo.ui.warn
860 if f in pathconflicts:
860 if f in pathconflicts:
861 if repo.wvfs.isfileorlink(f):
861 if repo.wvfs.isfileorlink(f):
862 warn(_("%s: untracked file conflicts with directory\n") % f)
862 warn(_("%s: untracked file conflicts with directory\n") % f)
863 else:
863 else:
864 warn(_("%s: untracked directory conflicts with file\n") % f)
864 warn(_("%s: untracked directory conflicts with file\n") % f)
865 else:
865 else:
866 warn(_("%s: untracked file differs\n") % f)
866 warn(_("%s: untracked file differs\n") % f)
867 if abortconflicts:
867 if abortconflicts:
868 raise error.Abort(_("untracked files in working directory "
868 raise error.Abort(_("untracked files in working directory "
869 "differ from files in requested revision"))
869 "differ from files in requested revision"))
870
870
871 for f in sorted(warnconflicts):
871 for f in sorted(warnconflicts):
872 if repo.wvfs.isfileorlink(f):
872 if repo.wvfs.isfileorlink(f):
873 repo.ui.warn(_("%s: replacing untracked file\n") % f)
873 repo.ui.warn(_("%s: replacing untracked file\n") % f)
874 else:
874 else:
875 repo.ui.warn(_("%s: replacing untracked files in directory\n") % f)
875 repo.ui.warn(_("%s: replacing untracked files in directory\n") % f)
876
876
877 for f, (m, args, msg) in actions.iteritems():
877 for f, (m, args, msg) in actions.iteritems():
878 if m == ACTION_CREATED:
878 if m == ACTION_CREATED:
879 backup = (f in fileconflicts or f in pathconflicts or
879 backup = (f in fileconflicts or f in pathconflicts or
880 any(p in pathconflicts for p in util.finddirs(f)))
880 any(p in pathconflicts for p in util.finddirs(f)))
881 flags, = args
881 flags, = args
882 actions[f] = (ACTION_GET, (flags, backup), msg)
882 actions[f] = (ACTION_GET, (flags, backup), msg)
883
883
884 def _forgetremoved(wctx, mctx, branchmerge):
884 def _forgetremoved(wctx, mctx, branchmerge):
885 """
885 """
886 Forget removed files
886 Forget removed files
887
887
888 If we're jumping between revisions (as opposed to merging), and if
888 If we're jumping between revisions (as opposed to merging), and if
889 neither the working directory nor the target rev has the file,
889 neither the working directory nor the target rev has the file,
890 then we need to remove it from the dirstate, to prevent the
890 then we need to remove it from the dirstate, to prevent the
891 dirstate from listing the file when it is no longer in the
891 dirstate from listing the file when it is no longer in the
892 manifest.
892 manifest.
893
893
894 If we're merging, and the other revision has removed a file
894 If we're merging, and the other revision has removed a file
895 that is not present in the working directory, we need to mark it
895 that is not present in the working directory, we need to mark it
896 as removed.
896 as removed.
897 """
897 """
898
898
899 actions = {}
899 actions = {}
900 m = ACTION_FORGET
900 m = ACTION_FORGET
901 if branchmerge:
901 if branchmerge:
902 m = ACTION_REMOVE
902 m = ACTION_REMOVE
903 for f in wctx.deleted():
903 for f in wctx.deleted():
904 if f not in mctx:
904 if f not in mctx:
905 actions[f] = m, None, "forget deleted"
905 actions[f] = m, None, "forget deleted"
906
906
907 if not branchmerge:
907 if not branchmerge:
908 for f in wctx.removed():
908 for f in wctx.removed():
909 if f not in mctx:
909 if f not in mctx:
910 actions[f] = ACTION_FORGET, None, "forget removed"
910 actions[f] = ACTION_FORGET, None, "forget removed"
911
911
912 return actions
912 return actions
913
913
914 def _checkcollision(repo, wmf, actions):
914 def _checkcollision(repo, wmf, actions):
915 """
915 """
916 Check for case-folding collisions.
916 Check for case-folding collisions.
917 """
917 """
918
918
919 # If the repo is narrowed, filter out files outside the narrowspec.
919 # If the repo is narrowed, filter out files outside the narrowspec.
920 narrowmatch = repo.narrowmatch()
920 narrowmatch = repo.narrowmatch()
921 if not narrowmatch.always():
921 if not narrowmatch.always():
922 wmf = wmf.matches(narrowmatch)
922 wmf = wmf.matches(narrowmatch)
923 if actions:
923 if actions:
924 narrowactions = {}
924 narrowactions = {}
925 for m, actionsfortype in actions.iteritems():
925 for m, actionsfortype in actions.iteritems():
926 narrowactions[m] = []
926 narrowactions[m] = []
927 for (f, args, msg) in actionsfortype:
927 for (f, args, msg) in actionsfortype:
928 if narrowmatch(f):
928 if narrowmatch(f):
929 narrowactions[m].append((f, args, msg))
929 narrowactions[m].append((f, args, msg))
930 actions = narrowactions
930 actions = narrowactions
931
931
932 # build provisional merged manifest up
932 # build provisional merged manifest up
933 pmmf = set(wmf)
933 pmmf = set(wmf)
934
934
935 if actions:
935 if actions:
936 # KEEP and EXEC are no-op
936 # KEEP and EXEC are no-op
937 for m in (ACTION_ADD, ACTION_ADD_MODIFIED, ACTION_FORGET, ACTION_GET,
937 for m in (ACTION_ADD, ACTION_ADD_MODIFIED, ACTION_FORGET, ACTION_GET,
938 ACTION_CHANGED_DELETED, ACTION_DELETED_CHANGED):
938 ACTION_CHANGED_DELETED, ACTION_DELETED_CHANGED):
939 for f, args, msg in actions[m]:
939 for f, args, msg in actions[m]:
940 pmmf.add(f)
940 pmmf.add(f)
941 for f, args, msg in actions[ACTION_REMOVE]:
941 for f, args, msg in actions[ACTION_REMOVE]:
942 pmmf.discard(f)
942 pmmf.discard(f)
943 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
943 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
944 f2, flags = args
944 f2, flags = args
945 pmmf.discard(f2)
945 pmmf.discard(f2)
946 pmmf.add(f)
946 pmmf.add(f)
947 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
947 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
948 pmmf.add(f)
948 pmmf.add(f)
949 for f, args, msg in actions[ACTION_MERGE]:
949 for f, args, msg in actions[ACTION_MERGE]:
950 f1, f2, fa, move, anc = args
950 f1, f2, fa, move, anc = args
951 if move:
951 if move:
952 pmmf.discard(f1)
952 pmmf.discard(f1)
953 pmmf.add(f)
953 pmmf.add(f)
954
954
955 # check case-folding collision in provisional merged manifest
955 # check case-folding collision in provisional merged manifest
956 foldmap = {}
956 foldmap = {}
957 for f in pmmf:
957 for f in pmmf:
958 fold = util.normcase(f)
958 fold = util.normcase(f)
959 if fold in foldmap:
959 if fold in foldmap:
960 raise error.Abort(_("case-folding collision between %s and %s")
960 raise error.Abort(_("case-folding collision between %s and %s")
961 % (f, foldmap[fold]))
961 % (f, foldmap[fold]))
962 foldmap[fold] = f
962 foldmap[fold] = f
963
963
964 # check case-folding of directories
964 # check case-folding of directories
965 foldprefix = unfoldprefix = lastfull = ''
965 foldprefix = unfoldprefix = lastfull = ''
966 for fold, f in sorted(foldmap.items()):
966 for fold, f in sorted(foldmap.items()):
967 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
967 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
968 # the folded prefix matches but actual casing is different
968 # the folded prefix matches but actual casing is different
969 raise error.Abort(_("case-folding collision between "
969 raise error.Abort(_("case-folding collision between "
970 "%s and directory of %s") % (lastfull, f))
970 "%s and directory of %s") % (lastfull, f))
971 foldprefix = fold + '/'
971 foldprefix = fold + '/'
972 unfoldprefix = f + '/'
972 unfoldprefix = f + '/'
973 lastfull = f
973 lastfull = f
974
974
975 def driverpreprocess(repo, ms, wctx, labels=None):
975 def driverpreprocess(repo, ms, wctx, labels=None):
976 """run the preprocess step of the merge driver, if any
976 """run the preprocess step of the merge driver, if any
977
977
978 This is currently not implemented -- it's an extension point."""
978 This is currently not implemented -- it's an extension point."""
979 return True
979 return True
980
980
981 def driverconclude(repo, ms, wctx, labels=None):
981 def driverconclude(repo, ms, wctx, labels=None):
982 """run the conclude step of the merge driver, if any
982 """run the conclude step of the merge driver, if any
983
983
984 This is currently not implemented -- it's an extension point."""
984 This is currently not implemented -- it's an extension point."""
985 return True
985 return True
986
986
987 def _filesindirs(repo, manifest, dirs):
987 def _filesindirs(repo, manifest, dirs):
988 """
988 """
989 Generator that yields pairs of all the files in the manifest that are found
989 Generator that yields pairs of all the files in the manifest that are found
990 inside the directories listed in dirs, and which directory they are found
990 inside the directories listed in dirs, and which directory they are found
991 in.
991 in.
992 """
992 """
993 for f in manifest:
993 for f in manifest:
994 for p in util.finddirs(f):
994 for p in util.finddirs(f):
995 if p in dirs:
995 if p in dirs:
996 yield f, p
996 yield f, p
997 break
997 break
998
998
999 def checkpathconflicts(repo, wctx, mctx, actions):
999 def checkpathconflicts(repo, wctx, mctx, actions):
1000 """
1000 """
1001 Check if any actions introduce path conflicts in the repository, updating
1001 Check if any actions introduce path conflicts in the repository, updating
1002 actions to record or handle the path conflict accordingly.
1002 actions to record or handle the path conflict accordingly.
1003 """
1003 """
1004 mf = wctx.manifest()
1004 mf = wctx.manifest()
1005
1005
1006 # The set of local files that conflict with a remote directory.
1006 # The set of local files that conflict with a remote directory.
1007 localconflicts = set()
1007 localconflicts = set()
1008
1008
1009 # The set of directories that conflict with a remote file, and so may cause
1009 # The set of directories that conflict with a remote file, and so may cause
1010 # conflicts if they still contain any files after the merge.
1010 # conflicts if they still contain any files after the merge.
1011 remoteconflicts = set()
1011 remoteconflicts = set()
1012
1012
1013 # The set of directories that appear as both a file and a directory in the
1013 # The set of directories that appear as both a file and a directory in the
1014 # remote manifest. These indicate an invalid remote manifest, which
1014 # remote manifest. These indicate an invalid remote manifest, which
1015 # can't be updated to cleanly.
1015 # can't be updated to cleanly.
1016 invalidconflicts = set()
1016 invalidconflicts = set()
1017
1017
1018 # The set of directories that contain files that are being created.
1018 # The set of directories that contain files that are being created.
1019 createdfiledirs = set()
1019 createdfiledirs = set()
1020
1020
1021 # The set of files deleted by all the actions.
1021 # The set of files deleted by all the actions.
1022 deletedfiles = set()
1022 deletedfiles = set()
1023
1023
1024 for f, (m, args, msg) in actions.items():
1024 for f, (m, args, msg) in actions.items():
1025 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED, ACTION_MERGE,
1025 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED, ACTION_MERGE,
1026 ACTION_CREATED_MERGE):
1026 ACTION_CREATED_MERGE):
1027 # This action may create a new local file.
1027 # This action may create a new local file.
1028 createdfiledirs.update(util.finddirs(f))
1028 createdfiledirs.update(util.finddirs(f))
1029 if mf.hasdir(f):
1029 if mf.hasdir(f):
1030 # The file aliases a local directory. This might be ok if all
1030 # The file aliases a local directory. This might be ok if all
1031 # the files in the local directory are being deleted. This
1031 # the files in the local directory are being deleted. This
1032 # will be checked once we know what all the deleted files are.
1032 # will be checked once we know what all the deleted files are.
1033 remoteconflicts.add(f)
1033 remoteconflicts.add(f)
1034 # Track the names of all deleted files.
1034 # Track the names of all deleted files.
1035 if m == ACTION_REMOVE:
1035 if m == ACTION_REMOVE:
1036 deletedfiles.add(f)
1036 deletedfiles.add(f)
1037 if m == ACTION_MERGE:
1037 if m == ACTION_MERGE:
1038 f1, f2, fa, move, anc = args
1038 f1, f2, fa, move, anc = args
1039 if move:
1039 if move:
1040 deletedfiles.add(f1)
1040 deletedfiles.add(f1)
1041 if m == ACTION_DIR_RENAME_MOVE_LOCAL:
1041 if m == ACTION_DIR_RENAME_MOVE_LOCAL:
1042 f2, flags = args
1042 f2, flags = args
1043 deletedfiles.add(f2)
1043 deletedfiles.add(f2)
1044
1044
1045 # Check all directories that contain created files for path conflicts.
1045 # Check all directories that contain created files for path conflicts.
1046 for p in createdfiledirs:
1046 for p in createdfiledirs:
1047 if p in mf:
1047 if p in mf:
1048 if p in mctx:
1048 if p in mctx:
1049 # A file is in a directory which aliases both a local
1049 # A file is in a directory which aliases both a local
1050 # and a remote file. This is an internal inconsistency
1050 # and a remote file. This is an internal inconsistency
1051 # within the remote manifest.
1051 # within the remote manifest.
1052 invalidconflicts.add(p)
1052 invalidconflicts.add(p)
1053 else:
1053 else:
1054 # A file is in a directory which aliases a local file.
1054 # A file is in a directory which aliases a local file.
1055 # We will need to rename the local file.
1055 # We will need to rename the local file.
1056 localconflicts.add(p)
1056 localconflicts.add(p)
1057 if p in actions and actions[p][0] in (ACTION_CREATED,
1057 if p in actions and actions[p][0] in (ACTION_CREATED,
1058 ACTION_DELETED_CHANGED,
1058 ACTION_DELETED_CHANGED,
1059 ACTION_MERGE,
1059 ACTION_MERGE,
1060 ACTION_CREATED_MERGE):
1060 ACTION_CREATED_MERGE):
1061 # The file is in a directory which aliases a remote file.
1061 # The file is in a directory which aliases a remote file.
1062 # This is an internal inconsistency within the remote
1062 # This is an internal inconsistency within the remote
1063 # manifest.
1063 # manifest.
1064 invalidconflicts.add(p)
1064 invalidconflicts.add(p)
1065
1065
1066 # Rename all local conflicting files that have not been deleted.
1066 # Rename all local conflicting files that have not been deleted.
1067 for p in localconflicts:
1067 for p in localconflicts:
1068 if p not in deletedfiles:
1068 if p not in deletedfiles:
1069 ctxname = bytes(wctx).rstrip('+')
1069 ctxname = bytes(wctx).rstrip('+')
1070 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1070 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1071 actions[pnew] = (ACTION_PATH_CONFLICT_RESOLVE, (p,),
1071 actions[pnew] = (ACTION_PATH_CONFLICT_RESOLVE, (p,),
1072 'local path conflict')
1072 'local path conflict')
1073 actions[p] = (ACTION_PATH_CONFLICT, (pnew, 'l'),
1073 actions[p] = (ACTION_PATH_CONFLICT, (pnew, 'l'),
1074 'path conflict')
1074 'path conflict')
1075
1075
1076 if remoteconflicts:
1076 if remoteconflicts:
1077 # Check if all files in the conflicting directories have been removed.
1077 # Check if all files in the conflicting directories have been removed.
1078 ctxname = bytes(mctx).rstrip('+')
1078 ctxname = bytes(mctx).rstrip('+')
1079 for f, p in _filesindirs(repo, mf, remoteconflicts):
1079 for f, p in _filesindirs(repo, mf, remoteconflicts):
1080 if f not in deletedfiles:
1080 if f not in deletedfiles:
1081 m, args, msg = actions[p]
1081 m, args, msg = actions[p]
1082 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1082 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1083 if m in (ACTION_DELETED_CHANGED, ACTION_MERGE):
1083 if m in (ACTION_DELETED_CHANGED, ACTION_MERGE):
1084 # Action was merge, just update target.
1084 # Action was merge, just update target.
1085 actions[pnew] = (m, args, msg)
1085 actions[pnew] = (m, args, msg)
1086 else:
1086 else:
1087 # Action was create, change to renamed get action.
1087 # Action was create, change to renamed get action.
1088 fl = args[0]
1088 fl = args[0]
1089 actions[pnew] = (ACTION_LOCAL_DIR_RENAME_GET, (p, fl),
1089 actions[pnew] = (ACTION_LOCAL_DIR_RENAME_GET, (p, fl),
1090 'remote path conflict')
1090 'remote path conflict')
1091 actions[p] = (ACTION_PATH_CONFLICT, (pnew, ACTION_REMOVE),
1091 actions[p] = (ACTION_PATH_CONFLICT, (pnew, ACTION_REMOVE),
1092 'path conflict')
1092 'path conflict')
1093 remoteconflicts.remove(p)
1093 remoteconflicts.remove(p)
1094 break
1094 break
1095
1095
1096 if invalidconflicts:
1096 if invalidconflicts:
1097 for p in invalidconflicts:
1097 for p in invalidconflicts:
1098 repo.ui.warn(_("%s: is both a file and a directory\n") % p)
1098 repo.ui.warn(_("%s: is both a file and a directory\n") % p)
1099 raise error.Abort(_("destination manifest contains path conflicts"))
1099 raise error.Abort(_("destination manifest contains path conflicts"))
1100
1100
1101 def _filternarrowactions(narrowmatch, branchmerge, actions):
1101 def _filternarrowactions(narrowmatch, branchmerge, actions):
1102 """
1102 """
1103 Filters out actions that can ignored because the repo is narrowed.
1103 Filters out actions that can ignored because the repo is narrowed.
1104
1104
1105 Raise an exception if the merge cannot be completed because the repo is
1105 Raise an exception if the merge cannot be completed because the repo is
1106 narrowed.
1106 narrowed.
1107 """
1107 """
1108 nooptypes = {'k'} # TODO: handle with nonconflicttypes
1108 nooptypes = {'k'} # TODO: handle with nonconflicttypes
1109 nonconflicttypes = set('a am c cm f g r e'.split())
1109 nonconflicttypes = set('a am c cm f g r e'.split())
1110 # We mutate the items in the dict during iteration, so iterate
1110 # We mutate the items in the dict during iteration, so iterate
1111 # over a copy.
1111 # over a copy.
1112 for f, action in list(actions.items()):
1112 for f, action in list(actions.items()):
1113 if narrowmatch(f):
1113 if narrowmatch(f):
1114 pass
1114 pass
1115 elif not branchmerge:
1115 elif not branchmerge:
1116 del actions[f] # just updating, ignore changes outside clone
1116 del actions[f] # just updating, ignore changes outside clone
1117 elif action[0] in nooptypes:
1117 elif action[0] in nooptypes:
1118 del actions[f] # merge does not affect file
1118 del actions[f] # merge does not affect file
1119 elif action[0] in nonconflicttypes:
1119 elif action[0] in nonconflicttypes:
1120 raise error.Abort(_('merge affects file \'%s\' outside narrow, '
1120 raise error.Abort(_('merge affects file \'%s\' outside narrow, '
1121 'which is not yet supported') % f,
1121 'which is not yet supported') % f,
1122 hint=_('merging in the other direction '
1122 hint=_('merging in the other direction '
1123 'may work'))
1123 'may work'))
1124 else:
1124 else:
1125 raise error.Abort(_('conflict in file \'%s\' is outside '
1125 raise error.Abort(_('conflict in file \'%s\' is outside '
1126 'narrow clone') % f)
1126 'narrow clone') % f)
1127
1127
1128 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
1128 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
1129 acceptremote, followcopies, forcefulldiff=False):
1129 acceptremote, followcopies, forcefulldiff=False):
1130 """
1130 """
1131 Merge wctx and p2 with ancestor pa and generate merge action list
1131 Merge wctx and p2 with ancestor pa and generate merge action list
1132
1132
1133 branchmerge and force are as passed in to update
1133 branchmerge and force are as passed in to update
1134 matcher = matcher to filter file lists
1134 matcher = matcher to filter file lists
1135 acceptremote = accept the incoming changes without prompting
1135 acceptremote = accept the incoming changes without prompting
1136 """
1136 """
1137 if matcher is not None and matcher.always():
1137 if matcher is not None and matcher.always():
1138 matcher = None
1138 matcher = None
1139
1139
1140 copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
1140 copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
1141
1141
1142 # manifests fetched in order are going to be faster, so prime the caches
1142 # manifests fetched in order are going to be faster, so prime the caches
1143 [x.manifest() for x in
1143 [x.manifest() for x in
1144 sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)]
1144 sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)]
1145
1145
1146 if followcopies:
1146 if followcopies:
1147 ret = copies.mergecopies(repo, wctx, p2, pa)
1147 ret = copies.mergecopies(repo, wctx, p2, pa)
1148 copy, movewithdir, diverge, renamedelete, dirmove = ret
1148 copy, movewithdir, diverge, renamedelete, dirmove = ret
1149
1149
1150 boolbm = pycompat.bytestr(bool(branchmerge))
1150 boolbm = pycompat.bytestr(bool(branchmerge))
1151 boolf = pycompat.bytestr(bool(force))
1151 boolf = pycompat.bytestr(bool(force))
1152 boolm = pycompat.bytestr(bool(matcher))
1152 boolm = pycompat.bytestr(bool(matcher))
1153 repo.ui.note(_("resolving manifests\n"))
1153 repo.ui.note(_("resolving manifests\n"))
1154 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
1154 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
1155 % (boolbm, boolf, boolm))
1155 % (boolbm, boolf, boolm))
1156 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
1156 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
1157
1157
1158 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
1158 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
1159 copied = set(copy.values())
1159 copied = set(copy.values())
1160 copied.update(movewithdir.values())
1160 copied.update(movewithdir.values())
1161
1161
1162 if '.hgsubstate' in m1 and wctx.rev() is None:
1162 if '.hgsubstate' in m1 and wctx.rev() is None:
1163 # Check whether sub state is modified, and overwrite the manifest
1163 # Check whether sub state is modified, and overwrite the manifest
1164 # to flag the change. If wctx is a committed revision, we shouldn't
1164 # to flag the change. If wctx is a committed revision, we shouldn't
1165 # care for the dirty state of the working directory.
1165 # care for the dirty state of the working directory.
1166 if any(wctx.sub(s).dirty() for s in wctx.substate):
1166 if any(wctx.sub(s).dirty() for s in wctx.substate):
1167 m1['.hgsubstate'] = modifiednodeid
1167 m1['.hgsubstate'] = modifiednodeid
1168
1168
1169 # Don't use m2-vs-ma optimization if:
1169 # Don't use m2-vs-ma optimization if:
1170 # - ma is the same as m1 or m2, which we're just going to diff again later
1170 # - ma is the same as m1 or m2, which we're just going to diff again later
1171 # - The caller specifically asks for a full diff, which is useful during bid
1171 # - The caller specifically asks for a full diff, which is useful during bid
1172 # merge.
1172 # merge.
1173 if (pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff):
1173 if (pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff):
1174 # Identify which files are relevant to the merge, so we can limit the
1174 # Identify which files are relevant to the merge, so we can limit the
1175 # total m1-vs-m2 diff to just those files. This has significant
1175 # total m1-vs-m2 diff to just those files. This has significant
1176 # performance benefits in large repositories.
1176 # performance benefits in large repositories.
1177 relevantfiles = set(ma.diff(m2).keys())
1177 relevantfiles = set(ma.diff(m2).keys())
1178
1178
1179 # For copied and moved files, we need to add the source file too.
1179 # For copied and moved files, we need to add the source file too.
1180 for copykey, copyvalue in copy.iteritems():
1180 for copykey, copyvalue in copy.iteritems():
1181 if copyvalue in relevantfiles:
1181 if copyvalue in relevantfiles:
1182 relevantfiles.add(copykey)
1182 relevantfiles.add(copykey)
1183 for movedirkey in movewithdir:
1183 for movedirkey in movewithdir:
1184 relevantfiles.add(movedirkey)
1184 relevantfiles.add(movedirkey)
1185 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
1185 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
1186 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
1186 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
1187
1187
1188 diff = m1.diff(m2, match=matcher)
1188 diff = m1.diff(m2, match=matcher)
1189
1189
1190 actions = {}
1190 actions = {}
1191 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
1191 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
1192 if n1 and n2: # file exists on both local and remote side
1192 if n1 and n2: # file exists on both local and remote side
1193 if f not in ma:
1193 if f not in ma:
1194 fa = copy.get(f, None)
1194 fa = copy.get(f, None)
1195 if fa is not None:
1195 if fa is not None:
1196 actions[f] = (ACTION_MERGE, (f, f, fa, False, pa.node()),
1196 actions[f] = (ACTION_MERGE, (f, f, fa, False, pa.node()),
1197 'both renamed from %s' % fa)
1197 'both renamed from %s' % fa)
1198 else:
1198 else:
1199 actions[f] = (ACTION_MERGE, (f, f, None, False, pa.node()),
1199 actions[f] = (ACTION_MERGE, (f, f, None, False, pa.node()),
1200 'both created')
1200 'both created')
1201 else:
1201 else:
1202 a = ma[f]
1202 a = ma[f]
1203 fla = ma.flags(f)
1203 fla = ma.flags(f)
1204 nol = 'l' not in fl1 + fl2 + fla
1204 nol = 'l' not in fl1 + fl2 + fla
1205 if n2 == a and fl2 == fla:
1205 if n2 == a and fl2 == fla:
1206 actions[f] = (ACTION_KEEP, (), 'remote unchanged')
1206 actions[f] = (ACTION_KEEP, (), 'remote unchanged')
1207 elif n1 == a and fl1 == fla: # local unchanged - use remote
1207 elif n1 == a and fl1 == fla: # local unchanged - use remote
1208 if n1 == n2: # optimization: keep local content
1208 if n1 == n2: # optimization: keep local content
1209 actions[f] = (ACTION_EXEC, (fl2,), 'update permissions')
1209 actions[f] = (ACTION_EXEC, (fl2,), 'update permissions')
1210 else:
1210 else:
1211 actions[f] = (ACTION_GET, (fl2, False),
1211 actions[f] = (ACTION_GET, (fl2, False),
1212 'remote is newer')
1212 'remote is newer')
1213 elif nol and n2 == a: # remote only changed 'x'
1213 elif nol and n2 == a: # remote only changed 'x'
1214 actions[f] = (ACTION_EXEC, (fl2,), 'update permissions')
1214 actions[f] = (ACTION_EXEC, (fl2,), 'update permissions')
1215 elif nol and n1 == a: # local only changed 'x'
1215 elif nol and n1 == a: # local only changed 'x'
1216 actions[f] = (ACTION_GET, (fl1, False), 'remote is newer')
1216 actions[f] = (ACTION_GET, (fl1, False), 'remote is newer')
1217 else: # both changed something
1217 else: # both changed something
1218 actions[f] = (ACTION_MERGE, (f, f, f, False, pa.node()),
1218 actions[f] = (ACTION_MERGE, (f, f, f, False, pa.node()),
1219 'versions differ')
1219 'versions differ')
1220 elif n1: # file exists only on local side
1220 elif n1: # file exists only on local side
1221 if f in copied:
1221 if f in copied:
1222 pass # we'll deal with it on m2 side
1222 pass # we'll deal with it on m2 side
1223 elif f in movewithdir: # directory rename, move local
1223 elif f in movewithdir: # directory rename, move local
1224 f2 = movewithdir[f]
1224 f2 = movewithdir[f]
1225 if f2 in m2:
1225 if f2 in m2:
1226 actions[f2] = (ACTION_MERGE, (f, f2, None, True, pa.node()),
1226 actions[f2] = (ACTION_MERGE, (f, f2, None, True, pa.node()),
1227 'remote directory rename, both created')
1227 'remote directory rename, both created')
1228 else:
1228 else:
1229 actions[f2] = (ACTION_DIR_RENAME_MOVE_LOCAL, (f, fl1),
1229 actions[f2] = (ACTION_DIR_RENAME_MOVE_LOCAL, (f, fl1),
1230 'remote directory rename - move from %s' % f)
1230 'remote directory rename - move from %s' % f)
1231 elif f in copy:
1231 elif f in copy:
1232 f2 = copy[f]
1232 f2 = copy[f]
1233 actions[f] = (ACTION_MERGE, (f, f2, f2, False, pa.node()),
1233 actions[f] = (ACTION_MERGE, (f, f2, f2, False, pa.node()),
1234 'local copied/moved from %s' % f2)
1234 'local copied/moved from %s' % f2)
1235 elif f in ma: # clean, a different, no remote
1235 elif f in ma: # clean, a different, no remote
1236 if n1 != ma[f]:
1236 if n1 != ma[f]:
1237 if acceptremote:
1237 if acceptremote:
1238 actions[f] = (ACTION_REMOVE, None, 'remote delete')
1238 actions[f] = (ACTION_REMOVE, None, 'remote delete')
1239 else:
1239 else:
1240 actions[f] = (ACTION_CHANGED_DELETED,
1240 actions[f] = (ACTION_CHANGED_DELETED,
1241 (f, None, f, False, pa.node()),
1241 (f, None, f, False, pa.node()),
1242 'prompt changed/deleted')
1242 'prompt changed/deleted')
1243 elif n1 == addednodeid:
1243 elif n1 == addednodeid:
1244 # This extra 'a' is added by working copy manifest to mark
1244 # This extra 'a' is added by working copy manifest to mark
1245 # the file as locally added. We should forget it instead of
1245 # the file as locally added. We should forget it instead of
1246 # deleting it.
1246 # deleting it.
1247 actions[f] = (ACTION_FORGET, None, 'remote deleted')
1247 actions[f] = (ACTION_FORGET, None, 'remote deleted')
1248 else:
1248 else:
1249 actions[f] = (ACTION_REMOVE, None, 'other deleted')
1249 actions[f] = (ACTION_REMOVE, None, 'other deleted')
1250 elif n2: # file exists only on remote side
1250 elif n2: # file exists only on remote side
1251 if f in copied:
1251 if f in copied:
1252 pass # we'll deal with it on m1 side
1252 pass # we'll deal with it on m1 side
1253 elif f in movewithdir:
1253 elif f in movewithdir:
1254 f2 = movewithdir[f]
1254 f2 = movewithdir[f]
1255 if f2 in m1:
1255 if f2 in m1:
1256 actions[f2] = (ACTION_MERGE,
1256 actions[f2] = (ACTION_MERGE,
1257 (f2, f, None, False, pa.node()),
1257 (f2, f, None, False, pa.node()),
1258 'local directory rename, both created')
1258 'local directory rename, both created')
1259 else:
1259 else:
1260 actions[f2] = (ACTION_LOCAL_DIR_RENAME_GET, (f, fl2),
1260 actions[f2] = (ACTION_LOCAL_DIR_RENAME_GET, (f, fl2),
1261 'local directory rename - get from %s' % f)
1261 'local directory rename - get from %s' % f)
1262 elif f in copy:
1262 elif f in copy:
1263 f2 = copy[f]
1263 f2 = copy[f]
1264 if f2 in m2:
1264 if f2 in m2:
1265 actions[f] = (ACTION_MERGE, (f2, f, f2, False, pa.node()),
1265 actions[f] = (ACTION_MERGE, (f2, f, f2, False, pa.node()),
1266 'remote copied from %s' % f2)
1266 'remote copied from %s' % f2)
1267 else:
1267 else:
1268 actions[f] = (ACTION_MERGE, (f2, f, f2, True, pa.node()),
1268 actions[f] = (ACTION_MERGE, (f2, f, f2, True, pa.node()),
1269 'remote moved from %s' % f2)
1269 'remote moved from %s' % f2)
1270 elif f not in ma:
1270 elif f not in ma:
1271 # local unknown, remote created: the logic is described by the
1271 # local unknown, remote created: the logic is described by the
1272 # following table:
1272 # following table:
1273 #
1273 #
1274 # force branchmerge different | action
1274 # force branchmerge different | action
1275 # n * * | create
1275 # n * * | create
1276 # y n * | create
1276 # y n * | create
1277 # y y n | create
1277 # y y n | create
1278 # y y y | merge
1278 # y y y | merge
1279 #
1279 #
1280 # Checking whether the files are different is expensive, so we
1280 # Checking whether the files are different is expensive, so we
1281 # don't do that when we can avoid it.
1281 # don't do that when we can avoid it.
1282 if not force:
1282 if not force:
1283 actions[f] = (ACTION_CREATED, (fl2,), 'remote created')
1283 actions[f] = (ACTION_CREATED, (fl2,), 'remote created')
1284 elif not branchmerge:
1284 elif not branchmerge:
1285 actions[f] = (ACTION_CREATED, (fl2,), 'remote created')
1285 actions[f] = (ACTION_CREATED, (fl2,), 'remote created')
1286 else:
1286 else:
1287 actions[f] = (ACTION_CREATED_MERGE, (fl2, pa.node()),
1287 actions[f] = (ACTION_CREATED_MERGE, (fl2, pa.node()),
1288 'remote created, get or merge')
1288 'remote created, get or merge')
1289 elif n2 != ma[f]:
1289 elif n2 != ma[f]:
1290 df = None
1290 df = None
1291 for d in dirmove:
1291 for d in dirmove:
1292 if f.startswith(d):
1292 if f.startswith(d):
1293 # new file added in a directory that was moved
1293 # new file added in a directory that was moved
1294 df = dirmove[d] + f[len(d):]
1294 df = dirmove[d] + f[len(d):]
1295 break
1295 break
1296 if df is not None and df in m1:
1296 if df is not None and df in m1:
1297 actions[df] = (ACTION_MERGE, (df, f, f, False, pa.node()),
1297 actions[df] = (ACTION_MERGE, (df, f, f, False, pa.node()),
1298 'local directory rename - respect move '
1298 'local directory rename - respect move '
1299 'from %s' % f)
1299 'from %s' % f)
1300 elif acceptremote:
1300 elif acceptremote:
1301 actions[f] = (ACTION_CREATED, (fl2,), 'remote recreating')
1301 actions[f] = (ACTION_CREATED, (fl2,), 'remote recreating')
1302 else:
1302 else:
1303 actions[f] = (ACTION_DELETED_CHANGED,
1303 actions[f] = (ACTION_DELETED_CHANGED,
1304 (None, f, f, False, pa.node()),
1304 (None, f, f, False, pa.node()),
1305 'prompt deleted/changed')
1305 'prompt deleted/changed')
1306
1306
1307 if repo.ui.configbool('experimental', 'merge.checkpathconflicts'):
1307 if repo.ui.configbool('experimental', 'merge.checkpathconflicts'):
1308 # If we are merging, look for path conflicts.
1308 # If we are merging, look for path conflicts.
1309 checkpathconflicts(repo, wctx, p2, actions)
1309 checkpathconflicts(repo, wctx, p2, actions)
1310
1310
1311 narrowmatch = repo.narrowmatch()
1311 narrowmatch = repo.narrowmatch()
1312 if not narrowmatch.always():
1312 if not narrowmatch.always():
1313 # Updates "actions" in place
1313 # Updates "actions" in place
1314 _filternarrowactions(narrowmatch, branchmerge, actions)
1314 _filternarrowactions(narrowmatch, branchmerge, actions)
1315
1315
1316 return actions, diverge, renamedelete
1316 return actions, diverge, renamedelete
1317
1317
1318 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
1318 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
1319 """Resolves false conflicts where the nodeid changed but the content
1319 """Resolves false conflicts where the nodeid changed but the content
1320 remained the same."""
1320 remained the same."""
1321 # We force a copy of actions.items() because we're going to mutate
1321 # We force a copy of actions.items() because we're going to mutate
1322 # actions as we resolve trivial conflicts.
1322 # actions as we resolve trivial conflicts.
1323 for f, (m, args, msg) in list(actions.items()):
1323 for f, (m, args, msg) in list(actions.items()):
1324 if (m == ACTION_CHANGED_DELETED and f in ancestor
1324 if (m == ACTION_CHANGED_DELETED and f in ancestor
1325 and not wctx[f].cmp(ancestor[f])):
1325 and not wctx[f].cmp(ancestor[f])):
1326 # local did change but ended up with same content
1326 # local did change but ended up with same content
1327 actions[f] = ACTION_REMOVE, None, 'prompt same'
1327 actions[f] = ACTION_REMOVE, None, 'prompt same'
1328 elif (m == ACTION_DELETED_CHANGED and f in ancestor
1328 elif (m == ACTION_DELETED_CHANGED and f in ancestor
1329 and not mctx[f].cmp(ancestor[f])):
1329 and not mctx[f].cmp(ancestor[f])):
1330 # remote did change but ended up with same content
1330 # remote did change but ended up with same content
1331 del actions[f] # don't get = keep local deleted
1331 del actions[f] # don't get = keep local deleted
1332
1332
1333 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
1333 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
1334 acceptremote, followcopies, matcher=None,
1334 acceptremote, followcopies, matcher=None,
1335 mergeforce=False):
1335 mergeforce=False):
1336 """Calculate the actions needed to merge mctx into wctx using ancestors"""
1336 """Calculate the actions needed to merge mctx into wctx using ancestors"""
1337 # Avoid cycle.
1337 # Avoid cycle.
1338 from . import sparse
1338 from . import sparse
1339
1339
1340 if len(ancestors) == 1: # default
1340 if len(ancestors) == 1: # default
1341 actions, diverge, renamedelete = manifestmerge(
1341 actions, diverge, renamedelete = manifestmerge(
1342 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
1342 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
1343 acceptremote, followcopies)
1343 acceptremote, followcopies)
1344 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1344 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1345
1345
1346 else: # only when merge.preferancestor=* - the default
1346 else: # only when merge.preferancestor=* - the default
1347 repo.ui.note(
1347 repo.ui.note(
1348 _("note: merging %s and %s using bids from ancestors %s\n") %
1348 _("note: merging %s and %s using bids from ancestors %s\n") %
1349 (wctx, mctx, _(' and ').join(pycompat.bytestr(anc)
1349 (wctx, mctx, _(' and ').join(pycompat.bytestr(anc)
1350 for anc in ancestors)))
1350 for anc in ancestors)))
1351
1351
1352 # Call for bids
1352 # Call for bids
1353 fbids = {} # mapping filename to bids (action method to list af actions)
1353 fbids = {} # mapping filename to bids (action method to list af actions)
1354 diverge, renamedelete = None, None
1354 diverge, renamedelete = None, None
1355 for ancestor in ancestors:
1355 for ancestor in ancestors:
1356 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
1356 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
1357 actions, diverge1, renamedelete1 = manifestmerge(
1357 actions, diverge1, renamedelete1 = manifestmerge(
1358 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
1358 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
1359 acceptremote, followcopies, forcefulldiff=True)
1359 acceptremote, followcopies, forcefulldiff=True)
1360 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1360 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1361
1361
1362 # Track the shortest set of warning on the theory that bid
1362 # Track the shortest set of warning on the theory that bid
1363 # merge will correctly incorporate more information
1363 # merge will correctly incorporate more information
1364 if diverge is None or len(diverge1) < len(diverge):
1364 if diverge is None or len(diverge1) < len(diverge):
1365 diverge = diverge1
1365 diverge = diverge1
1366 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1366 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1367 renamedelete = renamedelete1
1367 renamedelete = renamedelete1
1368
1368
1369 for f, a in sorted(actions.iteritems()):
1369 for f, a in sorted(actions.iteritems()):
1370 m, args, msg = a
1370 m, args, msg = a
1371 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
1371 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
1372 if f in fbids:
1372 if f in fbids:
1373 d = fbids[f]
1373 d = fbids[f]
1374 if m in d:
1374 if m in d:
1375 d[m].append(a)
1375 d[m].append(a)
1376 else:
1376 else:
1377 d[m] = [a]
1377 d[m] = [a]
1378 else:
1378 else:
1379 fbids[f] = {m: [a]}
1379 fbids[f] = {m: [a]}
1380
1380
1381 # Pick the best bid for each file
1381 # Pick the best bid for each file
1382 repo.ui.note(_('\nauction for merging merge bids\n'))
1382 repo.ui.note(_('\nauction for merging merge bids\n'))
1383 actions = {}
1383 actions = {}
1384 for f, bids in sorted(fbids.items()):
1384 for f, bids in sorted(fbids.items()):
1385 # bids is a mapping from action method to list af actions
1385 # bids is a mapping from action method to list af actions
1386 # Consensus?
1386 # Consensus?
1387 if len(bids) == 1: # all bids are the same kind of method
1387 if len(bids) == 1: # all bids are the same kind of method
1388 m, l = list(bids.items())[0]
1388 m, l = list(bids.items())[0]
1389 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1389 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1390 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
1390 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
1391 actions[f] = l[0]
1391 actions[f] = l[0]
1392 continue
1392 continue
1393 # If keep is an option, just do it.
1393 # If keep is an option, just do it.
1394 if ACTION_KEEP in bids:
1394 if ACTION_KEEP in bids:
1395 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1395 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1396 actions[f] = bids[ACTION_KEEP][0]
1396 actions[f] = bids[ACTION_KEEP][0]
1397 continue
1397 continue
1398 # If there are gets and they all agree [how could they not?], do it.
1398 # If there are gets and they all agree [how could they not?], do it.
1399 if ACTION_GET in bids:
1399 if ACTION_GET in bids:
1400 ga0 = bids[ACTION_GET][0]
1400 ga0 = bids[ACTION_GET][0]
1401 if all(a == ga0 for a in bids[ACTION_GET][1:]):
1401 if all(a == ga0 for a in bids[ACTION_GET][1:]):
1402 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1402 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1403 actions[f] = ga0
1403 actions[f] = ga0
1404 continue
1404 continue
1405 # TODO: Consider other simple actions such as mode changes
1405 # TODO: Consider other simple actions such as mode changes
1406 # Handle inefficient democrazy.
1406 # Handle inefficient democrazy.
1407 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1407 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1408 for m, l in sorted(bids.items()):
1408 for m, l in sorted(bids.items()):
1409 for _f, args, msg in l:
1409 for _f, args, msg in l:
1410 repo.ui.note(' %s -> %s\n' % (msg, m))
1410 repo.ui.note(' %s -> %s\n' % (msg, m))
1411 # Pick random action. TODO: Instead, prompt user when resolving
1411 # Pick random action. TODO: Instead, prompt user when resolving
1412 m, l = list(bids.items())[0]
1412 m, l = list(bids.items())[0]
1413 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1413 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1414 (f, m))
1414 (f, m))
1415 actions[f] = l[0]
1415 actions[f] = l[0]
1416 continue
1416 continue
1417 repo.ui.note(_('end of auction\n\n'))
1417 repo.ui.note(_('end of auction\n\n'))
1418
1418
1419 if wctx.rev() is None:
1419 if wctx.rev() is None:
1420 fractions = _forgetremoved(wctx, mctx, branchmerge)
1420 fractions = _forgetremoved(wctx, mctx, branchmerge)
1421 actions.update(fractions)
1421 actions.update(fractions)
1422
1422
1423 prunedactions = sparse.filterupdatesactions(repo, wctx, mctx, branchmerge,
1423 prunedactions = sparse.filterupdatesactions(repo, wctx, mctx, branchmerge,
1424 actions)
1424 actions)
1425 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1425 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1426
1426
1427 return prunedactions, diverge, renamedelete
1427 return prunedactions, diverge, renamedelete
1428
1428
1429 def _getcwd():
1429 def _getcwd():
1430 try:
1430 try:
1431 return encoding.getcwd()
1431 return encoding.getcwd()
1432 except OSError as err:
1432 except OSError as err:
1433 if err.errno == errno.ENOENT:
1433 if err.errno == errno.ENOENT:
1434 return None
1434 return None
1435 raise
1435 raise
1436
1436
1437 def batchremove(repo, wctx, actions):
1437 def batchremove(repo, wctx, actions):
1438 """apply removes to the working directory
1438 """apply removes to the working directory
1439
1439
1440 yields tuples for progress updates
1440 yields tuples for progress updates
1441 """
1441 """
1442 verbose = repo.ui.verbose
1442 verbose = repo.ui.verbose
1443 cwd = _getcwd()
1443 cwd = _getcwd()
1444 i = 0
1444 i = 0
1445 for f, args, msg in actions:
1445 for f, args, msg in actions:
1446 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1446 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1447 if verbose:
1447 if verbose:
1448 repo.ui.note(_("removing %s\n") % f)
1448 repo.ui.note(_("removing %s\n") % f)
1449 wctx[f].audit()
1449 wctx[f].audit()
1450 try:
1450 try:
1451 wctx[f].remove(ignoremissing=True)
1451 wctx[f].remove(ignoremissing=True)
1452 except OSError as inst:
1452 except OSError as inst:
1453 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1453 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1454 (f, inst.strerror))
1454 (f, inst.strerror))
1455 if i == 100:
1455 if i == 100:
1456 yield i, f
1456 yield i, f
1457 i = 0
1457 i = 0
1458 i += 1
1458 i += 1
1459 if i > 0:
1459 if i > 0:
1460 yield i, f
1460 yield i, f
1461
1461
1462 if cwd and not _getcwd():
1462 if cwd and not _getcwd():
1463 # cwd was removed in the course of removing files; print a helpful
1463 # cwd was removed in the course of removing files; print a helpful
1464 # warning.
1464 # warning.
1465 repo.ui.warn(_("current directory was removed\n"
1465 repo.ui.warn(_("current directory was removed\n"
1466 "(consider changing to repo root: %s)\n") % repo.root)
1466 "(consider changing to repo root: %s)\n") % repo.root)
1467
1467
1468 def batchget(repo, mctx, wctx, wantfiledata, actions):
1468 def batchget(repo, mctx, wctx, wantfiledata, actions):
1469 """apply gets to the working directory
1469 """apply gets to the working directory
1470
1470
1471 mctx is the context to get from
1471 mctx is the context to get from
1472
1472
1473 Yields arbitrarily many (False, tuple) for progress updates, followed by
1473 Yields arbitrarily many (False, tuple) for progress updates, followed by
1474 exactly one (True, filedata). When wantfiledata is false, filedata is an
1474 exactly one (True, filedata). When wantfiledata is false, filedata is an
1475 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1475 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1476 mtime) of the file f written for each action.
1476 mtime) of the file f written for each action.
1477 """
1477 """
1478 filedata = {}
1478 filedata = {}
1479 verbose = repo.ui.verbose
1479 verbose = repo.ui.verbose
1480 fctx = mctx.filectx
1480 fctx = mctx.filectx
1481 ui = repo.ui
1481 ui = repo.ui
1482 i = 0
1482 i = 0
1483 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1483 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1484 for f, (flags, backup), msg in actions:
1484 for f, (flags, backup), msg in actions:
1485 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1485 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1486 if verbose:
1486 if verbose:
1487 repo.ui.note(_("getting %s\n") % f)
1487 repo.ui.note(_("getting %s\n") % f)
1488
1488
1489 if backup:
1489 if backup:
1490 # If a file or directory exists with the same name, back that
1490 # If a file or directory exists with the same name, back that
1491 # up. Otherwise, look to see if there is a file that conflicts
1491 # up. Otherwise, look to see if there is a file that conflicts
1492 # with a directory this file is in, and if so, back that up.
1492 # with a directory this file is in, and if so, back that up.
1493 conflicting = f
1493 conflicting = f
1494 if not repo.wvfs.lexists(f):
1494 if not repo.wvfs.lexists(f):
1495 for p in util.finddirs(f):
1495 for p in util.finddirs(f):
1496 if repo.wvfs.isfileorlink(p):
1496 if repo.wvfs.isfileorlink(p):
1497 conflicting = p
1497 conflicting = p
1498 break
1498 break
1499 if repo.wvfs.lexists(conflicting):
1499 if repo.wvfs.lexists(conflicting):
1500 orig = scmutil.backuppath(ui, repo, conflicting)
1500 orig = scmutil.backuppath(ui, repo, conflicting)
1501 util.rename(repo.wjoin(conflicting), orig)
1501 util.rename(repo.wjoin(conflicting), orig)
1502 wfctx = wctx[f]
1502 wfctx = wctx[f]
1503 wfctx.clearunknown()
1503 wfctx.clearunknown()
1504 atomictemp = ui.configbool("experimental", "update.atomic-file")
1504 atomictemp = ui.configbool("experimental", "update.atomic-file")
1505 size = wfctx.write(fctx(f).data(), flags,
1505 size = wfctx.write(fctx(f).data(), flags,
1506 backgroundclose=True,
1506 backgroundclose=True,
1507 atomictemp=atomictemp)
1507 atomictemp=atomictemp)
1508 if wantfiledata:
1508 if wantfiledata:
1509 s = wfctx.lstat()
1509 s = wfctx.lstat()
1510 mode = s.st_mode
1510 mode = s.st_mode
1511 mtime = s[stat.ST_MTIME]
1511 mtime = s[stat.ST_MTIME]
1512 filedata[f] = ((mode, size, mtime)) # for dirstate.normal
1512 filedata[f] = ((mode, size, mtime)) # for dirstate.normal
1513 if i == 100:
1513 if i == 100:
1514 yield False, (i, f)
1514 yield False, (i, f)
1515 i = 0
1515 i = 0
1516 i += 1
1516 i += 1
1517 if i > 0:
1517 if i > 0:
1518 yield False, (i, f)
1518 yield False, (i, f)
1519 yield True, filedata
1519 yield True, filedata
1520
1520
1521 def _prefetchfiles(repo, ctx, actions):
1521 def _prefetchfiles(repo, ctx, actions):
1522 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1522 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1523 of merge actions. ``ctx`` is the context being merged in."""
1523 of merge actions. ``ctx`` is the context being merged in."""
1524
1524
1525 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1525 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1526 # don't touch the context to be merged in. 'cd' is skipped, because
1526 # don't touch the context to be merged in. 'cd' is skipped, because
1527 # changed/deleted never resolves to something from the remote side.
1527 # changed/deleted never resolves to something from the remote side.
1528 oplist = [actions[a] for a in (ACTION_GET, ACTION_DELETED_CHANGED,
1528 oplist = [actions[a] for a in (ACTION_GET, ACTION_DELETED_CHANGED,
1529 ACTION_LOCAL_DIR_RENAME_GET, ACTION_MERGE)]
1529 ACTION_LOCAL_DIR_RENAME_GET, ACTION_MERGE)]
1530 prefetch = scmutil.prefetchfiles
1530 prefetch = scmutil.prefetchfiles
1531 matchfiles = scmutil.matchfiles
1531 matchfiles = scmutil.matchfiles
1532 prefetch(repo, [ctx.rev()],
1532 prefetch(repo, [ctx.rev()],
1533 matchfiles(repo,
1533 matchfiles(repo,
1534 [f for sublist in oplist for f, args, msg in sublist]))
1534 [f for sublist in oplist for f, args, msg in sublist]))
1535
1535
1536 @attr.s(frozen=True)
1536 @attr.s(frozen=True)
1537 class updateresult(object):
1537 class updateresult(object):
1538 updatedcount = attr.ib()
1538 updatedcount = attr.ib()
1539 mergedcount = attr.ib()
1539 mergedcount = attr.ib()
1540 removedcount = attr.ib()
1540 removedcount = attr.ib()
1541 unresolvedcount = attr.ib()
1541 unresolvedcount = attr.ib()
1542
1542
1543 def isempty(self):
1543 def isempty(self):
1544 return not (self.updatedcount or self.mergedcount
1544 return not (self.updatedcount or self.mergedcount
1545 or self.removedcount or self.unresolvedcount)
1545 or self.removedcount or self.unresolvedcount)
1546
1546
1547 def emptyactions():
1547 def emptyactions():
1548 """create an actions dict, to be populated and passed to applyupdates()"""
1548 """create an actions dict, to be populated and passed to applyupdates()"""
1549 return dict((m, [])
1549 return dict((m, [])
1550 for m in (
1550 for m in (
1551 ACTION_ADD,
1551 ACTION_ADD,
1552 ACTION_ADD_MODIFIED,
1552 ACTION_ADD_MODIFIED,
1553 ACTION_FORGET,
1553 ACTION_FORGET,
1554 ACTION_GET,
1554 ACTION_GET,
1555 ACTION_CHANGED_DELETED,
1555 ACTION_CHANGED_DELETED,
1556 ACTION_DELETED_CHANGED,
1556 ACTION_DELETED_CHANGED,
1557 ACTION_REMOVE,
1557 ACTION_REMOVE,
1558 ACTION_DIR_RENAME_MOVE_LOCAL,
1558 ACTION_DIR_RENAME_MOVE_LOCAL,
1559 ACTION_LOCAL_DIR_RENAME_GET,
1559 ACTION_LOCAL_DIR_RENAME_GET,
1560 ACTION_MERGE,
1560 ACTION_MERGE,
1561 ACTION_EXEC,
1561 ACTION_EXEC,
1562 ACTION_KEEP,
1562 ACTION_KEEP,
1563 ACTION_PATH_CONFLICT,
1563 ACTION_PATH_CONFLICT,
1564 ACTION_PATH_CONFLICT_RESOLVE))
1564 ACTION_PATH_CONFLICT_RESOLVE))
1565
1565
1566 def applyupdates(repo, actions, wctx, mctx, overwrite, wantfiledata,
1566 def applyupdates(repo, actions, wctx, mctx, overwrite, wantfiledata,
1567 labels=None):
1567 labels=None):
1568 """apply the merge action list to the working directory
1568 """apply the merge action list to the working directory
1569
1569
1570 wctx is the working copy context
1570 wctx is the working copy context
1571 mctx is the context to be merged into the working copy
1571 mctx is the context to be merged into the working copy
1572
1572
1573 Return a tuple of (counts, filedata), where counts is a tuple
1573 Return a tuple of (counts, filedata), where counts is a tuple
1574 (updated, merged, removed, unresolved) that describes how many
1574 (updated, merged, removed, unresolved) that describes how many
1575 files were affected by the update, and filedata is as described in
1575 files were affected by the update, and filedata is as described in
1576 batchget.
1576 batchget.
1577 """
1577 """
1578
1578
1579 _prefetchfiles(repo, mctx, actions)
1579 _prefetchfiles(repo, mctx, actions)
1580
1580
1581 updated, merged, removed = 0, 0, 0
1581 updated, merged, removed = 0, 0, 0
1582 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1582 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1583 moves = []
1583 moves = []
1584 for m, l in actions.items():
1584 for m, l in actions.items():
1585 l.sort()
1585 l.sort()
1586
1586
1587 # 'cd' and 'dc' actions are treated like other merge conflicts
1587 # 'cd' and 'dc' actions are treated like other merge conflicts
1588 mergeactions = sorted(actions[ACTION_CHANGED_DELETED])
1588 mergeactions = sorted(actions[ACTION_CHANGED_DELETED])
1589 mergeactions.extend(sorted(actions[ACTION_DELETED_CHANGED]))
1589 mergeactions.extend(sorted(actions[ACTION_DELETED_CHANGED]))
1590 mergeactions.extend(actions[ACTION_MERGE])
1590 mergeactions.extend(actions[ACTION_MERGE])
1591 for f, args, msg in mergeactions:
1591 for f, args, msg in mergeactions:
1592 f1, f2, fa, move, anc = args
1592 f1, f2, fa, move, anc = args
1593 if f == '.hgsubstate': # merged internally
1593 if f == '.hgsubstate': # merged internally
1594 continue
1594 continue
1595 if f1 is None:
1595 if f1 is None:
1596 fcl = filemerge.absentfilectx(wctx, fa)
1596 fcl = filemerge.absentfilectx(wctx, fa)
1597 else:
1597 else:
1598 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1598 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1599 fcl = wctx[f1]
1599 fcl = wctx[f1]
1600 if f2 is None:
1600 if f2 is None:
1601 fco = filemerge.absentfilectx(mctx, fa)
1601 fco = filemerge.absentfilectx(mctx, fa)
1602 else:
1602 else:
1603 fco = mctx[f2]
1603 fco = mctx[f2]
1604 actx = repo[anc]
1604 actx = repo[anc]
1605 if fa in actx:
1605 if fa in actx:
1606 fca = actx[fa]
1606 fca = actx[fa]
1607 else:
1607 else:
1608 # TODO: move to absentfilectx
1608 # TODO: move to absentfilectx
1609 fca = repo.filectx(f1, fileid=nullrev)
1609 fca = repo.filectx(f1, fileid=nullrev)
1610 ms.add(fcl, fco, fca, f)
1610 ms.add(fcl, fco, fca, f)
1611 if f1 != f and move:
1611 if f1 != f and move:
1612 moves.append(f1)
1612 moves.append(f1)
1613
1613
1614 # remove renamed files after safely stored
1614 # remove renamed files after safely stored
1615 for f in moves:
1615 for f in moves:
1616 if wctx[f].lexists():
1616 if wctx[f].lexists():
1617 repo.ui.debug("removing %s\n" % f)
1617 repo.ui.debug("removing %s\n" % f)
1618 wctx[f].audit()
1618 wctx[f].audit()
1619 wctx[f].remove()
1619 wctx[f].remove()
1620
1620
1621 numupdates = sum(len(l) for m, l in actions.items()
1621 numupdates = sum(len(l) for m, l in actions.items()
1622 if m != ACTION_KEEP)
1622 if m != ACTION_KEEP)
1623 progress = repo.ui.makeprogress(_('updating'), unit=_('files'),
1623 progress = repo.ui.makeprogress(_('updating'), unit=_('files'),
1624 total=numupdates)
1624 total=numupdates)
1625
1625
1626 if [a for a in actions[ACTION_REMOVE] if a[0] == '.hgsubstate']:
1626 if [a for a in actions[ACTION_REMOVE] if a[0] == '.hgsubstate']:
1627 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1627 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1628
1628
1629 # record path conflicts
1629 # record path conflicts
1630 for f, args, msg in actions[ACTION_PATH_CONFLICT]:
1630 for f, args, msg in actions[ACTION_PATH_CONFLICT]:
1631 f1, fo = args
1631 f1, fo = args
1632 s = repo.ui.status
1632 s = repo.ui.status
1633 s(_("%s: path conflict - a file or link has the same name as a "
1633 s(_("%s: path conflict - a file or link has the same name as a "
1634 "directory\n") % f)
1634 "directory\n") % f)
1635 if fo == 'l':
1635 if fo == 'l':
1636 s(_("the local file has been renamed to %s\n") % f1)
1636 s(_("the local file has been renamed to %s\n") % f1)
1637 else:
1637 else:
1638 s(_("the remote file has been renamed to %s\n") % f1)
1638 s(_("the remote file has been renamed to %s\n") % f1)
1639 s(_("resolve manually then use 'hg resolve --mark %s'\n") % f)
1639 s(_("resolve manually then use 'hg resolve --mark %s'\n") % f)
1640 ms.addpath(f, f1, fo)
1640 ms.addpath(f, f1, fo)
1641 progress.increment(item=f)
1641 progress.increment(item=f)
1642
1642
1643 # When merging in-memory, we can't support worker processes, so set the
1643 # When merging in-memory, we can't support worker processes, so set the
1644 # per-item cost at 0 in that case.
1644 # per-item cost at 0 in that case.
1645 cost = 0 if wctx.isinmemory() else 0.001
1645 cost = 0 if wctx.isinmemory() else 0.001
1646
1646
1647 # remove in parallel (must come before resolving path conflicts and getting)
1647 # remove in parallel (must come before resolving path conflicts and getting)
1648 prog = worker.worker(repo.ui, cost, batchremove, (repo, wctx),
1648 prog = worker.worker(repo.ui, cost, batchremove, (repo, wctx),
1649 actions[ACTION_REMOVE])
1649 actions[ACTION_REMOVE])
1650 for i, item in prog:
1650 for i, item in prog:
1651 progress.increment(step=i, item=item)
1651 progress.increment(step=i, item=item)
1652 removed = len(actions[ACTION_REMOVE])
1652 removed = len(actions[ACTION_REMOVE])
1653
1653
1654 # resolve path conflicts (must come before getting)
1654 # resolve path conflicts (must come before getting)
1655 for f, args, msg in actions[ACTION_PATH_CONFLICT_RESOLVE]:
1655 for f, args, msg in actions[ACTION_PATH_CONFLICT_RESOLVE]:
1656 repo.ui.debug(" %s: %s -> pr\n" % (f, msg))
1656 repo.ui.debug(" %s: %s -> pr\n" % (f, msg))
1657 f0, = args
1657 f0, = args
1658 if wctx[f0].lexists():
1658 if wctx[f0].lexists():
1659 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1659 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1660 wctx[f].audit()
1660 wctx[f].audit()
1661 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1661 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1662 wctx[f0].remove()
1662 wctx[f0].remove()
1663 progress.increment(item=f)
1663 progress.increment(item=f)
1664
1664
1665 # get in parallel.
1665 # get in parallel.
1666 threadsafe = repo.ui.configbool('experimental',
1666 threadsafe = repo.ui.configbool('experimental',
1667 'worker.wdir-get-thread-safe')
1667 'worker.wdir-get-thread-safe')
1668 prog = worker.worker(repo.ui, cost, batchget,
1668 prog = worker.worker(repo.ui, cost, batchget,
1669 (repo, mctx, wctx, wantfiledata),
1669 (repo, mctx, wctx, wantfiledata),
1670 actions[ACTION_GET],
1670 actions[ACTION_GET],
1671 threadsafe=threadsafe,
1671 threadsafe=threadsafe,
1672 hasretval=True)
1672 hasretval=True)
1673 getfiledata = {}
1673 getfiledata = {}
1674 for final, res in prog:
1674 for final, res in prog:
1675 if final:
1675 if final:
1676 getfiledata = res
1676 getfiledata = res
1677 else:
1677 else:
1678 i, item = res
1678 i, item = res
1679 progress.increment(step=i, item=item)
1679 progress.increment(step=i, item=item)
1680 updated = len(actions[ACTION_GET])
1680 updated = len(actions[ACTION_GET])
1681
1681
1682 if [a for a in actions[ACTION_GET] if a[0] == '.hgsubstate']:
1682 if [a for a in actions[ACTION_GET] if a[0] == '.hgsubstate']:
1683 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1683 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1684
1684
1685 # forget (manifest only, just log it) (must come first)
1685 # forget (manifest only, just log it) (must come first)
1686 for f, args, msg in actions[ACTION_FORGET]:
1686 for f, args, msg in actions[ACTION_FORGET]:
1687 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1687 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1688 progress.increment(item=f)
1688 progress.increment(item=f)
1689
1689
1690 # re-add (manifest only, just log it)
1690 # re-add (manifest only, just log it)
1691 for f, args, msg in actions[ACTION_ADD]:
1691 for f, args, msg in actions[ACTION_ADD]:
1692 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1692 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1693 progress.increment(item=f)
1693 progress.increment(item=f)
1694
1694
1695 # re-add/mark as modified (manifest only, just log it)
1695 # re-add/mark as modified (manifest only, just log it)
1696 for f, args, msg in actions[ACTION_ADD_MODIFIED]:
1696 for f, args, msg in actions[ACTION_ADD_MODIFIED]:
1697 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1697 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1698 progress.increment(item=f)
1698 progress.increment(item=f)
1699
1699
1700 # keep (noop, just log it)
1700 # keep (noop, just log it)
1701 for f, args, msg in actions[ACTION_KEEP]:
1701 for f, args, msg in actions[ACTION_KEEP]:
1702 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1702 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1703 # no progress
1703 # no progress
1704
1704
1705 # directory rename, move local
1705 # directory rename, move local
1706 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
1706 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
1707 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1707 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1708 progress.increment(item=f)
1708 progress.increment(item=f)
1709 f0, flags = args
1709 f0, flags = args
1710 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1710 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1711 wctx[f].audit()
1711 wctx[f].audit()
1712 wctx[f].write(wctx.filectx(f0).data(), flags)
1712 wctx[f].write(wctx.filectx(f0).data(), flags)
1713 wctx[f0].remove()
1713 wctx[f0].remove()
1714 updated += 1
1714 updated += 1
1715
1715
1716 # local directory rename, get
1716 # local directory rename, get
1717 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
1717 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
1718 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1718 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1719 progress.increment(item=f)
1719 progress.increment(item=f)
1720 f0, flags = args
1720 f0, flags = args
1721 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1721 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1722 wctx[f].write(mctx.filectx(f0).data(), flags)
1722 wctx[f].write(mctx.filectx(f0).data(), flags)
1723 updated += 1
1723 updated += 1
1724
1724
1725 # exec
1725 # exec
1726 for f, args, msg in actions[ACTION_EXEC]:
1726 for f, args, msg in actions[ACTION_EXEC]:
1727 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1727 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1728 progress.increment(item=f)
1728 progress.increment(item=f)
1729 flags, = args
1729 flags, = args
1730 wctx[f].audit()
1730 wctx[f].audit()
1731 wctx[f].setflags('l' in flags, 'x' in flags)
1731 wctx[f].setflags('l' in flags, 'x' in flags)
1732 updated += 1
1732 updated += 1
1733
1733
1734 # the ordering is important here -- ms.mergedriver will raise if the merge
1734 # the ordering is important here -- ms.mergedriver will raise if the merge
1735 # driver has changed, and we want to be able to bypass it when overwrite is
1735 # driver has changed, and we want to be able to bypass it when overwrite is
1736 # True
1736 # True
1737 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1737 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1738
1738
1739 if usemergedriver:
1739 if usemergedriver:
1740 if wctx.isinmemory():
1740 if wctx.isinmemory():
1741 raise error.InMemoryMergeConflictsError("in-memory merge does not "
1741 raise error.InMemoryMergeConflictsError("in-memory merge does not "
1742 "support mergedriver")
1742 "support mergedriver")
1743 ms.commit()
1743 ms.commit()
1744 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1744 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1745 # the driver might leave some files unresolved
1745 # the driver might leave some files unresolved
1746 unresolvedf = set(ms.unresolved())
1746 unresolvedf = set(ms.unresolved())
1747 if not proceed:
1747 if not proceed:
1748 # XXX setting unresolved to at least 1 is a hack to make sure we
1748 # XXX setting unresolved to at least 1 is a hack to make sure we
1749 # error out
1749 # error out
1750 return updateresult(updated, merged, removed,
1750 return updateresult(updated, merged, removed,
1751 max(len(unresolvedf), 1))
1751 max(len(unresolvedf), 1))
1752 newactions = []
1752 newactions = []
1753 for f, args, msg in mergeactions:
1753 for f, args, msg in mergeactions:
1754 if f in unresolvedf:
1754 if f in unresolvedf:
1755 newactions.append((f, args, msg))
1755 newactions.append((f, args, msg))
1756 mergeactions = newactions
1756 mergeactions = newactions
1757
1757
1758 try:
1758 try:
1759 # premerge
1759 # premerge
1760 tocomplete = []
1760 tocomplete = []
1761 for f, args, msg in mergeactions:
1761 for f, args, msg in mergeactions:
1762 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1762 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1763 progress.increment(item=f)
1763 progress.increment(item=f)
1764 if f == '.hgsubstate': # subrepo states need updating
1764 if f == '.hgsubstate': # subrepo states need updating
1765 subrepoutil.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1765 subrepoutil.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1766 overwrite, labels)
1766 overwrite, labels)
1767 continue
1767 continue
1768 wctx[f].audit()
1768 wctx[f].audit()
1769 complete, r = ms.preresolve(f, wctx)
1769 complete, r = ms.preresolve(f, wctx)
1770 if not complete:
1770 if not complete:
1771 numupdates += 1
1771 numupdates += 1
1772 tocomplete.append((f, args, msg))
1772 tocomplete.append((f, args, msg))
1773
1773
1774 # merge
1774 # merge
1775 for f, args, msg in tocomplete:
1775 for f, args, msg in tocomplete:
1776 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1776 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1777 progress.increment(item=f, total=numupdates)
1777 progress.increment(item=f, total=numupdates)
1778 ms.resolve(f, wctx)
1778 ms.resolve(f, wctx)
1779
1779
1780 finally:
1780 finally:
1781 ms.commit()
1781 ms.commit()
1782
1782
1783 unresolved = ms.unresolvedcount()
1783 unresolved = ms.unresolvedcount()
1784
1784
1785 if (usemergedriver and not unresolved
1785 if (usemergedriver and not unresolved
1786 and ms.mdstate() != MERGE_DRIVER_STATE_SUCCESS):
1786 and ms.mdstate() != MERGE_DRIVER_STATE_SUCCESS):
1787 if not driverconclude(repo, ms, wctx, labels=labels):
1787 if not driverconclude(repo, ms, wctx, labels=labels):
1788 # XXX setting unresolved to at least 1 is a hack to make sure we
1788 # XXX setting unresolved to at least 1 is a hack to make sure we
1789 # error out
1789 # error out
1790 unresolved = max(unresolved, 1)
1790 unresolved = max(unresolved, 1)
1791
1791
1792 ms.commit()
1792 ms.commit()
1793
1793
1794 msupdated, msmerged, msremoved = ms.counts()
1794 msupdated, msmerged, msremoved = ms.counts()
1795 updated += msupdated
1795 updated += msupdated
1796 merged += msmerged
1796 merged += msmerged
1797 removed += msremoved
1797 removed += msremoved
1798
1798
1799 extraactions = ms.actions()
1799 extraactions = ms.actions()
1800 if extraactions:
1800 if extraactions:
1801 mfiles = set(a[0] for a in actions[ACTION_MERGE])
1801 mfiles = set(a[0] for a in actions[ACTION_MERGE])
1802 for k, acts in extraactions.iteritems():
1802 for k, acts in extraactions.iteritems():
1803 actions[k].extend(acts)
1803 actions[k].extend(acts)
1804 if k == ACTION_GET and wantfiledata:
1804 if k == ACTION_GET and wantfiledata:
1805 # no filedata until mergestate is updated to provide it
1805 # no filedata until mergestate is updated to provide it
1806 for a in acts:
1806 for a in acts:
1807 getfiledata[a[0]] = None
1807 getfiledata[a[0]] = None
1808 # Remove these files from actions[ACTION_MERGE] as well. This is
1808 # Remove these files from actions[ACTION_MERGE] as well. This is
1809 # important because in recordupdates, files in actions[ACTION_MERGE]
1809 # important because in recordupdates, files in actions[ACTION_MERGE]
1810 # are processed after files in other actions, and the merge driver
1810 # are processed after files in other actions, and the merge driver
1811 # might add files to those actions via extraactions above. This can
1811 # might add files to those actions via extraactions above. This can
1812 # lead to a file being recorded twice, with poor results. This is
1812 # lead to a file being recorded twice, with poor results. This is
1813 # especially problematic for actions[ACTION_REMOVE] (currently only
1813 # especially problematic for actions[ACTION_REMOVE] (currently only
1814 # possible with the merge driver in the initial merge process;
1814 # possible with the merge driver in the initial merge process;
1815 # interrupted merges don't go through this flow).
1815 # interrupted merges don't go through this flow).
1816 #
1816 #
1817 # The real fix here is to have indexes by both file and action so
1817 # The real fix here is to have indexes by both file and action so
1818 # that when the action for a file is changed it is automatically
1818 # that when the action for a file is changed it is automatically
1819 # reflected in the other action lists. But that involves a more
1819 # reflected in the other action lists. But that involves a more
1820 # complex data structure, so this will do for now.
1820 # complex data structure, so this will do for now.
1821 #
1821 #
1822 # We don't need to do the same operation for 'dc' and 'cd' because
1822 # We don't need to do the same operation for 'dc' and 'cd' because
1823 # those lists aren't consulted again.
1823 # those lists aren't consulted again.
1824 mfiles.difference_update(a[0] for a in acts)
1824 mfiles.difference_update(a[0] for a in acts)
1825
1825
1826 actions[ACTION_MERGE] = [a for a in actions[ACTION_MERGE]
1826 actions[ACTION_MERGE] = [a for a in actions[ACTION_MERGE]
1827 if a[0] in mfiles]
1827 if a[0] in mfiles]
1828
1828
1829 progress.complete()
1829 progress.complete()
1830 assert len(getfiledata) == (len(actions[ACTION_GET]) if wantfiledata else 0)
1830 assert len(getfiledata) == (len(actions[ACTION_GET]) if wantfiledata else 0)
1831 return updateresult(updated, merged, removed, unresolved), getfiledata
1831 return updateresult(updated, merged, removed, unresolved), getfiledata
1832
1832
1833 def recordupdates(repo, actions, branchmerge, getfiledata):
1833 def recordupdates(repo, actions, branchmerge, getfiledata):
1834 "record merge actions to the dirstate"
1834 "record merge actions to the dirstate"
1835 # remove (must come first)
1835 # remove (must come first)
1836 for f, args, msg in actions.get(ACTION_REMOVE, []):
1836 for f, args, msg in actions.get(ACTION_REMOVE, []):
1837 if branchmerge:
1837 if branchmerge:
1838 repo.dirstate.remove(f)
1838 repo.dirstate.remove(f)
1839 else:
1839 else:
1840 repo.dirstate.drop(f)
1840 repo.dirstate.drop(f)
1841
1841
1842 # forget (must come first)
1842 # forget (must come first)
1843 for f, args, msg in actions.get(ACTION_FORGET, []):
1843 for f, args, msg in actions.get(ACTION_FORGET, []):
1844 repo.dirstate.drop(f)
1844 repo.dirstate.drop(f)
1845
1845
1846 # resolve path conflicts
1846 # resolve path conflicts
1847 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
1847 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
1848 f0, = args
1848 f0, = args
1849 origf0 = repo.dirstate.copied(f0) or f0
1849 origf0 = repo.dirstate.copied(f0) or f0
1850 repo.dirstate.add(f)
1850 repo.dirstate.add(f)
1851 repo.dirstate.copy(origf0, f)
1851 repo.dirstate.copy(origf0, f)
1852 if f0 == origf0:
1852 if f0 == origf0:
1853 repo.dirstate.remove(f0)
1853 repo.dirstate.remove(f0)
1854 else:
1854 else:
1855 repo.dirstate.drop(f0)
1855 repo.dirstate.drop(f0)
1856
1856
1857 # re-add
1857 # re-add
1858 for f, args, msg in actions.get(ACTION_ADD, []):
1858 for f, args, msg in actions.get(ACTION_ADD, []):
1859 repo.dirstate.add(f)
1859 repo.dirstate.add(f)
1860
1860
1861 # re-add/mark as modified
1861 # re-add/mark as modified
1862 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
1862 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
1863 if branchmerge:
1863 if branchmerge:
1864 repo.dirstate.normallookup(f)
1864 repo.dirstate.normallookup(f)
1865 else:
1865 else:
1866 repo.dirstate.add(f)
1866 repo.dirstate.add(f)
1867
1867
1868 # exec change
1868 # exec change
1869 for f, args, msg in actions.get(ACTION_EXEC, []):
1869 for f, args, msg in actions.get(ACTION_EXEC, []):
1870 repo.dirstate.normallookup(f)
1870 repo.dirstate.normallookup(f)
1871
1871
1872 # keep
1872 # keep
1873 for f, args, msg in actions.get(ACTION_KEEP, []):
1873 for f, args, msg in actions.get(ACTION_KEEP, []):
1874 pass
1874 pass
1875
1875
1876 # get
1876 # get
1877 for f, args, msg in actions.get(ACTION_GET, []):
1877 for f, args, msg in actions.get(ACTION_GET, []):
1878 if branchmerge:
1878 if branchmerge:
1879 repo.dirstate.otherparent(f)
1879 repo.dirstate.otherparent(f)
1880 else:
1880 else:
1881 parentfiledata = getfiledata[f] if getfiledata else None
1881 parentfiledata = getfiledata[f] if getfiledata else None
1882 repo.dirstate.normal(f, parentfiledata=parentfiledata)
1882 repo.dirstate.normal(f, parentfiledata=parentfiledata)
1883
1883
1884 # merge
1884 # merge
1885 for f, args, msg in actions.get(ACTION_MERGE, []):
1885 for f, args, msg in actions.get(ACTION_MERGE, []):
1886 f1, f2, fa, move, anc = args
1886 f1, f2, fa, move, anc = args
1887 if branchmerge:
1887 if branchmerge:
1888 # We've done a branch merge, mark this file as merged
1888 # We've done a branch merge, mark this file as merged
1889 # so that we properly record the merger later
1889 # so that we properly record the merger later
1890 repo.dirstate.merge(f)
1890 repo.dirstate.merge(f)
1891 if f1 != f2: # copy/rename
1891 if f1 != f2: # copy/rename
1892 if move:
1892 if move:
1893 repo.dirstate.remove(f1)
1893 repo.dirstate.remove(f1)
1894 if f1 != f:
1894 if f1 != f:
1895 repo.dirstate.copy(f1, f)
1895 repo.dirstate.copy(f1, f)
1896 else:
1896 else:
1897 repo.dirstate.copy(f2, f)
1897 repo.dirstate.copy(f2, f)
1898 else:
1898 else:
1899 # We've update-merged a locally modified file, so
1899 # We've update-merged a locally modified file, so
1900 # we set the dirstate to emulate a normal checkout
1900 # we set the dirstate to emulate a normal checkout
1901 # of that file some time in the past. Thus our
1901 # of that file some time in the past. Thus our
1902 # merge will appear as a normal local file
1902 # merge will appear as a normal local file
1903 # modification.
1903 # modification.
1904 if f2 == f: # file not locally copied/moved
1904 if f2 == f: # file not locally copied/moved
1905 repo.dirstate.normallookup(f)
1905 repo.dirstate.normallookup(f)
1906 if move:
1906 if move:
1907 repo.dirstate.drop(f1)
1907 repo.dirstate.drop(f1)
1908
1908
1909 # directory rename, move local
1909 # directory rename, move local
1910 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
1910 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
1911 f0, flag = args
1911 f0, flag = args
1912 if branchmerge:
1912 if branchmerge:
1913 repo.dirstate.add(f)
1913 repo.dirstate.add(f)
1914 repo.dirstate.remove(f0)
1914 repo.dirstate.remove(f0)
1915 repo.dirstate.copy(f0, f)
1915 repo.dirstate.copy(f0, f)
1916 else:
1916 else:
1917 repo.dirstate.normal(f)
1917 repo.dirstate.normal(f)
1918 repo.dirstate.drop(f0)
1918 repo.dirstate.drop(f0)
1919
1919
1920 # directory rename, get
1920 # directory rename, get
1921 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
1921 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
1922 f0, flag = args
1922 f0, flag = args
1923 if branchmerge:
1923 if branchmerge:
1924 repo.dirstate.add(f)
1924 repo.dirstate.add(f)
1925 repo.dirstate.copy(f0, f)
1925 repo.dirstate.copy(f0, f)
1926 else:
1926 else:
1927 repo.dirstate.normal(f)
1927 repo.dirstate.normal(f)
1928
1928
1929 def update(repo, node, branchmerge, force, ancestor=None,
1929 def update(repo, node, branchmerge, force, ancestor=None,
1930 mergeancestor=False, labels=None, matcher=None, mergeforce=False,
1930 mergeancestor=False, labels=None, matcher=None, mergeforce=False,
1931 updatecheck=None, wc=None):
1931 updatecheck=None, wc=None):
1932 """
1932 """
1933 Perform a merge between the working directory and the given node
1933 Perform a merge between the working directory and the given node
1934
1934
1935 node = the node to update to
1935 node = the node to update to
1936 branchmerge = whether to merge between branches
1936 branchmerge = whether to merge between branches
1937 force = whether to force branch merging or file overwriting
1937 force = whether to force branch merging or file overwriting
1938 matcher = a matcher to filter file lists (dirstate not updated)
1938 matcher = a matcher to filter file lists (dirstate not updated)
1939 mergeancestor = whether it is merging with an ancestor. If true,
1939 mergeancestor = whether it is merging with an ancestor. If true,
1940 we should accept the incoming changes for any prompts that occur.
1940 we should accept the incoming changes for any prompts that occur.
1941 If false, merging with an ancestor (fast-forward) is only allowed
1941 If false, merging with an ancestor (fast-forward) is only allowed
1942 between different named branches. This flag is used by rebase extension
1942 between different named branches. This flag is used by rebase extension
1943 as a temporary fix and should be avoided in general.
1943 as a temporary fix and should be avoided in general.
1944 labels = labels to use for base, local and other
1944 labels = labels to use for base, local and other
1945 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1945 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1946 this is True, then 'force' should be True as well.
1946 this is True, then 'force' should be True as well.
1947
1947
1948 The table below shows all the behaviors of the update command given the
1948 The table below shows all the behaviors of the update command given the
1949 -c/--check and -C/--clean or no options, whether the working directory is
1949 -c/--check and -C/--clean or no options, whether the working directory is
1950 dirty, whether a revision is specified, and the relationship of the parent
1950 dirty, whether a revision is specified, and the relationship of the parent
1951 rev to the target rev (linear or not). Match from top first. The -n
1951 rev to the target rev (linear or not). Match from top first. The -n
1952 option doesn't exist on the command line, but represents the
1952 option doesn't exist on the command line, but represents the
1953 experimental.updatecheck=noconflict option.
1953 experimental.updatecheck=noconflict option.
1954
1954
1955 This logic is tested by test-update-branches.t.
1955 This logic is tested by test-update-branches.t.
1956
1956
1957 -c -C -n -m dirty rev linear | result
1957 -c -C -n -m dirty rev linear | result
1958 y y * * * * * | (1)
1958 y y * * * * * | (1)
1959 y * y * * * * | (1)
1959 y * y * * * * | (1)
1960 y * * y * * * | (1)
1960 y * * y * * * | (1)
1961 * y y * * * * | (1)
1961 * y y * * * * | (1)
1962 * y * y * * * | (1)
1962 * y * y * * * | (1)
1963 * * y y * * * | (1)
1963 * * y y * * * | (1)
1964 * * * * * n n | x
1964 * * * * * n n | x
1965 * * * * n * * | ok
1965 * * * * n * * | ok
1966 n n n n y * y | merge
1966 n n n n y * y | merge
1967 n n n n y y n | (2)
1967 n n n n y y n | (2)
1968 n n n y y * * | merge
1968 n n n y y * * | merge
1969 n n y n y * * | merge if no conflict
1969 n n y n y * * | merge if no conflict
1970 n y n n y * * | discard
1970 n y n n y * * | discard
1971 y n n n y * * | (3)
1971 y n n n y * * | (3)
1972
1972
1973 x = can't happen
1973 x = can't happen
1974 * = don't-care
1974 * = don't-care
1975 1 = incompatible options (checked in commands.py)
1975 1 = incompatible options (checked in commands.py)
1976 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1976 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1977 3 = abort: uncommitted changes (checked in commands.py)
1977 3 = abort: uncommitted changes (checked in commands.py)
1978
1978
1979 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1979 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1980 to repo[None] if None is passed.
1980 to repo[None] if None is passed.
1981
1981
1982 Return the same tuple as applyupdates().
1982 Return the same tuple as applyupdates().
1983 """
1983 """
1984 # Avoid cycle.
1984 # Avoid cycle.
1985 from . import sparse
1985 from . import sparse
1986
1986
1987 # This function used to find the default destination if node was None, but
1987 # This function used to find the default destination if node was None, but
1988 # that's now in destutil.py.
1988 # that's now in destutil.py.
1989 assert node is not None
1989 assert node is not None
1990 if not branchmerge and not force:
1990 if not branchmerge and not force:
1991 # TODO: remove the default once all callers that pass branchmerge=False
1991 # TODO: remove the default once all callers that pass branchmerge=False
1992 # and force=False pass a value for updatecheck. We may want to allow
1992 # and force=False pass a value for updatecheck. We may want to allow
1993 # updatecheck='abort' to better suppport some of these callers.
1993 # updatecheck='abort' to better suppport some of these callers.
1994 if updatecheck is None:
1994 if updatecheck is None:
1995 updatecheck = 'linear'
1995 updatecheck = 'linear'
1996 assert updatecheck in ('none', 'linear', 'noconflict')
1996 assert updatecheck in ('none', 'linear', 'noconflict')
1997 # If we're doing a partial update, we need to skip updating
1997 # If we're doing a partial update, we need to skip updating
1998 # the dirstate, so make a note of any partial-ness to the
1998 # the dirstate, so make a note of any partial-ness to the
1999 # update here.
1999 # update here.
2000 if matcher is None or matcher.always():
2000 if matcher is None or matcher.always():
2001 partial = False
2001 partial = False
2002 else:
2002 else:
2003 partial = True
2003 partial = True
2004 with repo.wlock():
2004 with repo.wlock():
2005 if wc is None:
2005 if wc is None:
2006 wc = repo[None]
2006 wc = repo[None]
2007 pl = wc.parents()
2007 pl = wc.parents()
2008 p1 = pl[0]
2008 p1 = pl[0]
2009 p2 = repo[node]
2009 p2 = repo[node]
2010 if ancestor is not None:
2010 if ancestor is not None:
2011 pas = [repo[ancestor]]
2011 pas = [repo[ancestor]]
2012 else:
2012 else:
2013 if repo.ui.configlist('merge', 'preferancestor') == ['*']:
2013 if repo.ui.configlist('merge', 'preferancestor') == ['*']:
2014 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
2014 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
2015 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
2015 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
2016 else:
2016 else:
2017 pas = [p1.ancestor(p2, warn=branchmerge)]
2017 pas = [p1.ancestor(p2, warn=branchmerge)]
2018
2018
2019 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
2019 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
2020
2020
2021 overwrite = force and not branchmerge
2021 overwrite = force and not branchmerge
2022 ### check phase
2022 ### check phase
2023 if not overwrite:
2023 if not overwrite:
2024 if len(pl) > 1:
2024 if len(pl) > 1:
2025 raise error.Abort(_("outstanding uncommitted merge"))
2025 raise error.Abort(_("outstanding uncommitted merge"))
2026 ms = mergestate.read(repo)
2026 ms = mergestate.read(repo)
2027 if list(ms.unresolved()):
2027 if list(ms.unresolved()):
2028 raise error.Abort(_("outstanding merge conflicts"),
2028 raise error.Abort(_("outstanding merge conflicts"),
2029 hint=_("use 'hg resolve' to resolve"))
2029 hint=_("use 'hg resolve' to resolve"))
2030 if branchmerge:
2030 if branchmerge:
2031 if pas == [p2]:
2031 if pas == [p2]:
2032 raise error.Abort(_("merging with a working directory ancestor"
2032 raise error.Abort(_("merging with a working directory ancestor"
2033 " has no effect"))
2033 " has no effect"))
2034 elif pas == [p1]:
2034 elif pas == [p1]:
2035 if not mergeancestor and wc.branch() == p2.branch():
2035 if not mergeancestor and wc.branch() == p2.branch():
2036 raise error.Abort(_("nothing to merge"),
2036 raise error.Abort(_("nothing to merge"),
2037 hint=_("use 'hg update' "
2037 hint=_("use 'hg update' "
2038 "or check 'hg heads'"))
2038 "or check 'hg heads'"))
2039 if not force and (wc.files() or wc.deleted()):
2039 if not force and (wc.files() or wc.deleted()):
2040 raise error.Abort(_("uncommitted changes"),
2040 raise error.Abort(_("uncommitted changes"),
2041 hint=_("use 'hg status' to list changes"))
2041 hint=_("use 'hg status' to list changes"))
2042 if not wc.isinmemory():
2042 if not wc.isinmemory():
2043 for s in sorted(wc.substate):
2043 for s in sorted(wc.substate):
2044 wc.sub(s).bailifchanged()
2044 wc.sub(s).bailifchanged()
2045
2045
2046 elif not overwrite:
2046 elif not overwrite:
2047 if p1 == p2: # no-op update
2047 if p1 == p2: # no-op update
2048 # call the hooks and exit early
2048 # call the hooks and exit early
2049 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
2049 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
2050 repo.hook('update', parent1=xp2, parent2='', error=0)
2050 repo.hook('update', parent1=xp2, parent2='', error=0)
2051 return updateresult(0, 0, 0, 0)
2051 return updateresult(0, 0, 0, 0)
2052
2052
2053 if (updatecheck == 'linear' and
2053 if (updatecheck == 'linear' and
2054 pas not in ([p1], [p2])): # nonlinear
2054 pas not in ([p1], [p2])): # nonlinear
2055 dirty = wc.dirty(missing=True)
2055 dirty = wc.dirty(missing=True)
2056 if dirty:
2056 if dirty:
2057 # Branching is a bit strange to ensure we do the minimal
2057 # Branching is a bit strange to ensure we do the minimal
2058 # amount of call to obsutil.foreground.
2058 # amount of call to obsutil.foreground.
2059 foreground = obsutil.foreground(repo, [p1.node()])
2059 foreground = obsutil.foreground(repo, [p1.node()])
2060 # note: the <node> variable contains a random identifier
2060 # note: the <node> variable contains a random identifier
2061 if repo[node].node() in foreground:
2061 if repo[node].node() in foreground:
2062 pass # allow updating to successors
2062 pass # allow updating to successors
2063 else:
2063 else:
2064 msg = _("uncommitted changes")
2064 msg = _("uncommitted changes")
2065 hint = _("commit or update --clean to discard changes")
2065 hint = _("commit or update --clean to discard changes")
2066 raise error.UpdateAbort(msg, hint=hint)
2066 raise error.UpdateAbort(msg, hint=hint)
2067 else:
2067 else:
2068 # Allow jumping branches if clean and specific rev given
2068 # Allow jumping branches if clean and specific rev given
2069 pass
2069 pass
2070
2070
2071 if overwrite:
2071 if overwrite:
2072 pas = [wc]
2072 pas = [wc]
2073 elif not branchmerge:
2073 elif not branchmerge:
2074 pas = [p1]
2074 pas = [p1]
2075
2075
2076 # deprecated config: merge.followcopies
2076 # deprecated config: merge.followcopies
2077 followcopies = repo.ui.configbool('merge', 'followcopies')
2077 followcopies = repo.ui.configbool('merge', 'followcopies')
2078 if overwrite:
2078 if overwrite:
2079 followcopies = False
2079 followcopies = False
2080 elif not pas[0]:
2080 elif not pas[0]:
2081 followcopies = False
2081 followcopies = False
2082 if not branchmerge and not wc.dirty(missing=True):
2082 if not branchmerge and not wc.dirty(missing=True):
2083 followcopies = False
2083 followcopies = False
2084
2084
2085 ### calculate phase
2085 ### calculate phase
2086 actionbyfile, diverge, renamedelete = calculateupdates(
2086 actionbyfile, diverge, renamedelete = calculateupdates(
2087 repo, wc, p2, pas, branchmerge, force, mergeancestor,
2087 repo, wc, p2, pas, branchmerge, force, mergeancestor,
2088 followcopies, matcher=matcher, mergeforce=mergeforce)
2088 followcopies, matcher=matcher, mergeforce=mergeforce)
2089
2089
2090 if updatecheck == 'noconflict':
2090 if updatecheck == 'noconflict':
2091 for f, (m, args, msg) in actionbyfile.iteritems():
2091 for f, (m, args, msg) in actionbyfile.iteritems():
2092 if m not in (ACTION_GET, ACTION_KEEP, ACTION_EXEC,
2092 if m not in (ACTION_GET, ACTION_KEEP, ACTION_EXEC,
2093 ACTION_REMOVE, ACTION_PATH_CONFLICT_RESOLVE):
2093 ACTION_REMOVE, ACTION_PATH_CONFLICT_RESOLVE):
2094 msg = _("conflicting changes")
2094 msg = _("conflicting changes")
2095 hint = _("commit or update --clean to discard changes")
2095 hint = _("commit or update --clean to discard changes")
2096 raise error.Abort(msg, hint=hint)
2096 raise error.Abort(msg, hint=hint)
2097
2097
2098 # Prompt and create actions. Most of this is in the resolve phase
2098 # Prompt and create actions. Most of this is in the resolve phase
2099 # already, but we can't handle .hgsubstate in filemerge or
2099 # already, but we can't handle .hgsubstate in filemerge or
2100 # subrepoutil.submerge yet so we have to keep prompting for it.
2100 # subrepoutil.submerge yet so we have to keep prompting for it.
2101 if '.hgsubstate' in actionbyfile:
2101 if '.hgsubstate' in actionbyfile:
2102 f = '.hgsubstate'
2102 f = '.hgsubstate'
2103 m, args, msg = actionbyfile[f]
2103 m, args, msg = actionbyfile[f]
2104 prompts = filemerge.partextras(labels)
2104 prompts = filemerge.partextras(labels)
2105 prompts['f'] = f
2105 prompts['f'] = f
2106 if m == ACTION_CHANGED_DELETED:
2106 if m == ACTION_CHANGED_DELETED:
2107 if repo.ui.promptchoice(
2107 if repo.ui.promptchoice(
2108 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
2108 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
2109 "use (c)hanged version or (d)elete?"
2109 "use (c)hanged version or (d)elete?"
2110 "$$ &Changed $$ &Delete") % prompts, 0):
2110 "$$ &Changed $$ &Delete") % prompts, 0):
2111 actionbyfile[f] = (ACTION_REMOVE, None, 'prompt delete')
2111 actionbyfile[f] = (ACTION_REMOVE, None, 'prompt delete')
2112 elif f in p1:
2112 elif f in p1:
2113 actionbyfile[f] = (ACTION_ADD_MODIFIED, None, 'prompt keep')
2113 actionbyfile[f] = (ACTION_ADD_MODIFIED, None, 'prompt keep')
2114 else:
2114 else:
2115 actionbyfile[f] = (ACTION_ADD, None, 'prompt keep')
2115 actionbyfile[f] = (ACTION_ADD, None, 'prompt keep')
2116 elif m == ACTION_DELETED_CHANGED:
2116 elif m == ACTION_DELETED_CHANGED:
2117 f1, f2, fa, move, anc = args
2117 f1, f2, fa, move, anc = args
2118 flags = p2[f2].flags()
2118 flags = p2[f2].flags()
2119 if repo.ui.promptchoice(
2119 if repo.ui.promptchoice(
2120 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
2120 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
2121 "use (c)hanged version or leave (d)eleted?"
2121 "use (c)hanged version or leave (d)eleted?"
2122 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
2122 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
2123 actionbyfile[f] = (ACTION_GET, (flags, False),
2123 actionbyfile[f] = (ACTION_GET, (flags, False),
2124 'prompt recreating')
2124 'prompt recreating')
2125 else:
2125 else:
2126 del actionbyfile[f]
2126 del actionbyfile[f]
2127
2127
2128 # Convert to dictionary-of-lists format
2128 # Convert to dictionary-of-lists format
2129 actions = emptyactions()
2129 actions = emptyactions()
2130 for f, (m, args, msg) in actionbyfile.iteritems():
2130 for f, (m, args, msg) in actionbyfile.iteritems():
2131 if m not in actions:
2131 if m not in actions:
2132 actions[m] = []
2132 actions[m] = []
2133 actions[m].append((f, args, msg))
2133 actions[m].append((f, args, msg))
2134
2134
2135 if not util.fscasesensitive(repo.path):
2135 if not util.fscasesensitive(repo.path):
2136 # check collision between files only in p2 for clean update
2136 # check collision between files only in p2 for clean update
2137 if (not branchmerge and
2137 if (not branchmerge and
2138 (force or not wc.dirty(missing=True, branch=False))):
2138 (force or not wc.dirty(missing=True, branch=False))):
2139 _checkcollision(repo, p2.manifest(), None)
2139 _checkcollision(repo, p2.manifest(), None)
2140 else:
2140 else:
2141 _checkcollision(repo, wc.manifest(), actions)
2141 _checkcollision(repo, wc.manifest(), actions)
2142
2142
2143 # divergent renames
2143 # divergent renames
2144 for f, fl in sorted(diverge.iteritems()):
2144 for f, fl in sorted(diverge.iteritems()):
2145 repo.ui.warn(_("note: possible conflict - %s was renamed "
2145 repo.ui.warn(_("note: possible conflict - %s was renamed "
2146 "multiple times to:\n") % f)
2146 "multiple times to:\n") % f)
2147 for nf in sorted(fl):
2147 for nf in sorted(fl):
2148 repo.ui.warn(" %s\n" % nf)
2148 repo.ui.warn(" %s\n" % nf)
2149
2149
2150 # rename and delete
2150 # rename and delete
2151 for f, fl in sorted(renamedelete.iteritems()):
2151 for f, fl in sorted(renamedelete.iteritems()):
2152 repo.ui.warn(_("note: possible conflict - %s was deleted "
2152 repo.ui.warn(_("note: possible conflict - %s was deleted "
2153 "and renamed to:\n") % f)
2153 "and renamed to:\n") % f)
2154 for nf in sorted(fl):
2154 for nf in sorted(fl):
2155 repo.ui.warn(" %s\n" % nf)
2155 repo.ui.warn(" %s\n" % nf)
2156
2156
2157 ### apply phase
2157 ### apply phase
2158 if not branchmerge: # just jump to the new rev
2158 if not branchmerge: # just jump to the new rev
2159 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
2159 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
2160 if not partial and not wc.isinmemory():
2160 if not partial and not wc.isinmemory():
2161 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
2161 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
2162 # note that we're in the middle of an update
2162 # note that we're in the middle of an update
2163 repo.vfs.write('updatestate', p2.hex())
2163 repo.vfs.write('updatestate', p2.hex())
2164
2164
2165 # Advertise fsmonitor when its presence could be useful.
2165 # Advertise fsmonitor when its presence could be useful.
2166 #
2166 #
2167 # We only advertise when performing an update from an empty working
2167 # We only advertise when performing an update from an empty working
2168 # directory. This typically only occurs during initial clone.
2168 # directory. This typically only occurs during initial clone.
2169 #
2169 #
2170 # We give users a mechanism to disable the warning in case it is
2170 # We give users a mechanism to disable the warning in case it is
2171 # annoying.
2171 # annoying.
2172 #
2172 #
2173 # We only allow on Linux and MacOS because that's where fsmonitor is
2173 # We only allow on Linux and MacOS because that's where fsmonitor is
2174 # considered stable.
2174 # considered stable.
2175 fsmonitorwarning = repo.ui.configbool('fsmonitor', 'warn_when_unused')
2175 fsmonitorwarning = repo.ui.configbool('fsmonitor', 'warn_when_unused')
2176 fsmonitorthreshold = repo.ui.configint('fsmonitor',
2176 fsmonitorthreshold = repo.ui.configint('fsmonitor',
2177 'warn_update_file_count')
2177 'warn_update_file_count')
2178 try:
2178 try:
2179 # avoid cycle: extensions -> cmdutil -> merge
2179 # avoid cycle: extensions -> cmdutil -> merge
2180 from . import extensions
2180 from . import extensions
2181 extensions.find('fsmonitor')
2181 extensions.find('fsmonitor')
2182 fsmonitorenabled = repo.ui.config('fsmonitor', 'mode') != 'off'
2182 fsmonitorenabled = repo.ui.config('fsmonitor', 'mode') != 'off'
2183 # We intentionally don't look at whether fsmonitor has disabled
2183 # We intentionally don't look at whether fsmonitor has disabled
2184 # itself because a) fsmonitor may have already printed a warning
2184 # itself because a) fsmonitor may have already printed a warning
2185 # b) we only care about the config state here.
2185 # b) we only care about the config state here.
2186 except KeyError:
2186 except KeyError:
2187 fsmonitorenabled = False
2187 fsmonitorenabled = False
2188
2188
2189 if (fsmonitorwarning
2189 if (fsmonitorwarning
2190 and not fsmonitorenabled
2190 and not fsmonitorenabled
2191 and p1.node() == nullid
2191 and p1.node() == nullid
2192 and len(actions[ACTION_GET]) >= fsmonitorthreshold
2192 and len(actions[ACTION_GET]) >= fsmonitorthreshold
2193 and pycompat.sysplatform.startswith(('linux', 'darwin'))):
2193 and pycompat.sysplatform.startswith(('linux', 'darwin'))):
2194 repo.ui.warn(
2194 repo.ui.warn(
2195 _('(warning: large working directory being used without '
2195 _('(warning: large working directory being used without '
2196 'fsmonitor enabled; enable fsmonitor to improve performance; '
2196 'fsmonitor enabled; enable fsmonitor to improve performance; '
2197 'see "hg help -e fsmonitor")\n'))
2197 'see "hg help -e fsmonitor")\n'))
2198
2198
2199 updatedirstate = not partial and not wc.isinmemory()
2199 updatedirstate = not partial and not wc.isinmemory()
2200 wantfiledata = updatedirstate and not branchmerge
2200 wantfiledata = updatedirstate and not branchmerge
2201 stats, getfiledata = applyupdates(repo, actions, wc, p2, overwrite,
2201 stats, getfiledata = applyupdates(repo, actions, wc, p2, overwrite,
2202 wantfiledata, labels=labels)
2202 wantfiledata, labels=labels)
2203
2203
2204 if updatedirstate:
2204 if updatedirstate:
2205 with repo.dirstate.parentchange():
2205 with repo.dirstate.parentchange():
2206 repo.setparents(fp1, fp2)
2206 repo.setparents(fp1, fp2)
2207 recordupdates(repo, actions, branchmerge, getfiledata)
2207 recordupdates(repo, actions, branchmerge, getfiledata)
2208 # update completed, clear state
2208 # update completed, clear state
2209 util.unlink(repo.vfs.join('updatestate'))
2209 util.unlink(repo.vfs.join('updatestate'))
2210
2210
2211 if not branchmerge:
2211 if not branchmerge:
2212 repo.dirstate.setbranch(p2.branch())
2212 repo.dirstate.setbranch(p2.branch())
2213
2213
2214 # If we're updating to a location, clean up any stale temporary includes
2214 # If we're updating to a location, clean up any stale temporary includes
2215 # (ex: this happens during hg rebase --abort).
2215 # (ex: this happens during hg rebase --abort).
2216 if not branchmerge:
2216 if not branchmerge:
2217 sparse.prunetemporaryincludes(repo)
2217 sparse.prunetemporaryincludes(repo)
2218
2218
2219 if not partial:
2219 if not partial:
2220 repo.hook('update', parent1=xp1, parent2=xp2,
2220 repo.hook('update', parent1=xp1, parent2=xp2,
2221 error=stats.unresolvedcount)
2221 error=stats.unresolvedcount)
2222 return stats
2222 return stats
2223
2223
2224 def graft(repo, ctx, pctx, labels=None, keepparent=False,
2224 def graft(repo, ctx, pctx, labels=None, keepparent=False,
2225 keepconflictparent=False):
2225 keepconflictparent=False):
2226 """Do a graft-like merge.
2226 """Do a graft-like merge.
2227
2227
2228 This is a merge where the merge ancestor is chosen such that one
2228 This is a merge where the merge ancestor is chosen such that one
2229 or more changesets are grafted onto the current changeset. In
2229 or more changesets are grafted onto the current changeset. In
2230 addition to the merge, this fixes up the dirstate to include only
2230 addition to the merge, this fixes up the dirstate to include only
2231 a single parent (if keepparent is False) and tries to duplicate any
2231 a single parent (if keepparent is False) and tries to duplicate any
2232 renames/copies appropriately.
2232 renames/copies appropriately.
2233
2233
2234 ctx - changeset to rebase
2234 ctx - changeset to rebase
2235 pctx - merge base, usually ctx.p1()
2235 pctx - merge base, usually ctx.p1()
2236 labels - merge labels eg ['local', 'graft']
2236 labels - merge labels eg ['local', 'graft']
2237 keepparent - keep second parent if any
2237 keepparent - keep second parent if any
2238 keepconflictparent - if unresolved, keep parent used for the merge
2238 keepconflictparent - if unresolved, keep parent used for the merge
2239
2239
2240 """
2240 """
2241 # If we're grafting a descendant onto an ancestor, be sure to pass
2241 # If we're grafting a descendant onto an ancestor, be sure to pass
2242 # mergeancestor=True to update. This does two things: 1) allows the merge if
2242 # mergeancestor=True to update. This does two things: 1) allows the merge if
2243 # the destination is the same as the parent of the ctx (so we can use graft
2243 # the destination is the same as the parent of the ctx (so we can use graft
2244 # to copy commits), and 2) informs update that the incoming changes are
2244 # to copy commits), and 2) informs update that the incoming changes are
2245 # newer than the destination so it doesn't prompt about "remote changed foo
2245 # newer than the destination so it doesn't prompt about "remote changed foo
2246 # which local deleted".
2246 # which local deleted".
2247 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
2247 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
2248
2248
2249 stats = update(repo, ctx.node(), True, True, pctx.node(),
2249 stats = update(repo, ctx.node(), True, True, pctx.node(),
2250 mergeancestor=mergeancestor, labels=labels)
2250 mergeancestor=mergeancestor, labels=labels)
2251
2251
2252
2252
2253 potherp1 = False
2253 if keepconflictparent and stats.unresolvedcount:
2254 if keepconflictparent and stats.unresolvedcount:
2254 pother = ctx.node()
2255 pother = ctx.node()
2255 else:
2256 else:
2256 pother = nullid
2257 pother = nullid
2257 parents = ctx.parents()
2258 parents = ctx.parents()
2258 if keepparent and len(parents) == 2 and pctx in parents:
2259 if keepparent and len(parents) == 2 and pctx in parents:
2260 if pctx == parents[0]:
2261 potherp1 = True
2259 parents.remove(pctx)
2262 parents.remove(pctx)
2260 pother = parents[0].node()
2263 pother = parents[0].node()
2261
2264
2262 with repo.dirstate.parentchange():
2265 with repo.dirstate.parentchange():
2263 repo.setparents(repo['.'].node(), pother)
2266 if potherp1:
2267 repo.setparents(pother, repo['.'].node())
2268 else:
2269 repo.setparents(repo['.'].node(), pother)
2264 repo.dirstate.write(repo.currenttransaction())
2270 repo.dirstate.write(repo.currenttransaction())
2265 # fix up dirstate for copies and renames
2271 # fix up dirstate for copies and renames
2266 copies.duplicatecopies(repo, repo[None], ctx.rev(), pctx.rev())
2272 copies.duplicatecopies(repo, repo[None], ctx.rev(), pctx.rev())
2267 return stats
2273 return stats
2268
2274
2269 def purge(repo, matcher, ignored=False, removeemptydirs=True,
2275 def purge(repo, matcher, ignored=False, removeemptydirs=True,
2270 removefiles=True, abortonerror=False, noop=False):
2276 removefiles=True, abortonerror=False, noop=False):
2271 """Purge the working directory of untracked files.
2277 """Purge the working directory of untracked files.
2272
2278
2273 ``matcher`` is a matcher configured to scan the working directory -
2279 ``matcher`` is a matcher configured to scan the working directory -
2274 potentially a subset.
2280 potentially a subset.
2275
2281
2276 ``ignored`` controls whether ignored files should also be purged.
2282 ``ignored`` controls whether ignored files should also be purged.
2277
2283
2278 ``removeemptydirs`` controls whether empty directories should be removed.
2284 ``removeemptydirs`` controls whether empty directories should be removed.
2279
2285
2280 ``removefiles`` controls whether files are removed.
2286 ``removefiles`` controls whether files are removed.
2281
2287
2282 ``abortonerror`` causes an exception to be raised if an error occurs
2288 ``abortonerror`` causes an exception to be raised if an error occurs
2283 deleting a file or directory.
2289 deleting a file or directory.
2284
2290
2285 ``noop`` controls whether to actually remove files. If not defined, actions
2291 ``noop`` controls whether to actually remove files. If not defined, actions
2286 will be taken.
2292 will be taken.
2287
2293
2288 Returns an iterable of relative paths in the working directory that were
2294 Returns an iterable of relative paths in the working directory that were
2289 or would be removed.
2295 or would be removed.
2290 """
2296 """
2291
2297
2292 def remove(removefn, path):
2298 def remove(removefn, path):
2293 try:
2299 try:
2294 removefn(path)
2300 removefn(path)
2295 except OSError:
2301 except OSError:
2296 m = _('%s cannot be removed') % path
2302 m = _('%s cannot be removed') % path
2297 if abortonerror:
2303 if abortonerror:
2298 raise error.Abort(m)
2304 raise error.Abort(m)
2299 else:
2305 else:
2300 repo.ui.warn(_('warning: %s\n') % m)
2306 repo.ui.warn(_('warning: %s\n') % m)
2301
2307
2302 # There's no API to copy a matcher. So mutate the passed matcher and
2308 # There's no API to copy a matcher. So mutate the passed matcher and
2303 # restore it when we're done.
2309 # restore it when we're done.
2304 oldexplicitdir = matcher.explicitdir
2310 oldexplicitdir = matcher.explicitdir
2305 oldtraversedir = matcher.traversedir
2311 oldtraversedir = matcher.traversedir
2306
2312
2307 res = []
2313 res = []
2308
2314
2309 try:
2315 try:
2310 if removeemptydirs:
2316 if removeemptydirs:
2311 directories = []
2317 directories = []
2312 matcher.explicitdir = matcher.traversedir = directories.append
2318 matcher.explicitdir = matcher.traversedir = directories.append
2313
2319
2314 status = repo.status(match=matcher, ignored=ignored, unknown=True)
2320 status = repo.status(match=matcher, ignored=ignored, unknown=True)
2315
2321
2316 if removefiles:
2322 if removefiles:
2317 for f in sorted(status.unknown + status.ignored):
2323 for f in sorted(status.unknown + status.ignored):
2318 if not noop:
2324 if not noop:
2319 repo.ui.note(_('removing file %s\n') % f)
2325 repo.ui.note(_('removing file %s\n') % f)
2320 remove(repo.wvfs.unlink, f)
2326 remove(repo.wvfs.unlink, f)
2321 res.append(f)
2327 res.append(f)
2322
2328
2323 if removeemptydirs:
2329 if removeemptydirs:
2324 for f in sorted(directories, reverse=True):
2330 for f in sorted(directories, reverse=True):
2325 if matcher(f) and not repo.wvfs.listdir(f):
2331 if matcher(f) and not repo.wvfs.listdir(f):
2326 if not noop:
2332 if not noop:
2327 repo.ui.note(_('removing directory %s\n') % f)
2333 repo.ui.note(_('removing directory %s\n') % f)
2328 remove(repo.wvfs.rmdir, f)
2334 remove(repo.wvfs.rmdir, f)
2329 res.append(f)
2335 res.append(f)
2330
2336
2331 return res
2337 return res
2332
2338
2333 finally:
2339 finally:
2334 matcher.explicitdir = oldexplicitdir
2340 matcher.explicitdir = oldexplicitdir
2335 matcher.traversedir = oldtraversedir
2341 matcher.traversedir = oldtraversedir
@@ -1,3356 +1,3357 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # run-tests.py - Run a set of tests on Mercurial
3 # run-tests.py - Run a set of tests on Mercurial
4 #
4 #
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 # Modifying this script is tricky because it has many modes:
10 # Modifying this script is tricky because it has many modes:
11 # - serial (default) vs parallel (-jN, N > 1)
11 # - serial (default) vs parallel (-jN, N > 1)
12 # - no coverage (default) vs coverage (-c, -C, -s)
12 # - no coverage (default) vs coverage (-c, -C, -s)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 # - tests are a mix of shell scripts and Python scripts
14 # - tests are a mix of shell scripts and Python scripts
15 #
15 #
16 # If you change this script, it is recommended that you ensure you
16 # If you change this script, it is recommended that you ensure you
17 # haven't broken it by running it in various modes with a representative
17 # haven't broken it by running it in various modes with a representative
18 # sample of test scripts. For example:
18 # sample of test scripts. For example:
19 #
19 #
20 # 1) serial, no coverage, temp install:
20 # 1) serial, no coverage, temp install:
21 # ./run-tests.py test-s*
21 # ./run-tests.py test-s*
22 # 2) serial, no coverage, local hg:
22 # 2) serial, no coverage, local hg:
23 # ./run-tests.py --local test-s*
23 # ./run-tests.py --local test-s*
24 # 3) serial, coverage, temp install:
24 # 3) serial, coverage, temp install:
25 # ./run-tests.py -c test-s*
25 # ./run-tests.py -c test-s*
26 # 4) serial, coverage, local hg:
26 # 4) serial, coverage, local hg:
27 # ./run-tests.py -c --local test-s* # unsupported
27 # ./run-tests.py -c --local test-s* # unsupported
28 # 5) parallel, no coverage, temp install:
28 # 5) parallel, no coverage, temp install:
29 # ./run-tests.py -j2 test-s*
29 # ./run-tests.py -j2 test-s*
30 # 6) parallel, no coverage, local hg:
30 # 6) parallel, no coverage, local hg:
31 # ./run-tests.py -j2 --local test-s*
31 # ./run-tests.py -j2 --local test-s*
32 # 7) parallel, coverage, temp install:
32 # 7) parallel, coverage, temp install:
33 # ./run-tests.py -j2 -c test-s* # currently broken
33 # ./run-tests.py -j2 -c test-s* # currently broken
34 # 8) parallel, coverage, local install:
34 # 8) parallel, coverage, local install:
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 # 9) parallel, custom tmp dir:
36 # 9) parallel, custom tmp dir:
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 # 10) parallel, pure, tests that call run-tests:
38 # 10) parallel, pure, tests that call run-tests:
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
40 #
40 #
41 # (You could use any subset of the tests: test-s* happens to match
41 # (You could use any subset of the tests: test-s* happens to match
42 # enough that it's worth doing parallel runs, few enough that it
42 # enough that it's worth doing parallel runs, few enough that it
43 # completes fairly quickly, includes both shell and Python scripts, and
43 # completes fairly quickly, includes both shell and Python scripts, and
44 # includes some scripts that run daemon processes.)
44 # includes some scripts that run daemon processes.)
45
45
46 from __future__ import absolute_import, print_function
46 from __future__ import absolute_import, print_function
47
47
48 import argparse
48 import argparse
49 import collections
49 import collections
50 import difflib
50 import difflib
51 import distutils.version as version
51 import distutils.version as version
52 import errno
52 import errno
53 import json
53 import json
54 import multiprocessing
54 import multiprocessing
55 import os
55 import os
56 import random
56 import random
57 import re
57 import re
58 import shutil
58 import shutil
59 import signal
59 import signal
60 import socket
60 import socket
61 import subprocess
61 import subprocess
62 import sys
62 import sys
63 import sysconfig
63 import sysconfig
64 import tempfile
64 import tempfile
65 import threading
65 import threading
66 import time
66 import time
67 import unittest
67 import unittest
68 import uuid
68 import uuid
69 import xml.dom.minidom as minidom
69 import xml.dom.minidom as minidom
70
70
71 try:
71 try:
72 import Queue as queue
72 import Queue as queue
73 except ImportError:
73 except ImportError:
74 import queue
74 import queue
75
75
76 try:
76 try:
77 import shlex
77 import shlex
78 shellquote = shlex.quote
78 shellquote = shlex.quote
79 except (ImportError, AttributeError):
79 except (ImportError, AttributeError):
80 import pipes
80 import pipes
81 shellquote = pipes.quote
81 shellquote = pipes.quote
82
82
83 if os.environ.get('RTUNICODEPEDANTRY', False):
83 if os.environ.get('RTUNICODEPEDANTRY', False):
84 try:
84 try:
85 reload(sys)
85 reload(sys)
86 sys.setdefaultencoding("undefined")
86 sys.setdefaultencoding("undefined")
87 except NameError:
87 except NameError:
88 pass
88 pass
89
89
90 processlock = threading.Lock()
90 processlock = threading.Lock()
91
91
92 pygmentspresent = False
92 pygmentspresent = False
93 # ANSI color is unsupported prior to Windows 10
93 # ANSI color is unsupported prior to Windows 10
94 if os.name != 'nt':
94 if os.name != 'nt':
95 try: # is pygments installed
95 try: # is pygments installed
96 import pygments
96 import pygments
97 import pygments.lexers as lexers
97 import pygments.lexers as lexers
98 import pygments.lexer as lexer
98 import pygments.lexer as lexer
99 import pygments.formatters as formatters
99 import pygments.formatters as formatters
100 import pygments.token as token
100 import pygments.token as token
101 import pygments.style as style
101 import pygments.style as style
102 pygmentspresent = True
102 pygmentspresent = True
103 difflexer = lexers.DiffLexer()
103 difflexer = lexers.DiffLexer()
104 terminal256formatter = formatters.Terminal256Formatter()
104 terminal256formatter = formatters.Terminal256Formatter()
105 except ImportError:
105 except ImportError:
106 pass
106 pass
107
107
108 if pygmentspresent:
108 if pygmentspresent:
109 class TestRunnerStyle(style.Style):
109 class TestRunnerStyle(style.Style):
110 default_style = ""
110 default_style = ""
111 skipped = token.string_to_tokentype("Token.Generic.Skipped")
111 skipped = token.string_to_tokentype("Token.Generic.Skipped")
112 failed = token.string_to_tokentype("Token.Generic.Failed")
112 failed = token.string_to_tokentype("Token.Generic.Failed")
113 skippedname = token.string_to_tokentype("Token.Generic.SName")
113 skippedname = token.string_to_tokentype("Token.Generic.SName")
114 failedname = token.string_to_tokentype("Token.Generic.FName")
114 failedname = token.string_to_tokentype("Token.Generic.FName")
115 styles = {
115 styles = {
116 skipped: '#e5e5e5',
116 skipped: '#e5e5e5',
117 skippedname: '#00ffff',
117 skippedname: '#00ffff',
118 failed: '#7f0000',
118 failed: '#7f0000',
119 failedname: '#ff0000',
119 failedname: '#ff0000',
120 }
120 }
121
121
122 class TestRunnerLexer(lexer.RegexLexer):
122 class TestRunnerLexer(lexer.RegexLexer):
123 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
123 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
124 tokens = {
124 tokens = {
125 'root': [
125 'root': [
126 (r'^Skipped', token.Generic.Skipped, 'skipped'),
126 (r'^Skipped', token.Generic.Skipped, 'skipped'),
127 (r'^Failed ', token.Generic.Failed, 'failed'),
127 (r'^Failed ', token.Generic.Failed, 'failed'),
128 (r'^ERROR: ', token.Generic.Failed, 'failed'),
128 (r'^ERROR: ', token.Generic.Failed, 'failed'),
129 ],
129 ],
130 'skipped': [
130 'skipped': [
131 (testpattern, token.Generic.SName),
131 (testpattern, token.Generic.SName),
132 (r':.*', token.Generic.Skipped),
132 (r':.*', token.Generic.Skipped),
133 ],
133 ],
134 'failed': [
134 'failed': [
135 (testpattern, token.Generic.FName),
135 (testpattern, token.Generic.FName),
136 (r'(:| ).*', token.Generic.Failed),
136 (r'(:| ).*', token.Generic.Failed),
137 ]
137 ]
138 }
138 }
139
139
140 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
140 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
141 runnerlexer = TestRunnerLexer()
141 runnerlexer = TestRunnerLexer()
142
142
143 origenviron = os.environ.copy()
143 origenviron = os.environ.copy()
144
144
145 if sys.version_info > (3, 5, 0):
145 if sys.version_info > (3, 5, 0):
146 PYTHON3 = True
146 PYTHON3 = True
147 xrange = range # we use xrange in one place, and we'd rather not use range
147 xrange = range # we use xrange in one place, and we'd rather not use range
148 def _bytespath(p):
148 def _bytespath(p):
149 if p is None:
149 if p is None:
150 return p
150 return p
151 return p.encode('utf-8')
151 return p.encode('utf-8')
152
152
153 def _strpath(p):
153 def _strpath(p):
154 if p is None:
154 if p is None:
155 return p
155 return p
156 return p.decode('utf-8')
156 return p.decode('utf-8')
157
157
158 osenvironb = getattr(os, 'environb', None)
158 osenvironb = getattr(os, 'environb', None)
159 if osenvironb is None:
159 if osenvironb is None:
160 # Windows lacks os.environb, for instance. A proxy over the real thing
160 # Windows lacks os.environb, for instance. A proxy over the real thing
161 # instead of a copy allows the environment to be updated via bytes on
161 # instead of a copy allows the environment to be updated via bytes on
162 # all platforms.
162 # all platforms.
163 class environbytes(object):
163 class environbytes(object):
164 def __init__(self, strenv):
164 def __init__(self, strenv):
165 self.__len__ = strenv.__len__
165 self.__len__ = strenv.__len__
166 self.clear = strenv.clear
166 self.clear = strenv.clear
167 self._strenv = strenv
167 self._strenv = strenv
168 def __getitem__(self, k):
168 def __getitem__(self, k):
169 v = self._strenv.__getitem__(_strpath(k))
169 v = self._strenv.__getitem__(_strpath(k))
170 return _bytespath(v)
170 return _bytespath(v)
171 def __setitem__(self, k, v):
171 def __setitem__(self, k, v):
172 self._strenv.__setitem__(_strpath(k), _strpath(v))
172 self._strenv.__setitem__(_strpath(k), _strpath(v))
173 def __delitem__(self, k):
173 def __delitem__(self, k):
174 self._strenv.__delitem__(_strpath(k))
174 self._strenv.__delitem__(_strpath(k))
175 def __contains__(self, k):
175 def __contains__(self, k):
176 return self._strenv.__contains__(_strpath(k))
176 return self._strenv.__contains__(_strpath(k))
177 def __iter__(self):
177 def __iter__(self):
178 return iter([_bytespath(k) for k in iter(self._strenv)])
178 return iter([_bytespath(k) for k in iter(self._strenv)])
179 def get(self, k, default=None):
179 def get(self, k, default=None):
180 v = self._strenv.get(_strpath(k), _strpath(default))
180 v = self._strenv.get(_strpath(k), _strpath(default))
181 return _bytespath(v)
181 return _bytespath(v)
182 def pop(self, k, default=None):
182 def pop(self, k, default=None):
183 v = self._strenv.pop(_strpath(k), _strpath(default))
183 v = self._strenv.pop(_strpath(k), _strpath(default))
184 return _bytespath(v)
184 return _bytespath(v)
185
185
186 osenvironb = environbytes(os.environ)
186 osenvironb = environbytes(os.environ)
187
187
188 getcwdb = getattr(os, 'getcwdb')
188 getcwdb = getattr(os, 'getcwdb')
189 if not getcwdb or os.name == 'nt':
189 if not getcwdb or os.name == 'nt':
190 getcwdb = lambda: _bytespath(os.getcwd())
190 getcwdb = lambda: _bytespath(os.getcwd())
191
191
192 elif sys.version_info >= (3, 0, 0):
192 elif sys.version_info >= (3, 0, 0):
193 print('%s is only supported on Python 3.5+ and 2.7, not %s' %
193 print('%s is only supported on Python 3.5+ and 2.7, not %s' %
194 (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
194 (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
195 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
195 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
196 else:
196 else:
197 PYTHON3 = False
197 PYTHON3 = False
198
198
199 # In python 2.x, path operations are generally done using
199 # In python 2.x, path operations are generally done using
200 # bytestrings by default, so we don't have to do any extra
200 # bytestrings by default, so we don't have to do any extra
201 # fiddling there. We define the wrapper functions anyway just to
201 # fiddling there. We define the wrapper functions anyway just to
202 # help keep code consistent between platforms.
202 # help keep code consistent between platforms.
203 def _bytespath(p):
203 def _bytespath(p):
204 return p
204 return p
205
205
206 _strpath = _bytespath
206 _strpath = _bytespath
207 osenvironb = os.environ
207 osenvironb = os.environ
208 getcwdb = os.getcwd
208 getcwdb = os.getcwd
209
209
210 # For Windows support
210 # For Windows support
211 wifexited = getattr(os, "WIFEXITED", lambda x: False)
211 wifexited = getattr(os, "WIFEXITED", lambda x: False)
212
212
213 # Whether to use IPv6
213 # Whether to use IPv6
214 def checksocketfamily(name, port=20058):
214 def checksocketfamily(name, port=20058):
215 """return true if we can listen on localhost using family=name
215 """return true if we can listen on localhost using family=name
216
216
217 name should be either 'AF_INET', or 'AF_INET6'.
217 name should be either 'AF_INET', or 'AF_INET6'.
218 port being used is okay - EADDRINUSE is considered as successful.
218 port being used is okay - EADDRINUSE is considered as successful.
219 """
219 """
220 family = getattr(socket, name, None)
220 family = getattr(socket, name, None)
221 if family is None:
221 if family is None:
222 return False
222 return False
223 try:
223 try:
224 s = socket.socket(family, socket.SOCK_STREAM)
224 s = socket.socket(family, socket.SOCK_STREAM)
225 s.bind(('localhost', port))
225 s.bind(('localhost', port))
226 s.close()
226 s.close()
227 return True
227 return True
228 except socket.error as exc:
228 except socket.error as exc:
229 if exc.errno == errno.EADDRINUSE:
229 if exc.errno == errno.EADDRINUSE:
230 return True
230 return True
231 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
231 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
232 return False
232 return False
233 else:
233 else:
234 raise
234 raise
235 else:
235 else:
236 return False
236 return False
237
237
238 # useipv6 will be set by parseargs
238 # useipv6 will be set by parseargs
239 useipv6 = None
239 useipv6 = None
240
240
241 def checkportisavailable(port):
241 def checkportisavailable(port):
242 """return true if a port seems free to bind on localhost"""
242 """return true if a port seems free to bind on localhost"""
243 if useipv6:
243 if useipv6:
244 family = socket.AF_INET6
244 family = socket.AF_INET6
245 else:
245 else:
246 family = socket.AF_INET
246 family = socket.AF_INET
247 try:
247 try:
248 s = socket.socket(family, socket.SOCK_STREAM)
248 s = socket.socket(family, socket.SOCK_STREAM)
249 s.bind(('localhost', port))
249 s.bind(('localhost', port))
250 s.close()
250 s.close()
251 return True
251 return True
252 except socket.error as exc:
252 except socket.error as exc:
253 if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL,
253 if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL,
254 errno.EPROTONOSUPPORT):
254 errno.EPROTONOSUPPORT):
255 raise
255 raise
256 return False
256 return False
257
257
258 closefds = os.name == 'posix'
258 closefds = os.name == 'posix'
259 def Popen4(cmd, wd, timeout, env=None):
259 def Popen4(cmd, wd, timeout, env=None):
260 processlock.acquire()
260 processlock.acquire()
261 p = subprocess.Popen(_strpath(cmd), shell=True, bufsize=-1,
261 p = subprocess.Popen(_strpath(cmd), shell=True, bufsize=-1,
262 cwd=_strpath(wd), env=env,
262 cwd=_strpath(wd), env=env,
263 close_fds=closefds,
263 close_fds=closefds,
264 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
264 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
265 stderr=subprocess.STDOUT)
265 stderr=subprocess.STDOUT)
266 processlock.release()
266 processlock.release()
267
267
268 p.fromchild = p.stdout
268 p.fromchild = p.stdout
269 p.tochild = p.stdin
269 p.tochild = p.stdin
270 p.childerr = p.stderr
270 p.childerr = p.stderr
271
271
272 p.timeout = False
272 p.timeout = False
273 if timeout:
273 if timeout:
274 def t():
274 def t():
275 start = time.time()
275 start = time.time()
276 while time.time() - start < timeout and p.returncode is None:
276 while time.time() - start < timeout and p.returncode is None:
277 time.sleep(.1)
277 time.sleep(.1)
278 p.timeout = True
278 p.timeout = True
279 if p.returncode is None:
279 if p.returncode is None:
280 terminate(p)
280 terminate(p)
281 threading.Thread(target=t).start()
281 threading.Thread(target=t).start()
282
282
283 return p
283 return p
284
284
285 if sys.executable:
285 if sys.executable:
286 sysexecutable = sys.executable
286 sysexecutable = sys.executable
287 elif os.environ.get('PYTHONEXECUTABLE'):
287 elif os.environ.get('PYTHONEXECUTABLE'):
288 sysexecutable = os.environ['PYTHONEXECUTABLE']
288 sysexecutable = os.environ['PYTHONEXECUTABLE']
289 elif os.environ.get('PYTHON'):
289 elif os.environ.get('PYTHON'):
290 sysexecutable = os.environ['PYTHON']
290 sysexecutable = os.environ['PYTHON']
291 else:
291 else:
292 raise AssertionError('Could not find Python interpreter')
292 raise AssertionError('Could not find Python interpreter')
293
293
294 PYTHON = _bytespath(sysexecutable.replace('\\', '/'))
294 PYTHON = _bytespath(sysexecutable.replace('\\', '/'))
295 IMPL_PATH = b'PYTHONPATH'
295 IMPL_PATH = b'PYTHONPATH'
296 if 'java' in sys.platform:
296 if 'java' in sys.platform:
297 IMPL_PATH = b'JYTHONPATH'
297 IMPL_PATH = b'JYTHONPATH'
298
298
299 defaults = {
299 defaults = {
300 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
300 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
301 'timeout': ('HGTEST_TIMEOUT', 180),
301 'timeout': ('HGTEST_TIMEOUT', 180),
302 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
302 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
303 'port': ('HGTEST_PORT', 20059),
303 'port': ('HGTEST_PORT', 20059),
304 'shell': ('HGTEST_SHELL', 'sh'),
304 'shell': ('HGTEST_SHELL', 'sh'),
305 }
305 }
306
306
307 def canonpath(path):
307 def canonpath(path):
308 return os.path.realpath(os.path.expanduser(path))
308 return os.path.realpath(os.path.expanduser(path))
309
309
310 def parselistfiles(files, listtype, warn=True):
310 def parselistfiles(files, listtype, warn=True):
311 entries = dict()
311 entries = dict()
312 for filename in files:
312 for filename in files:
313 try:
313 try:
314 path = os.path.expanduser(os.path.expandvars(filename))
314 path = os.path.expanduser(os.path.expandvars(filename))
315 f = open(path, "rb")
315 f = open(path, "rb")
316 except IOError as err:
316 except IOError as err:
317 if err.errno != errno.ENOENT:
317 if err.errno != errno.ENOENT:
318 raise
318 raise
319 if warn:
319 if warn:
320 print("warning: no such %s file: %s" % (listtype, filename))
320 print("warning: no such %s file: %s" % (listtype, filename))
321 continue
321 continue
322
322
323 for line in f.readlines():
323 for line in f.readlines():
324 line = line.split(b'#', 1)[0].strip()
324 line = line.split(b'#', 1)[0].strip()
325 if line:
325 if line:
326 entries[line] = filename
326 entries[line] = filename
327
327
328 f.close()
328 f.close()
329 return entries
329 return entries
330
330
331 def parsettestcases(path):
331 def parsettestcases(path):
332 """read a .t test file, return a set of test case names
332 """read a .t test file, return a set of test case names
333
333
334 If path does not exist, return an empty set.
334 If path does not exist, return an empty set.
335 """
335 """
336 cases = []
336 cases = []
337 try:
337 try:
338 with open(path, 'rb') as f:
338 with open(path, 'rb') as f:
339 for l in f:
339 for l in f:
340 if l.startswith(b'#testcases '):
340 if l.startswith(b'#testcases '):
341 cases.append(sorted(l[11:].split()))
341 cases.append(sorted(l[11:].split()))
342 except IOError as ex:
342 except IOError as ex:
343 if ex.errno != errno.ENOENT:
343 if ex.errno != errno.ENOENT:
344 raise
344 raise
345 return cases
345 return cases
346
346
347 def getparser():
347 def getparser():
348 """Obtain the OptionParser used by the CLI."""
348 """Obtain the OptionParser used by the CLI."""
349 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
349 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
350
350
351 selection = parser.add_argument_group('Test Selection')
351 selection = parser.add_argument_group('Test Selection')
352 selection.add_argument('--allow-slow-tests', action='store_true',
352 selection.add_argument('--allow-slow-tests', action='store_true',
353 help='allow extremely slow tests')
353 help='allow extremely slow tests')
354 selection.add_argument("--blacklist", action="append",
354 selection.add_argument("--blacklist", action="append",
355 help="skip tests listed in the specified blacklist file")
355 help="skip tests listed in the specified blacklist file")
356 selection.add_argument("--changed",
356 selection.add_argument("--changed",
357 help="run tests that are changed in parent rev or working directory")
357 help="run tests that are changed in parent rev or working directory")
358 selection.add_argument("-k", "--keywords",
358 selection.add_argument("-k", "--keywords",
359 help="run tests matching keywords")
359 help="run tests matching keywords")
360 selection.add_argument("-r", "--retest", action="store_true",
360 selection.add_argument("-r", "--retest", action="store_true",
361 help = "retest failed tests")
361 help = "retest failed tests")
362 selection.add_argument("--test-list", action="append",
362 selection.add_argument("--test-list", action="append",
363 help="read tests to run from the specified file")
363 help="read tests to run from the specified file")
364 selection.add_argument("--whitelist", action="append",
364 selection.add_argument("--whitelist", action="append",
365 help="always run tests listed in the specified whitelist file")
365 help="always run tests listed in the specified whitelist file")
366 selection.add_argument('tests', metavar='TESTS', nargs='*',
366 selection.add_argument('tests', metavar='TESTS', nargs='*',
367 help='Tests to run')
367 help='Tests to run')
368
368
369 harness = parser.add_argument_group('Test Harness Behavior')
369 harness = parser.add_argument_group('Test Harness Behavior')
370 harness.add_argument('--bisect-repo',
370 harness.add_argument('--bisect-repo',
371 metavar='bisect_repo',
371 metavar='bisect_repo',
372 help=("Path of a repo to bisect. Use together with "
372 help=("Path of a repo to bisect. Use together with "
373 "--known-good-rev"))
373 "--known-good-rev"))
374 harness.add_argument("-d", "--debug", action="store_true",
374 harness.add_argument("-d", "--debug", action="store_true",
375 help="debug mode: write output of test scripts to console"
375 help="debug mode: write output of test scripts to console"
376 " rather than capturing and diffing it (disables timeout)")
376 " rather than capturing and diffing it (disables timeout)")
377 harness.add_argument("-f", "--first", action="store_true",
377 harness.add_argument("-f", "--first", action="store_true",
378 help="exit on the first test failure")
378 help="exit on the first test failure")
379 harness.add_argument("-i", "--interactive", action="store_true",
379 harness.add_argument("-i", "--interactive", action="store_true",
380 help="prompt to accept changed output")
380 help="prompt to accept changed output")
381 harness.add_argument("-j", "--jobs", type=int,
381 harness.add_argument("-j", "--jobs", type=int,
382 help="number of jobs to run in parallel"
382 help="number of jobs to run in parallel"
383 " (default: $%s or %d)" % defaults['jobs'])
383 " (default: $%s or %d)" % defaults['jobs'])
384 harness.add_argument("--keep-tmpdir", action="store_true",
384 harness.add_argument("--keep-tmpdir", action="store_true",
385 help="keep temporary directory after running tests")
385 help="keep temporary directory after running tests")
386 harness.add_argument('--known-good-rev',
386 harness.add_argument('--known-good-rev',
387 metavar="known_good_rev",
387 metavar="known_good_rev",
388 help=("Automatically bisect any failures using this "
388 help=("Automatically bisect any failures using this "
389 "revision as a known-good revision."))
389 "revision as a known-good revision."))
390 harness.add_argument("--list-tests", action="store_true",
390 harness.add_argument("--list-tests", action="store_true",
391 help="list tests instead of running them")
391 help="list tests instead of running them")
392 harness.add_argument("--loop", action="store_true",
392 harness.add_argument("--loop", action="store_true",
393 help="loop tests repeatedly")
393 help="loop tests repeatedly")
394 harness.add_argument('--random', action="store_true",
394 harness.add_argument('--random', action="store_true",
395 help='run tests in random order')
395 help='run tests in random order')
396 harness.add_argument('--order-by-runtime', action="store_true",
396 harness.add_argument('--order-by-runtime', action="store_true",
397 help='run slowest tests first, according to .testtimes')
397 help='run slowest tests first, according to .testtimes')
398 harness.add_argument("-p", "--port", type=int,
398 harness.add_argument("-p", "--port", type=int,
399 help="port on which servers should listen"
399 help="port on which servers should listen"
400 " (default: $%s or %d)" % defaults['port'])
400 " (default: $%s or %d)" % defaults['port'])
401 harness.add_argument('--profile-runner', action='store_true',
401 harness.add_argument('--profile-runner', action='store_true',
402 help='run statprof on run-tests')
402 help='run statprof on run-tests')
403 harness.add_argument("-R", "--restart", action="store_true",
403 harness.add_argument("-R", "--restart", action="store_true",
404 help="restart at last error")
404 help="restart at last error")
405 harness.add_argument("--runs-per-test", type=int, dest="runs_per_test",
405 harness.add_argument("--runs-per-test", type=int, dest="runs_per_test",
406 help="run each test N times (default=1)", default=1)
406 help="run each test N times (default=1)", default=1)
407 harness.add_argument("--shell",
407 harness.add_argument("--shell",
408 help="shell to use (default: $%s or %s)" % defaults['shell'])
408 help="shell to use (default: $%s or %s)" % defaults['shell'])
409 harness.add_argument('--showchannels', action='store_true',
409 harness.add_argument('--showchannels', action='store_true',
410 help='show scheduling channels')
410 help='show scheduling channels')
411 harness.add_argument("--slowtimeout", type=int,
411 harness.add_argument("--slowtimeout", type=int,
412 help="kill errant slow tests after SLOWTIMEOUT seconds"
412 help="kill errant slow tests after SLOWTIMEOUT seconds"
413 " (default: $%s or %d)" % defaults['slowtimeout'])
413 " (default: $%s or %d)" % defaults['slowtimeout'])
414 harness.add_argument("-t", "--timeout", type=int,
414 harness.add_argument("-t", "--timeout", type=int,
415 help="kill errant tests after TIMEOUT seconds"
415 help="kill errant tests after TIMEOUT seconds"
416 " (default: $%s or %d)" % defaults['timeout'])
416 " (default: $%s or %d)" % defaults['timeout'])
417 harness.add_argument("--tmpdir",
417 harness.add_argument("--tmpdir",
418 help="run tests in the given temporary directory"
418 help="run tests in the given temporary directory"
419 " (implies --keep-tmpdir)")
419 " (implies --keep-tmpdir)")
420 harness.add_argument("-v", "--verbose", action="store_true",
420 harness.add_argument("-v", "--verbose", action="store_true",
421 help="output verbose messages")
421 help="output verbose messages")
422
422
423 hgconf = parser.add_argument_group('Mercurial Configuration')
423 hgconf = parser.add_argument_group('Mercurial Configuration')
424 hgconf.add_argument("--chg", action="store_true",
424 hgconf.add_argument("--chg", action="store_true",
425 help="install and use chg wrapper in place of hg")
425 help="install and use chg wrapper in place of hg")
426 hgconf.add_argument("--compiler",
426 hgconf.add_argument("--compiler",
427 help="compiler to build with")
427 help="compiler to build with")
428 hgconf.add_argument('--extra-config-opt', action="append", default=[],
428 hgconf.add_argument('--extra-config-opt', action="append", default=[],
429 help='set the given config opt in the test hgrc')
429 help='set the given config opt in the test hgrc')
430 hgconf.add_argument("-l", "--local", action="store_true",
430 hgconf.add_argument("-l", "--local", action="store_true",
431 help="shortcut for --with-hg=<testdir>/../hg, "
431 help="shortcut for --with-hg=<testdir>/../hg, "
432 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
432 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
433 hgconf.add_argument("--ipv6", action="store_true",
433 hgconf.add_argument("--ipv6", action="store_true",
434 help="prefer IPv6 to IPv4 for network related tests")
434 help="prefer IPv6 to IPv4 for network related tests")
435 hgconf.add_argument("--pure", action="store_true",
435 hgconf.add_argument("--pure", action="store_true",
436 help="use pure Python code instead of C extensions")
436 help="use pure Python code instead of C extensions")
437 hgconf.add_argument("-3", "--py3-warnings", action="store_true",
437 hgconf.add_argument("-3", "--py3-warnings", action="store_true",
438 help="enable Py3k warnings on Python 2.7+")
438 help="enable Py3k warnings on Python 2.7+")
439 hgconf.add_argument("--with-chg", metavar="CHG",
439 hgconf.add_argument("--with-chg", metavar="CHG",
440 help="use specified chg wrapper in place of hg")
440 help="use specified chg wrapper in place of hg")
441 hgconf.add_argument("--with-hg",
441 hgconf.add_argument("--with-hg",
442 metavar="HG",
442 metavar="HG",
443 help="test using specified hg script rather than a "
443 help="test using specified hg script rather than a "
444 "temporary installation")
444 "temporary installation")
445
445
446 reporting = parser.add_argument_group('Results Reporting')
446 reporting = parser.add_argument_group('Results Reporting')
447 reporting.add_argument("-C", "--annotate", action="store_true",
447 reporting.add_argument("-C", "--annotate", action="store_true",
448 help="output files annotated with coverage")
448 help="output files annotated with coverage")
449 reporting.add_argument("--color", choices=["always", "auto", "never"],
449 reporting.add_argument("--color", choices=["always", "auto", "never"],
450 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
450 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
451 help="colorisation: always|auto|never (default: auto)")
451 help="colorisation: always|auto|never (default: auto)")
452 reporting.add_argument("-c", "--cover", action="store_true",
452 reporting.add_argument("-c", "--cover", action="store_true",
453 help="print a test coverage report")
453 help="print a test coverage report")
454 reporting.add_argument('--exceptions', action='store_true',
454 reporting.add_argument('--exceptions', action='store_true',
455 help='log all exceptions and generate an exception report')
455 help='log all exceptions and generate an exception report')
456 reporting.add_argument("-H", "--htmlcov", action="store_true",
456 reporting.add_argument("-H", "--htmlcov", action="store_true",
457 help="create an HTML report of the coverage of the files")
457 help="create an HTML report of the coverage of the files")
458 reporting.add_argument("--json", action="store_true",
458 reporting.add_argument("--json", action="store_true",
459 help="store test result data in 'report.json' file")
459 help="store test result data in 'report.json' file")
460 reporting.add_argument("--outputdir",
460 reporting.add_argument("--outputdir",
461 help="directory to write error logs to (default=test directory)")
461 help="directory to write error logs to (default=test directory)")
462 reporting.add_argument("-n", "--nodiff", action="store_true",
462 reporting.add_argument("-n", "--nodiff", action="store_true",
463 help="skip showing test changes")
463 help="skip showing test changes")
464 reporting.add_argument("-S", "--noskips", action="store_true",
464 reporting.add_argument("-S", "--noskips", action="store_true",
465 help="don't report skip tests verbosely")
465 help="don't report skip tests verbosely")
466 reporting.add_argument("--time", action="store_true",
466 reporting.add_argument("--time", action="store_true",
467 help="time how long each test takes")
467 help="time how long each test takes")
468 reporting.add_argument("--view",
468 reporting.add_argument("--view",
469 help="external diff viewer")
469 help="external diff viewer")
470 reporting.add_argument("--xunit",
470 reporting.add_argument("--xunit",
471 help="record xunit results at specified path")
471 help="record xunit results at specified path")
472
472
473 for option, (envvar, default) in defaults.items():
473 for option, (envvar, default) in defaults.items():
474 defaults[option] = type(default)(os.environ.get(envvar, default))
474 defaults[option] = type(default)(os.environ.get(envvar, default))
475 parser.set_defaults(**defaults)
475 parser.set_defaults(**defaults)
476
476
477 return parser
477 return parser
478
478
479 def parseargs(args, parser):
479 def parseargs(args, parser):
480 """Parse arguments with our OptionParser and validate results."""
480 """Parse arguments with our OptionParser and validate results."""
481 options = parser.parse_args(args)
481 options = parser.parse_args(args)
482
482
483 # jython is always pure
483 # jython is always pure
484 if 'java' in sys.platform or '__pypy__' in sys.modules:
484 if 'java' in sys.platform or '__pypy__' in sys.modules:
485 options.pure = True
485 options.pure = True
486
486
487 if options.local:
487 if options.local:
488 if options.with_hg or options.with_chg:
488 if options.with_hg or options.with_chg:
489 parser.error('--local cannot be used with --with-hg or --with-chg')
489 parser.error('--local cannot be used with --with-hg or --with-chg')
490 testdir = os.path.dirname(_bytespath(canonpath(sys.argv[0])))
490 testdir = os.path.dirname(_bytespath(canonpath(sys.argv[0])))
491 reporootdir = os.path.dirname(testdir)
491 reporootdir = os.path.dirname(testdir)
492 pathandattrs = [(b'hg', 'with_hg')]
492 pathandattrs = [(b'hg', 'with_hg')]
493 if options.chg:
493 if options.chg:
494 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
494 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
495 for relpath, attr in pathandattrs:
495 for relpath, attr in pathandattrs:
496 binpath = os.path.join(reporootdir, relpath)
496 binpath = os.path.join(reporootdir, relpath)
497 if os.name != 'nt' and not os.access(binpath, os.X_OK):
497 if os.name != 'nt' and not os.access(binpath, os.X_OK):
498 parser.error('--local specified, but %r not found or '
498 parser.error('--local specified, but %r not found or '
499 'not executable' % binpath)
499 'not executable' % binpath)
500 setattr(options, attr, _strpath(binpath))
500 setattr(options, attr, _strpath(binpath))
501
501
502 if options.with_hg:
502 if options.with_hg:
503 options.with_hg = canonpath(_bytespath(options.with_hg))
503 options.with_hg = canonpath(_bytespath(options.with_hg))
504 if not (os.path.isfile(options.with_hg) and
504 if not (os.path.isfile(options.with_hg) and
505 os.access(options.with_hg, os.X_OK)):
505 os.access(options.with_hg, os.X_OK)):
506 parser.error('--with-hg must specify an executable hg script')
506 parser.error('--with-hg must specify an executable hg script')
507 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
507 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
508 sys.stderr.write('warning: --with-hg should specify an hg script\n')
508 sys.stderr.write('warning: --with-hg should specify an hg script\n')
509 sys.stderr.flush()
509 sys.stderr.flush()
510
510
511 if (options.chg or options.with_chg) and os.name == 'nt':
511 if (options.chg or options.with_chg) and os.name == 'nt':
512 parser.error('chg does not work on %s' % os.name)
512 parser.error('chg does not work on %s' % os.name)
513 if options.with_chg:
513 if options.with_chg:
514 options.chg = False # no installation to temporary location
514 options.chg = False # no installation to temporary location
515 options.with_chg = canonpath(_bytespath(options.with_chg))
515 options.with_chg = canonpath(_bytespath(options.with_chg))
516 if not (os.path.isfile(options.with_chg) and
516 if not (os.path.isfile(options.with_chg) and
517 os.access(options.with_chg, os.X_OK)):
517 os.access(options.with_chg, os.X_OK)):
518 parser.error('--with-chg must specify a chg executable')
518 parser.error('--with-chg must specify a chg executable')
519 if options.chg and options.with_hg:
519 if options.chg and options.with_hg:
520 # chg shares installation location with hg
520 # chg shares installation location with hg
521 parser.error('--chg does not work when --with-hg is specified '
521 parser.error('--chg does not work when --with-hg is specified '
522 '(use --with-chg instead)')
522 '(use --with-chg instead)')
523
523
524 if options.color == 'always' and not pygmentspresent:
524 if options.color == 'always' and not pygmentspresent:
525 sys.stderr.write('warning: --color=always ignored because '
525 sys.stderr.write('warning: --color=always ignored because '
526 'pygments is not installed\n')
526 'pygments is not installed\n')
527
527
528 if options.bisect_repo and not options.known_good_rev:
528 if options.bisect_repo and not options.known_good_rev:
529 parser.error("--bisect-repo cannot be used without --known-good-rev")
529 parser.error("--bisect-repo cannot be used without --known-good-rev")
530
530
531 global useipv6
531 global useipv6
532 if options.ipv6:
532 if options.ipv6:
533 useipv6 = checksocketfamily('AF_INET6')
533 useipv6 = checksocketfamily('AF_INET6')
534 else:
534 else:
535 # only use IPv6 if IPv4 is unavailable and IPv6 is available
535 # only use IPv6 if IPv4 is unavailable and IPv6 is available
536 useipv6 = ((not checksocketfamily('AF_INET'))
536 useipv6 = ((not checksocketfamily('AF_INET'))
537 and checksocketfamily('AF_INET6'))
537 and checksocketfamily('AF_INET6'))
538
538
539 options.anycoverage = options.cover or options.annotate or options.htmlcov
539 options.anycoverage = options.cover or options.annotate or options.htmlcov
540 if options.anycoverage:
540 if options.anycoverage:
541 try:
541 try:
542 import coverage
542 import coverage
543 covver = version.StrictVersion(coverage.__version__).version
543 covver = version.StrictVersion(coverage.__version__).version
544 if covver < (3, 3):
544 if covver < (3, 3):
545 parser.error('coverage options require coverage 3.3 or later')
545 parser.error('coverage options require coverage 3.3 or later')
546 except ImportError:
546 except ImportError:
547 parser.error('coverage options now require the coverage package')
547 parser.error('coverage options now require the coverage package')
548
548
549 if options.anycoverage and options.local:
549 if options.anycoverage and options.local:
550 # this needs some path mangling somewhere, I guess
550 # this needs some path mangling somewhere, I guess
551 parser.error("sorry, coverage options do not work when --local "
551 parser.error("sorry, coverage options do not work when --local "
552 "is specified")
552 "is specified")
553
553
554 if options.anycoverage and options.with_hg:
554 if options.anycoverage and options.with_hg:
555 parser.error("sorry, coverage options do not work when --with-hg "
555 parser.error("sorry, coverage options do not work when --with-hg "
556 "is specified")
556 "is specified")
557
557
558 global verbose
558 global verbose
559 if options.verbose:
559 if options.verbose:
560 verbose = ''
560 verbose = ''
561
561
562 if options.tmpdir:
562 if options.tmpdir:
563 options.tmpdir = canonpath(options.tmpdir)
563 options.tmpdir = canonpath(options.tmpdir)
564
564
565 if options.jobs < 1:
565 if options.jobs < 1:
566 parser.error('--jobs must be positive')
566 parser.error('--jobs must be positive')
567 if options.interactive and options.debug:
567 if options.interactive and options.debug:
568 parser.error("-i/--interactive and -d/--debug are incompatible")
568 parser.error("-i/--interactive and -d/--debug are incompatible")
569 if options.debug:
569 if options.debug:
570 if options.timeout != defaults['timeout']:
570 if options.timeout != defaults['timeout']:
571 sys.stderr.write(
571 sys.stderr.write(
572 'warning: --timeout option ignored with --debug\n')
572 'warning: --timeout option ignored with --debug\n')
573 if options.slowtimeout != defaults['slowtimeout']:
573 if options.slowtimeout != defaults['slowtimeout']:
574 sys.stderr.write(
574 sys.stderr.write(
575 'warning: --slowtimeout option ignored with --debug\n')
575 'warning: --slowtimeout option ignored with --debug\n')
576 options.timeout = 0
576 options.timeout = 0
577 options.slowtimeout = 0
577 options.slowtimeout = 0
578 if options.py3_warnings:
578 if options.py3_warnings:
579 if PYTHON3:
579 if PYTHON3:
580 parser.error(
580 parser.error(
581 '--py3-warnings can only be used on Python 2.7')
581 '--py3-warnings can only be used on Python 2.7')
582
582
583 if options.blacklist:
583 if options.blacklist:
584 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
584 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
585 if options.whitelist:
585 if options.whitelist:
586 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
586 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
587 else:
587 else:
588 options.whitelisted = {}
588 options.whitelisted = {}
589
589
590 if options.showchannels:
590 if options.showchannels:
591 options.nodiff = True
591 options.nodiff = True
592
592
593 return options
593 return options
594
594
595 def rename(src, dst):
595 def rename(src, dst):
596 """Like os.rename(), trade atomicity and opened files friendliness
596 """Like os.rename(), trade atomicity and opened files friendliness
597 for existing destination support.
597 for existing destination support.
598 """
598 """
599 shutil.copy(src, dst)
599 shutil.copy(src, dst)
600 os.remove(src)
600 os.remove(src)
601
601
602 def makecleanable(path):
602 def makecleanable(path):
603 """Try to fix directory permission recursively so that the entire tree
603 """Try to fix directory permission recursively so that the entire tree
604 can be deleted"""
604 can be deleted"""
605 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
605 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
606 for d in dirnames:
606 for d in dirnames:
607 p = os.path.join(dirpath, d)
607 p = os.path.join(dirpath, d)
608 try:
608 try:
609 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
609 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
610 except OSError:
610 except OSError:
611 pass
611 pass
612
612
613 _unified_diff = difflib.unified_diff
613 _unified_diff = difflib.unified_diff
614 if PYTHON3:
614 if PYTHON3:
615 import functools
615 import functools
616 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
616 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
617
617
618 def getdiff(expected, output, ref, err):
618 def getdiff(expected, output, ref, err):
619 servefail = False
619 servefail = False
620 lines = []
620 lines = []
621 for line in _unified_diff(expected, output, ref, err):
621 for line in _unified_diff(expected, output, ref, err):
622 if line.startswith(b'+++') or line.startswith(b'---'):
622 if line.startswith(b'+++') or line.startswith(b'---'):
623 line = line.replace(b'\\', b'/')
623 line = line.replace(b'\\', b'/')
624 if line.endswith(b' \n'):
624 if line.endswith(b' \n'):
625 line = line[:-2] + b'\n'
625 line = line[:-2] + b'\n'
626 lines.append(line)
626 lines.append(line)
627 if not servefail and line.startswith(
627 if not servefail and line.startswith(
628 b'+ abort: child process failed to start'):
628 b'+ abort: child process failed to start'):
629 servefail = True
629 servefail = True
630
630
631 return servefail, lines
631 return servefail, lines
632
632
633 verbose = False
633 verbose = False
634 def vlog(*msg):
634 def vlog(*msg):
635 """Log only when in verbose mode."""
635 """Log only when in verbose mode."""
636 if verbose is False:
636 if verbose is False:
637 return
637 return
638
638
639 return log(*msg)
639 return log(*msg)
640
640
641 # Bytes that break XML even in a CDATA block: control characters 0-31
641 # Bytes that break XML even in a CDATA block: control characters 0-31
642 # sans \t, \n and \r
642 # sans \t, \n and \r
643 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
643 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
644
644
645 # Match feature conditionalized output lines in the form, capturing the feature
645 # Match feature conditionalized output lines in the form, capturing the feature
646 # list in group 2, and the preceeding line output in group 1:
646 # list in group 2, and the preceeding line output in group 1:
647 #
647 #
648 # output..output (feature !)\n
648 # output..output (feature !)\n
649 optline = re.compile(br'(.*) \((.+?) !\)\n$')
649 optline = re.compile(br'(.*) \((.+?) !\)\n$')
650
650
651 def cdatasafe(data):
651 def cdatasafe(data):
652 """Make a string safe to include in a CDATA block.
652 """Make a string safe to include in a CDATA block.
653
653
654 Certain control characters are illegal in a CDATA block, and
654 Certain control characters are illegal in a CDATA block, and
655 there's no way to include a ]]> in a CDATA either. This function
655 there's no way to include a ]]> in a CDATA either. This function
656 replaces illegal bytes with ? and adds a space between the ]] so
656 replaces illegal bytes with ? and adds a space between the ]] so
657 that it won't break the CDATA block.
657 that it won't break the CDATA block.
658 """
658 """
659 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
659 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
660
660
661 def log(*msg):
661 def log(*msg):
662 """Log something to stdout.
662 """Log something to stdout.
663
663
664 Arguments are strings to print.
664 Arguments are strings to print.
665 """
665 """
666 with iolock:
666 with iolock:
667 if verbose:
667 if verbose:
668 print(verbose, end=' ')
668 print(verbose, end=' ')
669 for m in msg:
669 for m in msg:
670 print(m, end=' ')
670 print(m, end=' ')
671 print()
671 print()
672 sys.stdout.flush()
672 sys.stdout.flush()
673
673
674 def highlightdiff(line, color):
674 def highlightdiff(line, color):
675 if not color:
675 if not color:
676 return line
676 return line
677 assert pygmentspresent
677 assert pygmentspresent
678 return pygments.highlight(line.decode('latin1'), difflexer,
678 return pygments.highlight(line.decode('latin1'), difflexer,
679 terminal256formatter).encode('latin1')
679 terminal256formatter).encode('latin1')
680
680
681 def highlightmsg(msg, color):
681 def highlightmsg(msg, color):
682 if not color:
682 if not color:
683 return msg
683 return msg
684 assert pygmentspresent
684 assert pygmentspresent
685 return pygments.highlight(msg, runnerlexer, runnerformatter)
685 return pygments.highlight(msg, runnerlexer, runnerformatter)
686
686
687 def terminate(proc):
687 def terminate(proc):
688 """Terminate subprocess"""
688 """Terminate subprocess"""
689 vlog('# Terminating process %d' % proc.pid)
689 vlog('# Terminating process %d' % proc.pid)
690 try:
690 try:
691 proc.terminate()
691 proc.terminate()
692 except OSError:
692 except OSError:
693 pass
693 pass
694
694
695 def killdaemons(pidfile):
695 def killdaemons(pidfile):
696 import killdaemons as killmod
696 import killdaemons as killmod
697 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
697 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
698 logfn=vlog)
698 logfn=vlog)
699
699
700 class Test(unittest.TestCase):
700 class Test(unittest.TestCase):
701 """Encapsulates a single, runnable test.
701 """Encapsulates a single, runnable test.
702
702
703 While this class conforms to the unittest.TestCase API, it differs in that
703 While this class conforms to the unittest.TestCase API, it differs in that
704 instances need to be instantiated manually. (Typically, unittest.TestCase
704 instances need to be instantiated manually. (Typically, unittest.TestCase
705 classes are instantiated automatically by scanning modules.)
705 classes are instantiated automatically by scanning modules.)
706 """
706 """
707
707
708 # Status code reserved for skipped tests (used by hghave).
708 # Status code reserved for skipped tests (used by hghave).
709 SKIPPED_STATUS = 80
709 SKIPPED_STATUS = 80
710
710
711 def __init__(self, path, outputdir, tmpdir, keeptmpdir=False,
711 def __init__(self, path, outputdir, tmpdir, keeptmpdir=False,
712 debug=False,
712 debug=False,
713 first=False,
713 first=False,
714 timeout=None,
714 timeout=None,
715 startport=None, extraconfigopts=None,
715 startport=None, extraconfigopts=None,
716 py3warnings=False, shell=None, hgcommand=None,
716 py3warnings=False, shell=None, hgcommand=None,
717 slowtimeout=None, usechg=False,
717 slowtimeout=None, usechg=False,
718 useipv6=False):
718 useipv6=False):
719 """Create a test from parameters.
719 """Create a test from parameters.
720
720
721 path is the full path to the file defining the test.
721 path is the full path to the file defining the test.
722
722
723 tmpdir is the main temporary directory to use for this test.
723 tmpdir is the main temporary directory to use for this test.
724
724
725 keeptmpdir determines whether to keep the test's temporary directory
725 keeptmpdir determines whether to keep the test's temporary directory
726 after execution. It defaults to removal (False).
726 after execution. It defaults to removal (False).
727
727
728 debug mode will make the test execute verbosely, with unfiltered
728 debug mode will make the test execute verbosely, with unfiltered
729 output.
729 output.
730
730
731 timeout controls the maximum run time of the test. It is ignored when
731 timeout controls the maximum run time of the test. It is ignored when
732 debug is True. See slowtimeout for tests with #require slow.
732 debug is True. See slowtimeout for tests with #require slow.
733
733
734 slowtimeout overrides timeout if the test has #require slow.
734 slowtimeout overrides timeout if the test has #require slow.
735
735
736 startport controls the starting port number to use for this test. Each
736 startport controls the starting port number to use for this test. Each
737 test will reserve 3 port numbers for execution. It is the caller's
737 test will reserve 3 port numbers for execution. It is the caller's
738 responsibility to allocate a non-overlapping port range to Test
738 responsibility to allocate a non-overlapping port range to Test
739 instances.
739 instances.
740
740
741 extraconfigopts is an iterable of extra hgrc config options. Values
741 extraconfigopts is an iterable of extra hgrc config options. Values
742 must have the form "key=value" (something understood by hgrc). Values
742 must have the form "key=value" (something understood by hgrc). Values
743 of the form "foo.key=value" will result in "[foo] key=value".
743 of the form "foo.key=value" will result in "[foo] key=value".
744
744
745 py3warnings enables Py3k warnings.
745 py3warnings enables Py3k warnings.
746
746
747 shell is the shell to execute tests in.
747 shell is the shell to execute tests in.
748 """
748 """
749 if timeout is None:
749 if timeout is None:
750 timeout = defaults['timeout']
750 timeout = defaults['timeout']
751 if startport is None:
751 if startport is None:
752 startport = defaults['port']
752 startport = defaults['port']
753 if slowtimeout is None:
753 if slowtimeout is None:
754 slowtimeout = defaults['slowtimeout']
754 slowtimeout = defaults['slowtimeout']
755 self.path = path
755 self.path = path
756 self.bname = os.path.basename(path)
756 self.bname = os.path.basename(path)
757 self.name = _strpath(self.bname)
757 self.name = _strpath(self.bname)
758 self._testdir = os.path.dirname(path)
758 self._testdir = os.path.dirname(path)
759 self._outputdir = outputdir
759 self._outputdir = outputdir
760 self._tmpname = os.path.basename(path)
760 self._tmpname = os.path.basename(path)
761 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
761 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
762
762
763 self._threadtmp = tmpdir
763 self._threadtmp = tmpdir
764 self._keeptmpdir = keeptmpdir
764 self._keeptmpdir = keeptmpdir
765 self._debug = debug
765 self._debug = debug
766 self._first = first
766 self._first = first
767 self._timeout = timeout
767 self._timeout = timeout
768 self._slowtimeout = slowtimeout
768 self._slowtimeout = slowtimeout
769 self._startport = startport
769 self._startport = startport
770 self._extraconfigopts = extraconfigopts or []
770 self._extraconfigopts = extraconfigopts or []
771 self._py3warnings = py3warnings
771 self._py3warnings = py3warnings
772 self._shell = _bytespath(shell)
772 self._shell = _bytespath(shell)
773 self._hgcommand = hgcommand or b'hg'
773 self._hgcommand = hgcommand or b'hg'
774 self._usechg = usechg
774 self._usechg = usechg
775 self._useipv6 = useipv6
775 self._useipv6 = useipv6
776
776
777 self._aborted = False
777 self._aborted = False
778 self._daemonpids = []
778 self._daemonpids = []
779 self._finished = None
779 self._finished = None
780 self._ret = None
780 self._ret = None
781 self._out = None
781 self._out = None
782 self._skipped = None
782 self._skipped = None
783 self._testtmp = None
783 self._testtmp = None
784 self._chgsockdir = None
784 self._chgsockdir = None
785
785
786 self._refout = self.readrefout()
786 self._refout = self.readrefout()
787
787
788 def readrefout(self):
788 def readrefout(self):
789 """read reference output"""
789 """read reference output"""
790 # If we're not in --debug mode and reference output file exists,
790 # If we're not in --debug mode and reference output file exists,
791 # check test output against it.
791 # check test output against it.
792 if self._debug:
792 if self._debug:
793 return None # to match "out is None"
793 return None # to match "out is None"
794 elif os.path.exists(self.refpath):
794 elif os.path.exists(self.refpath):
795 with open(self.refpath, 'rb') as f:
795 with open(self.refpath, 'rb') as f:
796 return f.read().splitlines(True)
796 return f.read().splitlines(True)
797 else:
797 else:
798 return []
798 return []
799
799
800 # needed to get base class __repr__ running
800 # needed to get base class __repr__ running
801 @property
801 @property
802 def _testMethodName(self):
802 def _testMethodName(self):
803 return self.name
803 return self.name
804
804
805 def __str__(self):
805 def __str__(self):
806 return self.name
806 return self.name
807
807
808 def shortDescription(self):
808 def shortDescription(self):
809 return self.name
809 return self.name
810
810
811 def setUp(self):
811 def setUp(self):
812 """Tasks to perform before run()."""
812 """Tasks to perform before run()."""
813 self._finished = False
813 self._finished = False
814 self._ret = None
814 self._ret = None
815 self._out = None
815 self._out = None
816 self._skipped = None
816 self._skipped = None
817
817
818 try:
818 try:
819 os.mkdir(self._threadtmp)
819 os.mkdir(self._threadtmp)
820 except OSError as e:
820 except OSError as e:
821 if e.errno != errno.EEXIST:
821 if e.errno != errno.EEXIST:
822 raise
822 raise
823
823
824 name = self._tmpname
824 name = self._tmpname
825 self._testtmp = os.path.join(self._threadtmp, name)
825 self._testtmp = os.path.join(self._threadtmp, name)
826 os.mkdir(self._testtmp)
826 os.mkdir(self._testtmp)
827
827
828 # Remove any previous output files.
828 # Remove any previous output files.
829 if os.path.exists(self.errpath):
829 if os.path.exists(self.errpath):
830 try:
830 try:
831 os.remove(self.errpath)
831 os.remove(self.errpath)
832 except OSError as e:
832 except OSError as e:
833 # We might have raced another test to clean up a .err
833 # We might have raced another test to clean up a .err
834 # file, so ignore ENOENT when removing a previous .err
834 # file, so ignore ENOENT when removing a previous .err
835 # file.
835 # file.
836 if e.errno != errno.ENOENT:
836 if e.errno != errno.ENOENT:
837 raise
837 raise
838
838
839 if self._usechg:
839 if self._usechg:
840 self._chgsockdir = os.path.join(self._threadtmp,
840 self._chgsockdir = os.path.join(self._threadtmp,
841 b'%s.chgsock' % name)
841 b'%s.chgsock' % name)
842 os.mkdir(self._chgsockdir)
842 os.mkdir(self._chgsockdir)
843
843
844 def run(self, result):
844 def run(self, result):
845 """Run this test and report results against a TestResult instance."""
845 """Run this test and report results against a TestResult instance."""
846 # This function is extremely similar to unittest.TestCase.run(). Once
846 # This function is extremely similar to unittest.TestCase.run(). Once
847 # we require Python 2.7 (or at least its version of unittest), this
847 # we require Python 2.7 (or at least its version of unittest), this
848 # function can largely go away.
848 # function can largely go away.
849 self._result = result
849 self._result = result
850 result.startTest(self)
850 result.startTest(self)
851 try:
851 try:
852 try:
852 try:
853 self.setUp()
853 self.setUp()
854 except (KeyboardInterrupt, SystemExit):
854 except (KeyboardInterrupt, SystemExit):
855 self._aborted = True
855 self._aborted = True
856 raise
856 raise
857 except Exception:
857 except Exception:
858 result.addError(self, sys.exc_info())
858 result.addError(self, sys.exc_info())
859 return
859 return
860
860
861 success = False
861 success = False
862 try:
862 try:
863 self.runTest()
863 self.runTest()
864 except KeyboardInterrupt:
864 except KeyboardInterrupt:
865 self._aborted = True
865 self._aborted = True
866 raise
866 raise
867 except unittest.SkipTest as e:
867 except unittest.SkipTest as e:
868 result.addSkip(self, str(e))
868 result.addSkip(self, str(e))
869 # The base class will have already counted this as a
869 # The base class will have already counted this as a
870 # test we "ran", but we want to exclude skipped tests
870 # test we "ran", but we want to exclude skipped tests
871 # from those we count towards those run.
871 # from those we count towards those run.
872 result.testsRun -= 1
872 result.testsRun -= 1
873 except self.failureException as e:
873 except self.failureException as e:
874 # This differs from unittest in that we don't capture
874 # This differs from unittest in that we don't capture
875 # the stack trace. This is for historical reasons and
875 # the stack trace. This is for historical reasons and
876 # this decision could be revisited in the future,
876 # this decision could be revisited in the future,
877 # especially for PythonTest instances.
877 # especially for PythonTest instances.
878 if result.addFailure(self, str(e)):
878 if result.addFailure(self, str(e)):
879 success = True
879 success = True
880 except Exception:
880 except Exception:
881 result.addError(self, sys.exc_info())
881 result.addError(self, sys.exc_info())
882 else:
882 else:
883 success = True
883 success = True
884
884
885 try:
885 try:
886 self.tearDown()
886 self.tearDown()
887 except (KeyboardInterrupt, SystemExit):
887 except (KeyboardInterrupt, SystemExit):
888 self._aborted = True
888 self._aborted = True
889 raise
889 raise
890 except Exception:
890 except Exception:
891 result.addError(self, sys.exc_info())
891 result.addError(self, sys.exc_info())
892 success = False
892 success = False
893
893
894 if success:
894 if success:
895 result.addSuccess(self)
895 result.addSuccess(self)
896 finally:
896 finally:
897 result.stopTest(self, interrupted=self._aborted)
897 result.stopTest(self, interrupted=self._aborted)
898
898
899 def runTest(self):
899 def runTest(self):
900 """Run this test instance.
900 """Run this test instance.
901
901
902 This will return a tuple describing the result of the test.
902 This will return a tuple describing the result of the test.
903 """
903 """
904 env = self._getenv()
904 env = self._getenv()
905 self._genrestoreenv(env)
905 self._genrestoreenv(env)
906 self._daemonpids.append(env['DAEMON_PIDS'])
906 self._daemonpids.append(env['DAEMON_PIDS'])
907 self._createhgrc(env['HGRCPATH'])
907 self._createhgrc(env['HGRCPATH'])
908
908
909 vlog('# Test', self.name)
909 vlog('# Test', self.name)
910
910
911 ret, out = self._run(env)
911 ret, out = self._run(env)
912 self._finished = True
912 self._finished = True
913 self._ret = ret
913 self._ret = ret
914 self._out = out
914 self._out = out
915
915
916 def describe(ret):
916 def describe(ret):
917 if ret < 0:
917 if ret < 0:
918 return 'killed by signal: %d' % -ret
918 return 'killed by signal: %d' % -ret
919 return 'returned error code %d' % ret
919 return 'returned error code %d' % ret
920
920
921 self._skipped = False
921 self._skipped = False
922
922
923 if ret == self.SKIPPED_STATUS:
923 if ret == self.SKIPPED_STATUS:
924 if out is None: # Debug mode, nothing to parse.
924 if out is None: # Debug mode, nothing to parse.
925 missing = ['unknown']
925 missing = ['unknown']
926 failed = None
926 failed = None
927 else:
927 else:
928 missing, failed = TTest.parsehghaveoutput(out)
928 missing, failed = TTest.parsehghaveoutput(out)
929
929
930 if not missing:
930 if not missing:
931 missing = ['skipped']
931 missing = ['skipped']
932
932
933 if failed:
933 if failed:
934 self.fail('hg have failed checking for %s' % failed[-1])
934 self.fail('hg have failed checking for %s' % failed[-1])
935 else:
935 else:
936 self._skipped = True
936 self._skipped = True
937 raise unittest.SkipTest(missing[-1])
937 raise unittest.SkipTest(missing[-1])
938 elif ret == 'timeout':
938 elif ret == 'timeout':
939 self.fail('timed out')
939 self.fail('timed out')
940 elif ret is False:
940 elif ret is False:
941 self.fail('no result code from test')
941 self.fail('no result code from test')
942 elif out != self._refout:
942 elif out != self._refout:
943 # Diff generation may rely on written .err file.
943 # Diff generation may rely on written .err file.
944 if ((ret != 0 or out != self._refout) and not self._skipped
944 if ((ret != 0 or out != self._refout) and not self._skipped
945 and not self._debug):
945 and not self._debug):
946 with open(self.errpath, 'wb') as f:
946 with open(self.errpath, 'wb') as f:
947 for line in out:
947 for line in out:
948 f.write(line)
948 f.write(line)
949
949
950 # The result object handles diff calculation for us.
950 # The result object handles diff calculation for us.
951 with firstlock:
951 with firstlock:
952 if self._result.addOutputMismatch(self, ret, out, self._refout):
952 if self._result.addOutputMismatch(self, ret, out, self._refout):
953 # change was accepted, skip failing
953 # change was accepted, skip failing
954 return
954 return
955 if self._first:
955 if self._first:
956 global firsterror
956 global firsterror
957 firsterror = True
957 firsterror = True
958
958
959 if ret:
959 if ret:
960 msg = 'output changed and ' + describe(ret)
960 msg = 'output changed and ' + describe(ret)
961 else:
961 else:
962 msg = 'output changed'
962 msg = 'output changed'
963
963
964 self.fail(msg)
964 self.fail(msg)
965 elif ret:
965 elif ret:
966 self.fail(describe(ret))
966 self.fail(describe(ret))
967
967
968 def tearDown(self):
968 def tearDown(self):
969 """Tasks to perform after run()."""
969 """Tasks to perform after run()."""
970 for entry in self._daemonpids:
970 for entry in self._daemonpids:
971 killdaemons(entry)
971 killdaemons(entry)
972 self._daemonpids = []
972 self._daemonpids = []
973
973
974 if self._keeptmpdir:
974 if self._keeptmpdir:
975 log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
975 log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
976 (self._testtmp.decode('utf-8'),
976 (self._testtmp.decode('utf-8'),
977 self._threadtmp.decode('utf-8')))
977 self._threadtmp.decode('utf-8')))
978 else:
978 else:
979 try:
979 try:
980 shutil.rmtree(self._testtmp)
980 shutil.rmtree(self._testtmp)
981 except OSError:
981 except OSError:
982 # unreadable directory may be left in $TESTTMP; fix permission
982 # unreadable directory may be left in $TESTTMP; fix permission
983 # and try again
983 # and try again
984 makecleanable(self._testtmp)
984 makecleanable(self._testtmp)
985 shutil.rmtree(self._testtmp, True)
985 shutil.rmtree(self._testtmp, True)
986 shutil.rmtree(self._threadtmp, True)
986 shutil.rmtree(self._threadtmp, True)
987
987
988 if self._usechg:
988 if self._usechg:
989 # chgservers will stop automatically after they find the socket
989 # chgservers will stop automatically after they find the socket
990 # files are deleted
990 # files are deleted
991 shutil.rmtree(self._chgsockdir, True)
991 shutil.rmtree(self._chgsockdir, True)
992
992
993 if ((self._ret != 0 or self._out != self._refout) and not self._skipped
993 if ((self._ret != 0 or self._out != self._refout) and not self._skipped
994 and not self._debug and self._out):
994 and not self._debug and self._out):
995 with open(self.errpath, 'wb') as f:
995 with open(self.errpath, 'wb') as f:
996 for line in self._out:
996 for line in self._out:
997 f.write(line)
997 f.write(line)
998
998
999 vlog("# Ret was:", self._ret, '(%s)' % self.name)
999 vlog("# Ret was:", self._ret, '(%s)' % self.name)
1000
1000
1001 def _run(self, env):
1001 def _run(self, env):
1002 # This should be implemented in child classes to run tests.
1002 # This should be implemented in child classes to run tests.
1003 raise unittest.SkipTest('unknown test type')
1003 raise unittest.SkipTest('unknown test type')
1004
1004
1005 def abort(self):
1005 def abort(self):
1006 """Terminate execution of this test."""
1006 """Terminate execution of this test."""
1007 self._aborted = True
1007 self._aborted = True
1008
1008
1009 def _portmap(self, i):
1009 def _portmap(self, i):
1010 offset = b'' if i == 0 else b'%d' % i
1010 offset = b'' if i == 0 else b'%d' % i
1011 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
1011 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
1012
1012
1013 def _getreplacements(self):
1013 def _getreplacements(self):
1014 """Obtain a mapping of text replacements to apply to test output.
1014 """Obtain a mapping of text replacements to apply to test output.
1015
1015
1016 Test output needs to be normalized so it can be compared to expected
1016 Test output needs to be normalized so it can be compared to expected
1017 output. This function defines how some of that normalization will
1017 output. This function defines how some of that normalization will
1018 occur.
1018 occur.
1019 """
1019 """
1020 r = [
1020 r = [
1021 # This list should be parallel to defineport in _getenv
1021 # This list should be parallel to defineport in _getenv
1022 self._portmap(0),
1022 self._portmap(0),
1023 self._portmap(1),
1023 self._portmap(1),
1024 self._portmap(2),
1024 self._portmap(2),
1025 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1025 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1026 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1026 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1027 ]
1027 ]
1028 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1028 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1029
1029
1030 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1030 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1031
1031
1032 if os.path.exists(replacementfile):
1032 if os.path.exists(replacementfile):
1033 data = {}
1033 data = {}
1034 with open(replacementfile, mode='rb') as source:
1034 with open(replacementfile, mode='rb') as source:
1035 # the intermediate 'compile' step help with debugging
1035 # the intermediate 'compile' step help with debugging
1036 code = compile(source.read(), replacementfile, 'exec')
1036 code = compile(source.read(), replacementfile, 'exec')
1037 exec(code, data)
1037 exec(code, data)
1038 for value in data.get('substitutions', ()):
1038 for value in data.get('substitutions', ()):
1039 if len(value) != 2:
1039 if len(value) != 2:
1040 msg = 'malformatted substitution in %s: %r'
1040 msg = 'malformatted substitution in %s: %r'
1041 msg %= (replacementfile, value)
1041 msg %= (replacementfile, value)
1042 raise ValueError(msg)
1042 raise ValueError(msg)
1043 r.append(value)
1043 r.append(value)
1044 return r
1044 return r
1045
1045
1046 def _escapepath(self, p):
1046 def _escapepath(self, p):
1047 if os.name == 'nt':
1047 if os.name == 'nt':
1048 return (
1048 return (
1049 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
1049 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
1050 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
1050 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
1051 for c in [p[i:i + 1] for i in range(len(p))]))
1051 for c in [p[i:i + 1] for i in range(len(p))]))
1052 )
1052 )
1053 else:
1053 else:
1054 return re.escape(p)
1054 return re.escape(p)
1055
1055
1056 def _localip(self):
1056 def _localip(self):
1057 if self._useipv6:
1057 if self._useipv6:
1058 return b'::1'
1058 return b'::1'
1059 else:
1059 else:
1060 return b'127.0.0.1'
1060 return b'127.0.0.1'
1061
1061
1062 def _genrestoreenv(self, testenv):
1062 def _genrestoreenv(self, testenv):
1063 """Generate a script that can be used by tests to restore the original
1063 """Generate a script that can be used by tests to restore the original
1064 environment."""
1064 environment."""
1065 # Put the restoreenv script inside self._threadtmp
1065 # Put the restoreenv script inside self._threadtmp
1066 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1066 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1067 testenv['HGTEST_RESTOREENV'] = _strpath(scriptpath)
1067 testenv['HGTEST_RESTOREENV'] = _strpath(scriptpath)
1068
1068
1069 # Only restore environment variable names that the shell allows
1069 # Only restore environment variable names that the shell allows
1070 # us to export.
1070 # us to export.
1071 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1071 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1072
1072
1073 # Do not restore these variables; otherwise tests would fail.
1073 # Do not restore these variables; otherwise tests would fail.
1074 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1074 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1075
1075
1076 with open(scriptpath, 'w') as envf:
1076 with open(scriptpath, 'w') as envf:
1077 for name, value in origenviron.items():
1077 for name, value in origenviron.items():
1078 if not name_regex.match(name):
1078 if not name_regex.match(name):
1079 # Skip environment variables with unusual names not
1079 # Skip environment variables with unusual names not
1080 # allowed by most shells.
1080 # allowed by most shells.
1081 continue
1081 continue
1082 if name in reqnames:
1082 if name in reqnames:
1083 continue
1083 continue
1084 envf.write('%s=%s\n' % (name, shellquote(value)))
1084 envf.write('%s=%s\n' % (name, shellquote(value)))
1085
1085
1086 for name in testenv:
1086 for name in testenv:
1087 if name in origenviron or name in reqnames:
1087 if name in origenviron or name in reqnames:
1088 continue
1088 continue
1089 envf.write('unset %s\n' % (name,))
1089 envf.write('unset %s\n' % (name,))
1090
1090
1091 def _getenv(self):
1091 def _getenv(self):
1092 """Obtain environment variables to use during test execution."""
1092 """Obtain environment variables to use during test execution."""
1093 def defineport(i):
1093 def defineport(i):
1094 offset = '' if i == 0 else '%s' % i
1094 offset = '' if i == 0 else '%s' % i
1095 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1095 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1096 env = os.environ.copy()
1096 env = os.environ.copy()
1097 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1097 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1098 env['HGEMITWARNINGS'] = '1'
1098 env['HGEMITWARNINGS'] = '1'
1099 env['TESTTMP'] = _strpath(self._testtmp)
1099 env['TESTTMP'] = _strpath(self._testtmp)
1100 env['TESTNAME'] = self.name
1100 env['TESTNAME'] = self.name
1101 env['HOME'] = _strpath(self._testtmp)
1101 env['HOME'] = _strpath(self._testtmp)
1102 # This number should match portneeded in _getport
1102 # This number should match portneeded in _getport
1103 for port in xrange(3):
1103 for port in xrange(3):
1104 # This list should be parallel to _portmap in _getreplacements
1104 # This list should be parallel to _portmap in _getreplacements
1105 defineport(port)
1105 defineport(port)
1106 env["HGRCPATH"] = _strpath(os.path.join(self._threadtmp, b'.hgrc'))
1106 env["HGRCPATH"] = _strpath(os.path.join(self._threadtmp, b'.hgrc'))
1107 env["DAEMON_PIDS"] = _strpath(os.path.join(self._threadtmp,
1107 env["DAEMON_PIDS"] = _strpath(os.path.join(self._threadtmp,
1108 b'daemon.pids'))
1108 b'daemon.pids'))
1109 env["HGEDITOR"] = ('"' + sysexecutable + '"'
1109 env["HGEDITOR"] = ('"' + sysexecutable + '"'
1110 + ' -c "import sys; sys.exit(0)"')
1110 + ' -c "import sys; sys.exit(0)"')
1111 env["HGUSER"] = "test"
1111 env["HGUSER"] = "test"
1112 env["HGENCODING"] = "ascii"
1112 env["HGENCODING"] = "ascii"
1113 env["HGENCODINGMODE"] = "strict"
1113 env["HGENCODINGMODE"] = "strict"
1114 env["HGHOSTNAME"] = "test-hostname"
1114 env["HGHOSTNAME"] = "test-hostname"
1115 env['HGIPV6'] = str(int(self._useipv6))
1115 env['HGIPV6'] = str(int(self._useipv6))
1116 # See contrib/catapipe.py for how to use this functionality.
1116 # See contrib/catapipe.py for how to use this functionality.
1117 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1117 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1118 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1118 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1119 # non-test one in as a default, otherwise set to devnull
1119 # non-test one in as a default, otherwise set to devnull
1120 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1120 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1121 'HGCATAPULTSERVERPIPE', os.devnull)
1121 'HGCATAPULTSERVERPIPE', os.devnull)
1122
1122
1123 extraextensions = []
1123 extraextensions = []
1124 for opt in self._extraconfigopts:
1124 for opt in self._extraconfigopts:
1125 section, key = opt.encode('utf-8').split(b'.', 1)
1125 section, key = opt.encode('utf-8').split(b'.', 1)
1126 if section != 'extensions':
1126 if section != 'extensions':
1127 continue
1127 continue
1128 name = key.split(b'=', 1)[0]
1128 name = key.split(b'=', 1)[0]
1129 extraextensions.append(name)
1129 extraextensions.append(name)
1130
1130
1131 if extraextensions:
1131 if extraextensions:
1132 env['HGTESTEXTRAEXTENSIONS'] = b' '.join(extraextensions)
1132 env['HGTESTEXTRAEXTENSIONS'] = b' '.join(extraextensions)
1133
1133
1134 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1134 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1135 # IP addresses.
1135 # IP addresses.
1136 env['LOCALIP'] = _strpath(self._localip())
1136 env['LOCALIP'] = _strpath(self._localip())
1137
1137
1138 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1138 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1139 # but this is needed for testing python instances like dummyssh,
1139 # but this is needed for testing python instances like dummyssh,
1140 # dummysmtpd.py, and dumbhttp.py.
1140 # dummysmtpd.py, and dumbhttp.py.
1141 if PYTHON3 and os.name == 'nt':
1141 if PYTHON3 and os.name == 'nt':
1142 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1142 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1143
1143
1144 # Reset some environment variables to well-known values so that
1144 # Reset some environment variables to well-known values so that
1145 # the tests produce repeatable output.
1145 # the tests produce repeatable output.
1146 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1146 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1147 env['TZ'] = 'GMT'
1147 env['TZ'] = 'GMT'
1148 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1148 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1149 env['COLUMNS'] = '80'
1149 env['COLUMNS'] = '80'
1150 env['TERM'] = 'xterm'
1150 env['TERM'] = 'xterm'
1151
1151
1152 dropped = [
1152 dropped = [
1153 'CDPATH',
1153 'CDPATH',
1154 'CHGDEBUG',
1154 'CHGDEBUG',
1155 'EDITOR',
1155 'EDITOR',
1156 'GREP_OPTIONS',
1156 'GREP_OPTIONS',
1157 'HG',
1157 'HG',
1158 'HGMERGE',
1158 'HGMERGE',
1159 'HGPLAIN',
1159 'HGPLAIN',
1160 'HGPLAINEXCEPT',
1160 'HGPLAINEXCEPT',
1161 'HGPROF',
1161 'HGPROF',
1162 'http_proxy',
1162 'http_proxy',
1163 'no_proxy',
1163 'no_proxy',
1164 'NO_PROXY',
1164 'NO_PROXY',
1165 'PAGER',
1165 'PAGER',
1166 'VISUAL',
1166 'VISUAL',
1167 ]
1167 ]
1168
1168
1169 for k in dropped:
1169 for k in dropped:
1170 if k in env:
1170 if k in env:
1171 del env[k]
1171 del env[k]
1172
1172
1173 # unset env related to hooks
1173 # unset env related to hooks
1174 for k in list(env):
1174 for k in list(env):
1175 if k.startswith('HG_'):
1175 if k.startswith('HG_'):
1176 del env[k]
1176 del env[k]
1177
1177
1178 if self._usechg:
1178 if self._usechg:
1179 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1179 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1180
1180
1181 return env
1181 return env
1182
1182
1183 def _createhgrc(self, path):
1183 def _createhgrc(self, path):
1184 """Create an hgrc file for this test."""
1184 """Create an hgrc file for this test."""
1185 with open(path, 'wb') as hgrc:
1185 with open(path, 'wb') as hgrc:
1186 hgrc.write(b'[ui]\n')
1186 hgrc.write(b'[ui]\n')
1187 hgrc.write(b'slash = True\n')
1187 hgrc.write(b'slash = True\n')
1188 hgrc.write(b'interactive = False\n')
1188 hgrc.write(b'interactive = False\n')
1189 hgrc.write(b'merge = internal:merge\n')
1189 hgrc.write(b'merge = internal:merge\n')
1190 hgrc.write(b'mergemarkers = detailed\n')
1190 hgrc.write(b'mergemarkers = detailed\n')
1191 hgrc.write(b'promptecho = True\n')
1191 hgrc.write(b'promptecho = True\n')
1192 hgrc.write(b'[defaults]\n')
1192 hgrc.write(b'[defaults]\n')
1193 hgrc.write(b'[devel]\n')
1193 hgrc.write(b'[devel]\n')
1194 hgrc.write(b'all-warnings = true\n')
1194 hgrc.write(b'all-warnings = true\n')
1195 hgrc.write(b'default-date = 0 0\n')
1195 hgrc.write(b'default-date = 0 0\n')
1196 hgrc.write(b'[largefiles]\n')
1196 hgrc.write(b'[largefiles]\n')
1197 hgrc.write(b'usercache = %s\n' %
1197 hgrc.write(b'usercache = %s\n' %
1198 (os.path.join(self._testtmp, b'.cache/largefiles')))
1198 (os.path.join(self._testtmp, b'.cache/largefiles')))
1199 hgrc.write(b'[lfs]\n')
1199 hgrc.write(b'[lfs]\n')
1200 hgrc.write(b'usercache = %s\n' %
1200 hgrc.write(b'usercache = %s\n' %
1201 (os.path.join(self._testtmp, b'.cache/lfs')))
1201 (os.path.join(self._testtmp, b'.cache/lfs')))
1202 hgrc.write(b'[web]\n')
1202 hgrc.write(b'[web]\n')
1203 hgrc.write(b'address = localhost\n')
1203 hgrc.write(b'address = localhost\n')
1204 hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
1204 hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
1205 hgrc.write(b'server-header = testing stub value\n')
1205 hgrc.write(b'server-header = testing stub value\n')
1206
1206
1207 for opt in self._extraconfigopts:
1207 for opt in self._extraconfigopts:
1208 section, key = opt.encode('utf-8').split(b'.', 1)
1208 section, key = opt.encode('utf-8').split(b'.', 1)
1209 assert b'=' in key, ('extra config opt %s must '
1209 assert b'=' in key, ('extra config opt %s must '
1210 'have an = for assignment' % opt)
1210 'have an = for assignment' % opt)
1211 hgrc.write(b'[%s]\n%s\n' % (section, key))
1211 hgrc.write(b'[%s]\n%s\n' % (section, key))
1212
1212
1213 def fail(self, msg):
1213 def fail(self, msg):
1214 # unittest differentiates between errored and failed.
1214 # unittest differentiates between errored and failed.
1215 # Failed is denoted by AssertionError (by default at least).
1215 # Failed is denoted by AssertionError (by default at least).
1216 raise AssertionError(msg)
1216 raise AssertionError(msg)
1217
1217
1218 def _runcommand(self, cmd, env, normalizenewlines=False):
1218 def _runcommand(self, cmd, env, normalizenewlines=False):
1219 """Run command in a sub-process, capturing the output (stdout and
1219 """Run command in a sub-process, capturing the output (stdout and
1220 stderr).
1220 stderr).
1221
1221
1222 Return a tuple (exitcode, output). output is None in debug mode.
1222 Return a tuple (exitcode, output). output is None in debug mode.
1223 """
1223 """
1224 if self._debug:
1224 if self._debug:
1225 proc = subprocess.Popen(_strpath(cmd), shell=True,
1225 proc = subprocess.Popen(_strpath(cmd), shell=True,
1226 cwd=_strpath(self._testtmp),
1226 cwd=_strpath(self._testtmp),
1227 env=env)
1227 env=env)
1228 ret = proc.wait()
1228 ret = proc.wait()
1229 return (ret, None)
1229 return (ret, None)
1230
1230
1231 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1231 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1232 def cleanup():
1232 def cleanup():
1233 terminate(proc)
1233 terminate(proc)
1234 ret = proc.wait()
1234 ret = proc.wait()
1235 if ret == 0:
1235 if ret == 0:
1236 ret = signal.SIGTERM << 8
1236 ret = signal.SIGTERM << 8
1237 killdaemons(env['DAEMON_PIDS'])
1237 killdaemons(env['DAEMON_PIDS'])
1238 return ret
1238 return ret
1239
1239
1240 proc.tochild.close()
1240 proc.tochild.close()
1241
1241
1242 try:
1242 try:
1243 output = proc.fromchild.read()
1243 output = proc.fromchild.read()
1244 except KeyboardInterrupt:
1244 except KeyboardInterrupt:
1245 vlog('# Handling keyboard interrupt')
1245 vlog('# Handling keyboard interrupt')
1246 cleanup()
1246 cleanup()
1247 raise
1247 raise
1248
1248
1249 ret = proc.wait()
1249 ret = proc.wait()
1250 if wifexited(ret):
1250 if wifexited(ret):
1251 ret = os.WEXITSTATUS(ret)
1251 ret = os.WEXITSTATUS(ret)
1252
1252
1253 if proc.timeout:
1253 if proc.timeout:
1254 ret = 'timeout'
1254 ret = 'timeout'
1255
1255
1256 if ret:
1256 if ret:
1257 killdaemons(env['DAEMON_PIDS'])
1257 killdaemons(env['DAEMON_PIDS'])
1258
1258
1259 for s, r in self._getreplacements():
1259 for s, r in self._getreplacements():
1260 output = re.sub(s, r, output)
1260 output = re.sub(s, r, output)
1261
1261
1262 if normalizenewlines:
1262 if normalizenewlines:
1263 output = output.replace(b'\r\n', b'\n')
1263 output = output.replace(b'\r\n', b'\n')
1264
1264
1265 return ret, output.splitlines(True)
1265 return ret, output.splitlines(True)
1266
1266
1267 class PythonTest(Test):
1267 class PythonTest(Test):
1268 """A Python-based test."""
1268 """A Python-based test."""
1269
1269
1270 @property
1270 @property
1271 def refpath(self):
1271 def refpath(self):
1272 return os.path.join(self._testdir, b'%s.out' % self.bname)
1272 return os.path.join(self._testdir, b'%s.out' % self.bname)
1273
1273
1274 def _run(self, env):
1274 def _run(self, env):
1275 py3switch = self._py3warnings and b' -3' or b''
1275 py3switch = self._py3warnings and b' -3' or b''
1276 # Quote the python(3) executable for Windows
1276 # Quote the python(3) executable for Windows
1277 cmd = b'"%s"%s "%s"' % (PYTHON, py3switch, self.path)
1277 cmd = b'"%s"%s "%s"' % (PYTHON, py3switch, self.path)
1278 vlog("# Running", cmd)
1278 vlog("# Running", cmd)
1279 normalizenewlines = os.name == 'nt'
1279 normalizenewlines = os.name == 'nt'
1280 result = self._runcommand(cmd, env,
1280 result = self._runcommand(cmd, env,
1281 normalizenewlines=normalizenewlines)
1281 normalizenewlines=normalizenewlines)
1282 if self._aborted:
1282 if self._aborted:
1283 raise KeyboardInterrupt()
1283 raise KeyboardInterrupt()
1284
1284
1285 return result
1285 return result
1286
1286
1287 # Some glob patterns apply only in some circumstances, so the script
1287 # Some glob patterns apply only in some circumstances, so the script
1288 # might want to remove (glob) annotations that otherwise should be
1288 # might want to remove (glob) annotations that otherwise should be
1289 # retained.
1289 # retained.
1290 checkcodeglobpats = [
1290 checkcodeglobpats = [
1291 # On Windows it looks like \ doesn't require a (glob), but we know
1291 # On Windows it looks like \ doesn't require a (glob), but we know
1292 # better.
1292 # better.
1293 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1293 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1294 re.compile(br'^moving \S+/.*[^)]$'),
1294 re.compile(br'^moving \S+/.*[^)]$'),
1295 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1295 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1296 # Not all platforms have 127.0.0.1 as loopback (though most do),
1296 # Not all platforms have 127.0.0.1 as loopback (though most do),
1297 # so we always glob that too.
1297 # so we always glob that too.
1298 re.compile(br'.*\$LOCALIP.*$'),
1298 re.compile(br'.*\$LOCALIP.*$'),
1299 ]
1299 ]
1300
1300
1301 bchr = chr
1301 bchr = chr
1302 if PYTHON3:
1302 if PYTHON3:
1303 bchr = lambda x: bytes([x])
1303 bchr = lambda x: bytes([x])
1304
1304
1305 WARN_UNDEFINED = 1
1305 WARN_UNDEFINED = 1
1306 WARN_YES = 2
1306 WARN_YES = 2
1307 WARN_NO = 3
1307 WARN_NO = 3
1308
1308
1309 MARK_OPTIONAL = b" (?)\n"
1309 MARK_OPTIONAL = b" (?)\n"
1310
1310
1311 def isoptional(line):
1311 def isoptional(line):
1312 return line.endswith(MARK_OPTIONAL)
1312 return line.endswith(MARK_OPTIONAL)
1313
1313
1314 class TTest(Test):
1314 class TTest(Test):
1315 """A "t test" is a test backed by a .t file."""
1315 """A "t test" is a test backed by a .t file."""
1316
1316
1317 SKIPPED_PREFIX = b'skipped: '
1317 SKIPPED_PREFIX = b'skipped: '
1318 FAILED_PREFIX = b'hghave check failed: '
1318 FAILED_PREFIX = b'hghave check failed: '
1319 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1319 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1320
1320
1321 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1321 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1322 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
1322 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
1323 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1323 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1324
1324
1325 def __init__(self, path, *args, **kwds):
1325 def __init__(self, path, *args, **kwds):
1326 # accept an extra "case" parameter
1326 # accept an extra "case" parameter
1327 case = kwds.pop('case', [])
1327 case = kwds.pop('case', [])
1328 self._case = case
1328 self._case = case
1329 self._allcases = {x for y in parsettestcases(path) for x in y}
1329 self._allcases = {x for y in parsettestcases(path) for x in y}
1330 super(TTest, self).__init__(path, *args, **kwds)
1330 super(TTest, self).__init__(path, *args, **kwds)
1331 if case:
1331 if case:
1332 casepath = b'#'.join(case)
1332 casepath = b'#'.join(case)
1333 self.name = '%s#%s' % (self.name, _strpath(casepath))
1333 self.name = '%s#%s' % (self.name, _strpath(casepath))
1334 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1334 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1335 self._tmpname += b'-%s' % casepath
1335 self._tmpname += b'-%s' % casepath
1336 self._have = {}
1336 self._have = {}
1337
1337
1338 @property
1338 @property
1339 def refpath(self):
1339 def refpath(self):
1340 return os.path.join(self._testdir, self.bname)
1340 return os.path.join(self._testdir, self.bname)
1341
1341
1342 def _run(self, env):
1342 def _run(self, env):
1343 with open(self.path, 'rb') as f:
1343 with open(self.path, 'rb') as f:
1344 lines = f.readlines()
1344 lines = f.readlines()
1345
1345
1346 # .t file is both reference output and the test input, keep reference
1346 # .t file is both reference output and the test input, keep reference
1347 # output updated with the the test input. This avoids some race
1347 # output updated with the the test input. This avoids some race
1348 # conditions where the reference output does not match the actual test.
1348 # conditions where the reference output does not match the actual test.
1349 if self._refout is not None:
1349 if self._refout is not None:
1350 self._refout = lines
1350 self._refout = lines
1351
1351
1352 salt, script, after, expected = self._parsetest(lines)
1352 salt, script, after, expected = self._parsetest(lines)
1353
1353
1354 # Write out the generated script.
1354 # Write out the generated script.
1355 fname = b'%s.sh' % self._testtmp
1355 fname = b'%s.sh' % self._testtmp
1356 with open(fname, 'wb') as f:
1356 with open(fname, 'wb') as f:
1357 for l in script:
1357 for l in script:
1358 f.write(l)
1358 f.write(l)
1359
1359
1360 cmd = b'%s "%s"' % (self._shell, fname)
1360 cmd = b'%s "%s"' % (self._shell, fname)
1361 vlog("# Running", cmd)
1361 vlog("# Running", cmd)
1362
1362
1363 exitcode, output = self._runcommand(cmd, env)
1363 exitcode, output = self._runcommand(cmd, env)
1364
1364
1365 if self._aborted:
1365 if self._aborted:
1366 raise KeyboardInterrupt()
1366 raise KeyboardInterrupt()
1367
1367
1368 # Do not merge output if skipped. Return hghave message instead.
1368 # Do not merge output if skipped. Return hghave message instead.
1369 # Similarly, with --debug, output is None.
1369 # Similarly, with --debug, output is None.
1370 if exitcode == self.SKIPPED_STATUS or output is None:
1370 if exitcode == self.SKIPPED_STATUS or output is None:
1371 return exitcode, output
1371 return exitcode, output
1372
1372
1373 return self._processoutput(exitcode, output, salt, after, expected)
1373 return self._processoutput(exitcode, output, salt, after, expected)
1374
1374
1375 def _hghave(self, reqs):
1375 def _hghave(self, reqs):
1376 allreqs = b' '.join(reqs)
1376 allreqs = b' '.join(reqs)
1377
1377
1378 self._detectslow(reqs)
1378 self._detectslow(reqs)
1379
1379
1380 if allreqs in self._have:
1380 if allreqs in self._have:
1381 return self._have.get(allreqs)
1381 return self._have.get(allreqs)
1382
1382
1383 # TODO do something smarter when all other uses of hghave are gone.
1383 # TODO do something smarter when all other uses of hghave are gone.
1384 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
1384 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
1385 tdir = runtestdir.replace(b'\\', b'/')
1385 tdir = runtestdir.replace(b'\\', b'/')
1386 proc = Popen4(b'%s -c "%s/hghave %s"' %
1386 proc = Popen4(b'%s -c "%s/hghave %s"' %
1387 (self._shell, tdir, allreqs),
1387 (self._shell, tdir, allreqs),
1388 self._testtmp, 0, self._getenv())
1388 self._testtmp, 0, self._getenv())
1389 stdout, stderr = proc.communicate()
1389 stdout, stderr = proc.communicate()
1390 ret = proc.wait()
1390 ret = proc.wait()
1391 if wifexited(ret):
1391 if wifexited(ret):
1392 ret = os.WEXITSTATUS(ret)
1392 ret = os.WEXITSTATUS(ret)
1393 if ret == 2:
1393 if ret == 2:
1394 print(stdout.decode('utf-8'))
1394 print(stdout.decode('utf-8'))
1395 sys.exit(1)
1395 sys.exit(1)
1396
1396
1397 if ret != 0:
1397 if ret != 0:
1398 self._have[allreqs] = (False, stdout)
1398 self._have[allreqs] = (False, stdout)
1399 return False, stdout
1399 return False, stdout
1400
1400
1401 self._have[allreqs] = (True, None)
1401 self._have[allreqs] = (True, None)
1402 return True, None
1402 return True, None
1403
1403
1404 def _detectslow(self, reqs):
1404 def _detectslow(self, reqs):
1405 """update the timeout of slow test when appropriate"""
1405 """update the timeout of slow test when appropriate"""
1406 if b'slow' in reqs:
1406 if b'slow' in reqs:
1407 self._timeout = self._slowtimeout
1407 self._timeout = self._slowtimeout
1408
1408
1409 def _iftest(self, args):
1409 def _iftest(self, args):
1410 # implements "#if"
1410 # implements "#if"
1411 reqs = []
1411 reqs = []
1412 for arg in args:
1412 for arg in args:
1413 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1413 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1414 if arg[3:] in self._case:
1414 if arg[3:] in self._case:
1415 return False
1415 return False
1416 elif arg in self._allcases:
1416 elif arg in self._allcases:
1417 if arg not in self._case:
1417 if arg not in self._case:
1418 return False
1418 return False
1419 else:
1419 else:
1420 reqs.append(arg)
1420 reqs.append(arg)
1421 self._detectslow(reqs)
1421 self._detectslow(reqs)
1422 return self._hghave(reqs)[0]
1422 return self._hghave(reqs)[0]
1423
1423
1424 def _parsetest(self, lines):
1424 def _parsetest(self, lines):
1425 # We generate a shell script which outputs unique markers to line
1425 # We generate a shell script which outputs unique markers to line
1426 # up script results with our source. These markers include input
1426 # up script results with our source. These markers include input
1427 # line number and the last return code.
1427 # line number and the last return code.
1428 salt = b"SALT%d" % time.time()
1428 salt = b"SALT%d" % time.time()
1429 def addsalt(line, inpython):
1429 def addsalt(line, inpython):
1430 if inpython:
1430 if inpython:
1431 script.append(b'%s %d 0\n' % (salt, line))
1431 script.append(b'%s %d 0\n' % (salt, line))
1432 else:
1432 else:
1433 script.append(b'echo %s %d $?\n' % (salt, line))
1433 script.append(b'echo %s %d $?\n' % (salt, line))
1434 activetrace = []
1434 activetrace = []
1435 session = str(uuid.uuid4())
1435 session = str(uuid.uuid4())
1436 if PYTHON3:
1436 if PYTHON3:
1437 session = session.encode('ascii')
1437 session = session.encode('ascii')
1438 hgcatapult = (os.getenv('HGTESTCATAPULTSERVERPIPE') or
1438 hgcatapult = (os.getenv('HGTESTCATAPULTSERVERPIPE') or
1439 os.getenv('HGCATAPULTSERVERPIPE'))
1439 os.getenv('HGCATAPULTSERVERPIPE'))
1440 def toggletrace(cmd=None):
1440 def toggletrace(cmd=None):
1441 if not hgcatapult or hgcatapult == os.devnull:
1441 if not hgcatapult or hgcatapult == os.devnull:
1442 return
1442 return
1443
1443
1444 if activetrace:
1444 if activetrace:
1445 script.append(
1445 script.append(
1446 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' % (
1446 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' % (
1447 session, activetrace[0]))
1447 session, activetrace[0]))
1448 if cmd is None:
1448 if cmd is None:
1449 return
1449 return
1450
1450
1451 if isinstance(cmd, str):
1451 if isinstance(cmd, str):
1452 quoted = shellquote(cmd.strip())
1452 quoted = shellquote(cmd.strip())
1453 else:
1453 else:
1454 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1454 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1455 quoted = quoted.replace(b'\\', b'\\\\')
1455 quoted = quoted.replace(b'\\', b'\\\\')
1456 script.append(
1456 script.append(
1457 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' % (
1457 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' % (
1458 session, quoted))
1458 session, quoted))
1459 activetrace[0:] = [quoted]
1459 activetrace[0:] = [quoted]
1460
1460
1461 script = []
1461 script = []
1462
1462
1463 # After we run the shell script, we re-unify the script output
1463 # After we run the shell script, we re-unify the script output
1464 # with non-active parts of the source, with synchronization by our
1464 # with non-active parts of the source, with synchronization by our
1465 # SALT line number markers. The after table contains the non-active
1465 # SALT line number markers. The after table contains the non-active
1466 # components, ordered by line number.
1466 # components, ordered by line number.
1467 after = {}
1467 after = {}
1468
1468
1469 # Expected shell script output.
1469 # Expected shell script output.
1470 expected = {}
1470 expected = {}
1471
1471
1472 pos = prepos = -1
1472 pos = prepos = -1
1473
1473
1474 # True or False when in a true or false conditional section
1474 # True or False when in a true or false conditional section
1475 skipping = None
1475 skipping = None
1476
1476
1477 # We keep track of whether or not we're in a Python block so we
1477 # We keep track of whether or not we're in a Python block so we
1478 # can generate the surrounding doctest magic.
1478 # can generate the surrounding doctest magic.
1479 inpython = False
1479 inpython = False
1480
1480
1481 if self._debug:
1481 if self._debug:
1482 script.append(b'set -x\n')
1482 script.append(b'set -x\n')
1483 if self._hgcommand != b'hg':
1483 if self._hgcommand != b'hg':
1484 script.append(b'alias hg="%s"\n' % self._hgcommand)
1484 script.append(b'alias hg="%s"\n' % self._hgcommand)
1485 if os.getenv('MSYSTEM'):
1485 if os.getenv('MSYSTEM'):
1486 script.append(b'alias pwd="pwd -W"\n')
1486 script.append(b'alias pwd="pwd -W"\n')
1487
1487
1488 if hgcatapult and hgcatapult != os.devnull:
1488 if hgcatapult and hgcatapult != os.devnull:
1489 if PYTHON3:
1489 if PYTHON3:
1490 hgcatapult = hgcatapult.encode('utf8')
1490 hgcatapult = hgcatapult.encode('utf8')
1491 cataname = self.name.encode('utf8')
1491 cataname = self.name.encode('utf8')
1492 else:
1492 else:
1493 cataname = self.name
1493 cataname = self.name
1494
1494
1495 # Kludge: use a while loop to keep the pipe from getting
1495 # Kludge: use a while loop to keep the pipe from getting
1496 # closed by our echo commands. The still-running file gets
1496 # closed by our echo commands. The still-running file gets
1497 # reaped at the end of the script, which causes the while
1497 # reaped at the end of the script, which causes the while
1498 # loop to exit and closes the pipe. Sigh.
1498 # loop to exit and closes the pipe. Sigh.
1499 script.append(
1499 script.append(
1500 b'rtendtracing() {\n'
1500 b'rtendtracing() {\n'
1501 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1501 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1502 b' rm -f "$TESTTMP/.still-running"\n'
1502 b' rm -f "$TESTTMP/.still-running"\n'
1503 b'}\n'
1503 b'}\n'
1504 b'trap "rtendtracing" 0\n'
1504 b'trap "rtendtracing" 0\n'
1505 b'touch "$TESTTMP/.still-running"\n'
1505 b'touch "$TESTTMP/.still-running"\n'
1506 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1506 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1507 b'> %(catapult)s &\n'
1507 b'> %(catapult)s &\n'
1508 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1508 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1509 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1509 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1510 % {
1510 % {
1511 b'name': cataname,
1511 b'name': cataname,
1512 b'session': session,
1512 b'session': session,
1513 b'catapult': hgcatapult,
1513 b'catapult': hgcatapult,
1514 }
1514 }
1515 )
1515 )
1516
1516
1517 if self._case:
1517 if self._case:
1518 casestr = b'#'.join(self._case)
1518 casestr = b'#'.join(self._case)
1519 if isinstance(self._case, str):
1519 if isinstance(self._case, str):
1520 quoted = shellquote(casestr)
1520 quoted = shellquote(casestr)
1521 else:
1521 else:
1522 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1522 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1523 script.append(b'TESTCASE=%s\n' % quoted)
1523 script.append(b'TESTCASE=%s\n' % quoted)
1524 script.append(b'export TESTCASE\n')
1524 script.append(b'export TESTCASE\n')
1525
1525
1526 n = 0
1526 n = 0
1527 for n, l in enumerate(lines):
1527 for n, l in enumerate(lines):
1528 if not l.endswith(b'\n'):
1528 if not l.endswith(b'\n'):
1529 l += b'\n'
1529 l += b'\n'
1530 if l.startswith(b'#require'):
1530 if l.startswith(b'#require'):
1531 lsplit = l.split()
1531 lsplit = l.split()
1532 if len(lsplit) < 2 or lsplit[0] != b'#require':
1532 if len(lsplit) < 2 or lsplit[0] != b'#require':
1533 after.setdefault(pos, []).append(' !!! invalid #require\n')
1533 after.setdefault(pos, []).append(' !!! invalid #require\n')
1534 if not skipping:
1534 if not skipping:
1535 haveresult, message = self._hghave(lsplit[1:])
1535 haveresult, message = self._hghave(lsplit[1:])
1536 if not haveresult:
1536 if not haveresult:
1537 script = [b'echo "%s"\nexit 80\n' % message]
1537 script = [b'echo "%s"\nexit 80\n' % message]
1538 break
1538 break
1539 after.setdefault(pos, []).append(l)
1539 after.setdefault(pos, []).append(l)
1540 elif l.startswith(b'#if'):
1540 elif l.startswith(b'#if'):
1541 lsplit = l.split()
1541 lsplit = l.split()
1542 if len(lsplit) < 2 or lsplit[0] != b'#if':
1542 if len(lsplit) < 2 or lsplit[0] != b'#if':
1543 after.setdefault(pos, []).append(' !!! invalid #if\n')
1543 after.setdefault(pos, []).append(' !!! invalid #if\n')
1544 if skipping is not None:
1544 if skipping is not None:
1545 after.setdefault(pos, []).append(' !!! nested #if\n')
1545 after.setdefault(pos, []).append(' !!! nested #if\n')
1546 skipping = not self._iftest(lsplit[1:])
1546 skipping = not self._iftest(lsplit[1:])
1547 after.setdefault(pos, []).append(l)
1547 after.setdefault(pos, []).append(l)
1548 elif l.startswith(b'#else'):
1548 elif l.startswith(b'#else'):
1549 if skipping is None:
1549 if skipping is None:
1550 after.setdefault(pos, []).append(' !!! missing #if\n')
1550 after.setdefault(pos, []).append(' !!! missing #if\n')
1551 skipping = not skipping
1551 skipping = not skipping
1552 after.setdefault(pos, []).append(l)
1552 after.setdefault(pos, []).append(l)
1553 elif l.startswith(b'#endif'):
1553 elif l.startswith(b'#endif'):
1554 if skipping is None:
1554 if skipping is None:
1555 after.setdefault(pos, []).append(' !!! missing #if\n')
1555 after.setdefault(pos, []).append(' !!! missing #if\n')
1556 skipping = None
1556 skipping = None
1557 after.setdefault(pos, []).append(l)
1557 after.setdefault(pos, []).append(l)
1558 elif skipping:
1558 elif skipping:
1559 after.setdefault(pos, []).append(l)
1559 after.setdefault(pos, []).append(l)
1560 elif l.startswith(b' >>> '): # python inlines
1560 elif l.startswith(b' >>> '): # python inlines
1561 after.setdefault(pos, []).append(l)
1561 after.setdefault(pos, []).append(l)
1562 prepos = pos
1562 prepos = pos
1563 pos = n
1563 pos = n
1564 if not inpython:
1564 if not inpython:
1565 # We've just entered a Python block. Add the header.
1565 # We've just entered a Python block. Add the header.
1566 inpython = True
1566 inpython = True
1567 addsalt(prepos, False) # Make sure we report the exit code.
1567 addsalt(prepos, False) # Make sure we report the exit code.
1568 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1568 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1569 addsalt(n, True)
1569 addsalt(n, True)
1570 script.append(l[2:])
1570 script.append(l[2:])
1571 elif l.startswith(b' ... '): # python inlines
1571 elif l.startswith(b' ... '): # python inlines
1572 after.setdefault(prepos, []).append(l)
1572 after.setdefault(prepos, []).append(l)
1573 script.append(l[2:])
1573 script.append(l[2:])
1574 elif l.startswith(b' $ '): # commands
1574 elif l.startswith(b' $ '): # commands
1575 if inpython:
1575 if inpython:
1576 script.append(b'EOF\n')
1576 script.append(b'EOF\n')
1577 inpython = False
1577 inpython = False
1578 after.setdefault(pos, []).append(l)
1578 after.setdefault(pos, []).append(l)
1579 prepos = pos
1579 prepos = pos
1580 pos = n
1580 pos = n
1581 addsalt(n, False)
1581 addsalt(n, False)
1582 rawcmd = l[4:]
1582 rawcmd = l[4:]
1583 cmd = rawcmd.split()
1583 cmd = rawcmd.split()
1584 toggletrace(rawcmd)
1584 toggletrace(rawcmd)
1585 if len(cmd) == 2 and cmd[0] == b'cd':
1585 if len(cmd) == 2 and cmd[0] == b'cd':
1586 l = b' $ cd %s || exit 1\n' % cmd[1]
1586 l = b' $ cd %s || exit 1\n' % cmd[1]
1587 script.append(rawcmd)
1587 script.append(rawcmd)
1588 elif l.startswith(b' > '): # continuations
1588 elif l.startswith(b' > '): # continuations
1589 after.setdefault(prepos, []).append(l)
1589 after.setdefault(prepos, []).append(l)
1590 script.append(l[4:])
1590 script.append(l[4:])
1591 elif l.startswith(b' '): # results
1591 elif l.startswith(b' '): # results
1592 # Queue up a list of expected results.
1592 # Queue up a list of expected results.
1593 expected.setdefault(pos, []).append(l[2:])
1593 expected.setdefault(pos, []).append(l[2:])
1594 else:
1594 else:
1595 if inpython:
1595 if inpython:
1596 script.append(b'EOF\n')
1596 script.append(b'EOF\n')
1597 inpython = False
1597 inpython = False
1598 # Non-command/result. Queue up for merged output.
1598 # Non-command/result. Queue up for merged output.
1599 after.setdefault(pos, []).append(l)
1599 after.setdefault(pos, []).append(l)
1600
1600
1601 if inpython:
1601 if inpython:
1602 script.append(b'EOF\n')
1602 script.append(b'EOF\n')
1603 if skipping is not None:
1603 if skipping is not None:
1604 after.setdefault(pos, []).append(' !!! missing #endif\n')
1604 after.setdefault(pos, []).append(' !!! missing #endif\n')
1605 addsalt(n + 1, False)
1605 addsalt(n + 1, False)
1606 # Need to end any current per-command trace
1606 # Need to end any current per-command trace
1607 if activetrace:
1607 if activetrace:
1608 toggletrace()
1608 toggletrace()
1609 return salt, script, after, expected
1609 return salt, script, after, expected
1610
1610
1611 def _processoutput(self, exitcode, output, salt, after, expected):
1611 def _processoutput(self, exitcode, output, salt, after, expected):
1612 # Merge the script output back into a unified test.
1612 # Merge the script output back into a unified test.
1613 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
1613 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
1614 if exitcode != 0:
1614 if exitcode != 0:
1615 warnonly = WARN_NO
1615 warnonly = WARN_NO
1616
1616
1617 pos = -1
1617 pos = -1
1618 postout = []
1618 postout = []
1619 for out_rawline in output:
1619 for out_rawline in output:
1620 out_line, cmd_line = out_rawline, None
1620 out_line, cmd_line = out_rawline, None
1621 if salt in out_rawline:
1621 if salt in out_rawline:
1622 out_line, cmd_line = out_rawline.split(salt, 1)
1622 out_line, cmd_line = out_rawline.split(salt, 1)
1623
1623
1624 pos, postout, warnonly = self._process_out_line(out_line,
1624 pos, postout, warnonly = self._process_out_line(out_line,
1625 pos,
1625 pos,
1626 postout,
1626 postout,
1627 expected,
1627 expected,
1628 warnonly)
1628 warnonly)
1629 pos, postout = self._process_cmd_line(cmd_line, pos, postout,
1629 pos, postout = self._process_cmd_line(cmd_line, pos, postout,
1630 after)
1630 after)
1631
1631
1632 if pos in after:
1632 if pos in after:
1633 postout += after.pop(pos)
1633 postout += after.pop(pos)
1634
1634
1635 if warnonly == WARN_YES:
1635 if warnonly == WARN_YES:
1636 exitcode = False # Set exitcode to warned.
1636 exitcode = False # Set exitcode to warned.
1637
1637
1638 return exitcode, postout
1638 return exitcode, postout
1639
1639
1640 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
1640 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
1641 while out_line:
1641 while out_line:
1642 if not out_line.endswith(b'\n'):
1642 if not out_line.endswith(b'\n'):
1643 out_line += b' (no-eol)\n'
1643 out_line += b' (no-eol)\n'
1644
1644
1645 # Find the expected output at the current position.
1645 # Find the expected output at the current position.
1646 els = [None]
1646 els = [None]
1647 if expected.get(pos, None):
1647 if expected.get(pos, None):
1648 els = expected[pos]
1648 els = expected[pos]
1649
1649
1650 optional = []
1650 optional = []
1651 for i, el in enumerate(els):
1651 for i, el in enumerate(els):
1652 r = False
1652 r = False
1653 if el:
1653 if el:
1654 r, exact = self.linematch(el, out_line)
1654 r, exact = self.linematch(el, out_line)
1655 if isinstance(r, str):
1655 if isinstance(r, str):
1656 if r == '-glob':
1656 if r == '-glob':
1657 out_line = ''.join(el.rsplit(' (glob)', 1))
1657 out_line = ''.join(el.rsplit(' (glob)', 1))
1658 r = '' # Warn only this line.
1658 r = '' # Warn only this line.
1659 elif r == "retry":
1659 elif r == "retry":
1660 postout.append(b' ' + el)
1660 postout.append(b' ' + el)
1661 else:
1661 else:
1662 log('\ninfo, unknown linematch result: %r\n' % r)
1662 log('\ninfo, unknown linematch result: %r\n' % r)
1663 r = False
1663 r = False
1664 if r:
1664 if r:
1665 els.pop(i)
1665 els.pop(i)
1666 break
1666 break
1667 if el:
1667 if el:
1668 if isoptional(el):
1668 if isoptional(el):
1669 optional.append(i)
1669 optional.append(i)
1670 else:
1670 else:
1671 m = optline.match(el)
1671 m = optline.match(el)
1672 if m:
1672 if m:
1673 conditions = [
1673 conditions = [
1674 c for c in m.group(2).split(b' ')]
1674 c for c in m.group(2).split(b' ')]
1675
1675
1676 if not self._iftest(conditions):
1676 if not self._iftest(conditions):
1677 optional.append(i)
1677 optional.append(i)
1678 if exact:
1678 if exact:
1679 # Don't allow line to be matches against a later
1679 # Don't allow line to be matches against a later
1680 # line in the output
1680 # line in the output
1681 els.pop(i)
1681 els.pop(i)
1682 break
1682 break
1683
1683
1684 if r:
1684 if r:
1685 if r == "retry":
1685 if r == "retry":
1686 continue
1686 continue
1687 # clean up any optional leftovers
1687 # clean up any optional leftovers
1688 for i in optional:
1688 for i in optional:
1689 postout.append(b' ' + els[i])
1689 postout.append(b' ' + els[i])
1690 for i in reversed(optional):
1690 for i in reversed(optional):
1691 del els[i]
1691 del els[i]
1692 postout.append(b' ' + el)
1692 postout.append(b' ' + el)
1693 else:
1693 else:
1694 if self.NEEDESCAPE(out_line):
1694 if self.NEEDESCAPE(out_line):
1695 out_line = TTest._stringescape(b'%s (esc)\n' %
1695 out_line = TTest._stringescape(b'%s (esc)\n' %
1696 out_line.rstrip(b'\n'))
1696 out_line.rstrip(b'\n'))
1697 postout.append(b' ' + out_line) # Let diff deal with it.
1697 postout.append(b' ' + out_line) # Let diff deal with it.
1698 if r != '': # If line failed.
1698 if r != '': # If line failed.
1699 warnonly = WARN_NO
1699 warnonly = WARN_NO
1700 elif warnonly == WARN_UNDEFINED:
1700 elif warnonly == WARN_UNDEFINED:
1701 warnonly = WARN_YES
1701 warnonly = WARN_YES
1702 break
1702 break
1703 else:
1703 else:
1704 # clean up any optional leftovers
1704 # clean up any optional leftovers
1705 while expected.get(pos, None):
1705 while expected.get(pos, None):
1706 el = expected[pos].pop(0)
1706 el = expected[pos].pop(0)
1707 if el:
1707 if el:
1708 if not isoptional(el):
1708 if not isoptional(el):
1709 m = optline.match(el)
1709 m = optline.match(el)
1710 if m:
1710 if m:
1711 conditions = [c for c in m.group(2).split(b' ')]
1711 conditions = [c for c in m.group(2).split(b' ')]
1712
1712
1713 if self._iftest(conditions):
1713 if self._iftest(conditions):
1714 # Don't append as optional line
1714 # Don't append as optional line
1715 continue
1715 continue
1716 else:
1716 else:
1717 continue
1717 continue
1718 postout.append(b' ' + el)
1718 postout.append(b' ' + el)
1719 return pos, postout, warnonly
1719 return pos, postout, warnonly
1720
1720
1721 def _process_cmd_line(self, cmd_line, pos, postout, after):
1721 def _process_cmd_line(self, cmd_line, pos, postout, after):
1722 """process a "command" part of a line from unified test output"""
1722 """process a "command" part of a line from unified test output"""
1723 if cmd_line:
1723 if cmd_line:
1724 # Add on last return code.
1724 # Add on last return code.
1725 ret = int(cmd_line.split()[1])
1725 ret = int(cmd_line.split()[1])
1726 if ret != 0:
1726 if ret != 0:
1727 postout.append(b' [%d]\n' % ret)
1727 postout.append(b' [%d]\n' % ret)
1728 if pos in after:
1728 if pos in after:
1729 # Merge in non-active test bits.
1729 # Merge in non-active test bits.
1730 postout += after.pop(pos)
1730 postout += after.pop(pos)
1731 pos = int(cmd_line.split()[0])
1731 pos = int(cmd_line.split()[0])
1732 return pos, postout
1732 return pos, postout
1733
1733
1734 @staticmethod
1734 @staticmethod
1735 def rematch(el, l):
1735 def rematch(el, l):
1736 try:
1736 try:
1737 el = b'(?:' + el + b')'
1737 el = b'(?:' + el + b')'
1738 # use \Z to ensure that the regex matches to the end of the string
1738 # use \Z to ensure that the regex matches to the end of the string
1739 if os.name == 'nt':
1739 if os.name == 'nt':
1740 return re.match(el + br'\r?\n\Z', l)
1740 return re.match(el + br'\r?\n\Z', l)
1741 return re.match(el + br'\n\Z', l)
1741 return re.match(el + br'\n\Z', l)
1742 except re.error:
1742 except re.error:
1743 # el is an invalid regex
1743 # el is an invalid regex
1744 return False
1744 return False
1745
1745
1746 @staticmethod
1746 @staticmethod
1747 def globmatch(el, l):
1747 def globmatch(el, l):
1748 # The only supported special characters are * and ? plus / which also
1748 # The only supported special characters are * and ? plus / which also
1749 # matches \ on windows. Escaping of these characters is supported.
1749 # matches \ on windows. Escaping of these characters is supported.
1750 if el + b'\n' == l:
1750 if el + b'\n' == l:
1751 if os.altsep:
1751 if os.altsep:
1752 # matching on "/" is not needed for this line
1752 # matching on "/" is not needed for this line
1753 for pat in checkcodeglobpats:
1753 for pat in checkcodeglobpats:
1754 if pat.match(el):
1754 if pat.match(el):
1755 return True
1755 return True
1756 return b'-glob'
1756 return b'-glob'
1757 return True
1757 return True
1758 el = el.replace(b'$LOCALIP', b'*')
1758 el = el.replace(b'$LOCALIP', b'*')
1759 i, n = 0, len(el)
1759 i, n = 0, len(el)
1760 res = b''
1760 res = b''
1761 while i < n:
1761 while i < n:
1762 c = el[i:i + 1]
1762 c = el[i:i + 1]
1763 i += 1
1763 i += 1
1764 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1764 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1765 res += el[i - 1:i + 1]
1765 res += el[i - 1:i + 1]
1766 i += 1
1766 i += 1
1767 elif c == b'*':
1767 elif c == b'*':
1768 res += b'.*'
1768 res += b'.*'
1769 elif c == b'?':
1769 elif c == b'?':
1770 res += b'.'
1770 res += b'.'
1771 elif c == b'/' and os.altsep:
1771 elif c == b'/' and os.altsep:
1772 res += b'[/\\\\]'
1772 res += b'[/\\\\]'
1773 else:
1773 else:
1774 res += re.escape(c)
1774 res += re.escape(c)
1775 return TTest.rematch(res, l)
1775 return TTest.rematch(res, l)
1776
1776
1777 def linematch(self, el, l):
1777 def linematch(self, el, l):
1778 if el == l: # perfect match (fast)
1778 if el == l: # perfect match (fast)
1779 return True, True
1779 return True, True
1780 retry = False
1780 retry = False
1781 if isoptional(el):
1781 if isoptional(el):
1782 retry = "retry"
1782 retry = "retry"
1783 el = el[:-len(MARK_OPTIONAL)] + b"\n"
1783 el = el[:-len(MARK_OPTIONAL)] + b"\n"
1784 else:
1784 else:
1785 m = optline.match(el)
1785 m = optline.match(el)
1786 if m:
1786 if m:
1787 conditions = [c for c in m.group(2).split(b' ')]
1787 conditions = [c for c in m.group(2).split(b' ')]
1788
1788
1789 el = m.group(1) + b"\n"
1789 el = m.group(1) + b"\n"
1790 if not self._iftest(conditions):
1790 if not self._iftest(conditions):
1791 # listed feature missing, should not match
1791 # listed feature missing, should not match
1792 return "retry", False
1792 return "retry", False
1793
1793
1794 if el.endswith(b" (esc)\n"):
1794 if el.endswith(b" (esc)\n"):
1795 if PYTHON3:
1795 if PYTHON3:
1796 el = el[:-7].decode('unicode_escape') + '\n'
1796 el = el[:-7].decode('unicode_escape') + '\n'
1797 el = el.encode('utf-8')
1797 el = el.encode('utf-8')
1798 else:
1798 else:
1799 el = el[:-7].decode('string-escape') + '\n'
1799 el = el[:-7].decode('string-escape') + '\n'
1800 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1800 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1801 return True, True
1801 return True, True
1802 if el.endswith(b" (re)\n"):
1802 if el.endswith(b" (re)\n"):
1803 return (TTest.rematch(el[:-6], l) or retry), False
1803 return (TTest.rematch(el[:-6], l) or retry), False
1804 if el.endswith(b" (glob)\n"):
1804 if el.endswith(b" (glob)\n"):
1805 # ignore '(glob)' added to l by 'replacements'
1805 # ignore '(glob)' added to l by 'replacements'
1806 if l.endswith(b" (glob)\n"):
1806 if l.endswith(b" (glob)\n"):
1807 l = l[:-8] + b"\n"
1807 l = l[:-8] + b"\n"
1808 return (TTest.globmatch(el[:-8], l) or retry), False
1808 return (TTest.globmatch(el[:-8], l) or retry), False
1809 if os.altsep:
1809 if os.altsep:
1810 _l = l.replace(b'\\', b'/')
1810 _l = l.replace(b'\\', b'/')
1811 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
1811 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
1812 return True, True
1812 return True, True
1813 return retry, True
1813 return retry, True
1814
1814
1815 @staticmethod
1815 @staticmethod
1816 def parsehghaveoutput(lines):
1816 def parsehghaveoutput(lines):
1817 '''Parse hghave log lines.
1817 '''Parse hghave log lines.
1818
1818
1819 Return tuple of lists (missing, failed):
1819 Return tuple of lists (missing, failed):
1820 * the missing/unknown features
1820 * the missing/unknown features
1821 * the features for which existence check failed'''
1821 * the features for which existence check failed'''
1822 missing = []
1822 missing = []
1823 failed = []
1823 failed = []
1824 for line in lines:
1824 for line in lines:
1825 if line.startswith(TTest.SKIPPED_PREFIX):
1825 if line.startswith(TTest.SKIPPED_PREFIX):
1826 line = line.splitlines()[0]
1826 line = line.splitlines()[0]
1827 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
1827 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
1828 elif line.startswith(TTest.FAILED_PREFIX):
1828 elif line.startswith(TTest.FAILED_PREFIX):
1829 line = line.splitlines()[0]
1829 line = line.splitlines()[0]
1830 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
1830 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
1831
1831
1832 return missing, failed
1832 return missing, failed
1833
1833
1834 @staticmethod
1834 @staticmethod
1835 def _escapef(m):
1835 def _escapef(m):
1836 return TTest.ESCAPEMAP[m.group(0)]
1836 return TTest.ESCAPEMAP[m.group(0)]
1837
1837
1838 @staticmethod
1838 @staticmethod
1839 def _stringescape(s):
1839 def _stringescape(s):
1840 return TTest.ESCAPESUB(TTest._escapef, s)
1840 return TTest.ESCAPESUB(TTest._escapef, s)
1841
1841
1842 iolock = threading.RLock()
1842 iolock = threading.RLock()
1843 firstlock = threading.RLock()
1843 firstlock = threading.RLock()
1844 firsterror = False
1844 firsterror = False
1845
1845
1846 class TestResult(unittest._TextTestResult):
1846 class TestResult(unittest._TextTestResult):
1847 """Holds results when executing via unittest."""
1847 """Holds results when executing via unittest."""
1848 # Don't worry too much about accessing the non-public _TextTestResult.
1848 # Don't worry too much about accessing the non-public _TextTestResult.
1849 # It is relatively common in Python testing tools.
1849 # It is relatively common in Python testing tools.
1850 def __init__(self, options, *args, **kwargs):
1850 def __init__(self, options, *args, **kwargs):
1851 super(TestResult, self).__init__(*args, **kwargs)
1851 super(TestResult, self).__init__(*args, **kwargs)
1852
1852
1853 self._options = options
1853 self._options = options
1854
1854
1855 # unittest.TestResult didn't have skipped until 2.7. We need to
1855 # unittest.TestResult didn't have skipped until 2.7. We need to
1856 # polyfill it.
1856 # polyfill it.
1857 self.skipped = []
1857 self.skipped = []
1858
1858
1859 # We have a custom "ignored" result that isn't present in any Python
1859 # We have a custom "ignored" result that isn't present in any Python
1860 # unittest implementation. It is very similar to skipped. It may make
1860 # unittest implementation. It is very similar to skipped. It may make
1861 # sense to map it into skip some day.
1861 # sense to map it into skip some day.
1862 self.ignored = []
1862 self.ignored = []
1863
1863
1864 self.times = []
1864 self.times = []
1865 self._firststarttime = None
1865 self._firststarttime = None
1866 # Data stored for the benefit of generating xunit reports.
1866 # Data stored for the benefit of generating xunit reports.
1867 self.successes = []
1867 self.successes = []
1868 self.faildata = {}
1868 self.faildata = {}
1869
1869
1870 if options.color == 'auto':
1870 if options.color == 'auto':
1871 self.color = pygmentspresent and self.stream.isatty()
1871 self.color = pygmentspresent and self.stream.isatty()
1872 elif options.color == 'never':
1872 elif options.color == 'never':
1873 self.color = False
1873 self.color = False
1874 else: # 'always', for testing purposes
1874 else: # 'always', for testing purposes
1875 self.color = pygmentspresent
1875 self.color = pygmentspresent
1876
1876
1877 def onStart(self, test):
1877 def onStart(self, test):
1878 """ Can be overriden by custom TestResult
1878 """ Can be overriden by custom TestResult
1879 """
1879 """
1880
1880
1881 def onEnd(self):
1881 def onEnd(self):
1882 """ Can be overriden by custom TestResult
1882 """ Can be overriden by custom TestResult
1883 """
1883 """
1884
1884
1885 def addFailure(self, test, reason):
1885 def addFailure(self, test, reason):
1886 self.failures.append((test, reason))
1886 self.failures.append((test, reason))
1887
1887
1888 if self._options.first:
1888 if self._options.first:
1889 self.stop()
1889 self.stop()
1890 else:
1890 else:
1891 with iolock:
1891 with iolock:
1892 if reason == "timed out":
1892 if reason == "timed out":
1893 self.stream.write('t')
1893 self.stream.write('t')
1894 else:
1894 else:
1895 if not self._options.nodiff:
1895 if not self._options.nodiff:
1896 self.stream.write('\n')
1896 self.stream.write('\n')
1897 # Exclude the '\n' from highlighting to lex correctly
1897 # Exclude the '\n' from highlighting to lex correctly
1898 formatted = 'ERROR: %s output changed\n' % test
1898 formatted = 'ERROR: %s output changed\n' % test
1899 self.stream.write(highlightmsg(formatted, self.color))
1899 self.stream.write(highlightmsg(formatted, self.color))
1900 self.stream.write('!')
1900 self.stream.write('!')
1901
1901
1902 self.stream.flush()
1902 self.stream.flush()
1903
1903
1904 def addSuccess(self, test):
1904 def addSuccess(self, test):
1905 with iolock:
1905 with iolock:
1906 super(TestResult, self).addSuccess(test)
1906 super(TestResult, self).addSuccess(test)
1907 self.successes.append(test)
1907 self.successes.append(test)
1908
1908
1909 def addError(self, test, err):
1909 def addError(self, test, err):
1910 super(TestResult, self).addError(test, err)
1910 super(TestResult, self).addError(test, err)
1911 if self._options.first:
1911 if self._options.first:
1912 self.stop()
1912 self.stop()
1913
1913
1914 # Polyfill.
1914 # Polyfill.
1915 def addSkip(self, test, reason):
1915 def addSkip(self, test, reason):
1916 self.skipped.append((test, reason))
1916 self.skipped.append((test, reason))
1917 with iolock:
1917 with iolock:
1918 if self.showAll:
1918 if self.showAll:
1919 self.stream.writeln('skipped %s' % reason)
1919 self.stream.writeln('skipped %s' % reason)
1920 else:
1920 else:
1921 self.stream.write('s')
1921 self.stream.write('s')
1922 self.stream.flush()
1922 self.stream.flush()
1923
1923
1924 def addIgnore(self, test, reason):
1924 def addIgnore(self, test, reason):
1925 self.ignored.append((test, reason))
1925 self.ignored.append((test, reason))
1926 with iolock:
1926 with iolock:
1927 if self.showAll:
1927 if self.showAll:
1928 self.stream.writeln('ignored %s' % reason)
1928 self.stream.writeln('ignored %s' % reason)
1929 else:
1929 else:
1930 if reason not in ('not retesting', "doesn't match keyword"):
1930 if reason not in ('not retesting', "doesn't match keyword"):
1931 self.stream.write('i')
1931 self.stream.write('i')
1932 else:
1932 else:
1933 self.testsRun += 1
1933 self.testsRun += 1
1934 self.stream.flush()
1934 self.stream.flush()
1935
1935
1936 def addOutputMismatch(self, test, ret, got, expected):
1936 def addOutputMismatch(self, test, ret, got, expected):
1937 """Record a mismatch in test output for a particular test."""
1937 """Record a mismatch in test output for a particular test."""
1938 if self.shouldStop or firsterror:
1938 if self.shouldStop or firsterror:
1939 # don't print, some other test case already failed and
1939 # don't print, some other test case already failed and
1940 # printed, we're just stale and probably failed due to our
1940 # printed, we're just stale and probably failed due to our
1941 # temp dir getting cleaned up.
1941 # temp dir getting cleaned up.
1942 return
1942 return
1943
1943
1944 accepted = False
1944 accepted = False
1945 lines = []
1945 lines = []
1946
1946
1947 with iolock:
1947 with iolock:
1948 if self._options.nodiff:
1948 if self._options.nodiff:
1949 pass
1949 pass
1950 elif self._options.view:
1950 elif self._options.view:
1951 v = self._options.view
1951 v = self._options.view
1952 subprocess.call(r'"%s" "%s" "%s"' %
1952 subprocess.call(r'"%s" "%s" "%s"' %
1953 (v, _strpath(test.refpath),
1953 (v, _strpath(test.refpath),
1954 _strpath(test.errpath)), shell=True)
1954 _strpath(test.errpath)), shell=True)
1955 else:
1955 else:
1956 servefail, lines = getdiff(expected, got,
1956 servefail, lines = getdiff(expected, got,
1957 test.refpath, test.errpath)
1957 test.refpath, test.errpath)
1958 self.stream.write('\n')
1958 self.stream.write('\n')
1959 for line in lines:
1959 for line in lines:
1960 line = highlightdiff(line, self.color)
1960 line = highlightdiff(line, self.color)
1961 if PYTHON3:
1961 if PYTHON3:
1962 self.stream.flush()
1962 self.stream.flush()
1963 self.stream.buffer.write(line)
1963 self.stream.buffer.write(line)
1964 self.stream.buffer.flush()
1964 self.stream.buffer.flush()
1965 else:
1965 else:
1966 self.stream.write(line)
1966 self.stream.write(line)
1967 self.stream.flush()
1967 self.stream.flush()
1968
1968
1969 if servefail:
1969 if servefail:
1970 raise test.failureException(
1970 raise test.failureException(
1971 'server failed to start (HGPORT=%s)' % test._startport)
1971 'server failed to start (HGPORT=%s)' % test._startport)
1972
1972
1973 # handle interactive prompt without releasing iolock
1973 # handle interactive prompt without releasing iolock
1974 if self._options.interactive:
1974 if self._options.interactive:
1975 if test.readrefout() != expected:
1975 if test.readrefout() != expected:
1976 self.stream.write(
1976 self.stream.write(
1977 'Reference output has changed (run again to prompt '
1977 'Reference output has changed (run again to prompt '
1978 'changes)')
1978 'changes)')
1979 else:
1979 else:
1980 self.stream.write('Accept this change? [n] ')
1980 self.stream.write('Accept this change? [n] ')
1981 self.stream.flush()
1981 self.stream.flush()
1982 answer = sys.stdin.readline().strip()
1982 answer = sys.stdin.readline().strip()
1983 if answer.lower() in ('y', 'yes'):
1983 if answer.lower() in ('y', 'yes'):
1984 if test.path.endswith(b'.t'):
1984 if test.path.endswith(b'.t'):
1985 rename(test.errpath, test.path)
1985 rename(test.errpath, test.path)
1986 else:
1986 else:
1987 rename(test.errpath, '%s.out' % test.path)
1987 rename(test.errpath, '%s.out' % test.path)
1988 accepted = True
1988 accepted = True
1989 if not accepted:
1989 if not accepted:
1990 self.faildata[test.name] = b''.join(lines)
1990 self.faildata[test.name] = b''.join(lines)
1991
1991
1992 return accepted
1992 return accepted
1993
1993
1994 def startTest(self, test):
1994 def startTest(self, test):
1995 super(TestResult, self).startTest(test)
1995 super(TestResult, self).startTest(test)
1996
1996
1997 # os.times module computes the user time and system time spent by
1997 # os.times module computes the user time and system time spent by
1998 # child's processes along with real elapsed time taken by a process.
1998 # child's processes along with real elapsed time taken by a process.
1999 # This module has one limitation. It can only work for Linux user
1999 # This module has one limitation. It can only work for Linux user
2000 # and not for Windows.
2000 # and not for Windows.
2001 test.started = os.times()
2001 test.started = os.times()
2002 if self._firststarttime is None: # thread racy but irrelevant
2002 if self._firststarttime is None: # thread racy but irrelevant
2003 self._firststarttime = test.started[4]
2003 self._firststarttime = test.started[4]
2004
2004
2005 def stopTest(self, test, interrupted=False):
2005 def stopTest(self, test, interrupted=False):
2006 super(TestResult, self).stopTest(test)
2006 super(TestResult, self).stopTest(test)
2007
2007
2008 test.stopped = os.times()
2008 test.stopped = os.times()
2009
2009
2010 starttime = test.started
2010 starttime = test.started
2011 endtime = test.stopped
2011 endtime = test.stopped
2012 origin = self._firststarttime
2012 origin = self._firststarttime
2013 self.times.append((test.name,
2013 self.times.append((test.name,
2014 endtime[2] - starttime[2], # user space CPU time
2014 endtime[2] - starttime[2], # user space CPU time
2015 endtime[3] - starttime[3], # sys space CPU time
2015 endtime[3] - starttime[3], # sys space CPU time
2016 endtime[4] - starttime[4], # real time
2016 endtime[4] - starttime[4], # real time
2017 starttime[4] - origin, # start date in run context
2017 starttime[4] - origin, # start date in run context
2018 endtime[4] - origin, # end date in run context
2018 endtime[4] - origin, # end date in run context
2019 ))
2019 ))
2020
2020
2021 if interrupted:
2021 if interrupted:
2022 with iolock:
2022 with iolock:
2023 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
2023 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
2024 test.name, self.times[-1][3]))
2024 test.name, self.times[-1][3]))
2025
2025
2026 def getTestResult():
2026 def getTestResult():
2027 """
2027 """
2028 Returns the relevant test result
2028 Returns the relevant test result
2029 """
2029 """
2030 if "CUSTOM_TEST_RESULT" in os.environ:
2030 if "CUSTOM_TEST_RESULT" in os.environ:
2031 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2031 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2032 return testresultmodule.TestResult
2032 return testresultmodule.TestResult
2033 else:
2033 else:
2034 return TestResult
2034 return TestResult
2035
2035
2036 class TestSuite(unittest.TestSuite):
2036 class TestSuite(unittest.TestSuite):
2037 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2037 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2038
2038
2039 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
2039 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
2040 retest=False, keywords=None, loop=False, runs_per_test=1,
2040 retest=False, keywords=None, loop=False, runs_per_test=1,
2041 loadtest=None, showchannels=False,
2041 loadtest=None, showchannels=False,
2042 *args, **kwargs):
2042 *args, **kwargs):
2043 """Create a new instance that can run tests with a configuration.
2043 """Create a new instance that can run tests with a configuration.
2044
2044
2045 testdir specifies the directory where tests are executed from. This
2045 testdir specifies the directory where tests are executed from. This
2046 is typically the ``tests`` directory from Mercurial's source
2046 is typically the ``tests`` directory from Mercurial's source
2047 repository.
2047 repository.
2048
2048
2049 jobs specifies the number of jobs to run concurrently. Each test
2049 jobs specifies the number of jobs to run concurrently. Each test
2050 executes on its own thread. Tests actually spawn new processes, so
2050 executes on its own thread. Tests actually spawn new processes, so
2051 state mutation should not be an issue.
2051 state mutation should not be an issue.
2052
2052
2053 If there is only one job, it will use the main thread.
2053 If there is only one job, it will use the main thread.
2054
2054
2055 whitelist and blacklist denote tests that have been whitelisted and
2055 whitelist and blacklist denote tests that have been whitelisted and
2056 blacklisted, respectively. These arguments don't belong in TestSuite.
2056 blacklisted, respectively. These arguments don't belong in TestSuite.
2057 Instead, whitelist and blacklist should be handled by the thing that
2057 Instead, whitelist and blacklist should be handled by the thing that
2058 populates the TestSuite with tests. They are present to preserve
2058 populates the TestSuite with tests. They are present to preserve
2059 backwards compatible behavior which reports skipped tests as part
2059 backwards compatible behavior which reports skipped tests as part
2060 of the results.
2060 of the results.
2061
2061
2062 retest denotes whether to retest failed tests. This arguably belongs
2062 retest denotes whether to retest failed tests. This arguably belongs
2063 outside of TestSuite.
2063 outside of TestSuite.
2064
2064
2065 keywords denotes key words that will be used to filter which tests
2065 keywords denotes key words that will be used to filter which tests
2066 to execute. This arguably belongs outside of TestSuite.
2066 to execute. This arguably belongs outside of TestSuite.
2067
2067
2068 loop denotes whether to loop over tests forever.
2068 loop denotes whether to loop over tests forever.
2069 """
2069 """
2070 super(TestSuite, self).__init__(*args, **kwargs)
2070 super(TestSuite, self).__init__(*args, **kwargs)
2071
2071
2072 self._jobs = jobs
2072 self._jobs = jobs
2073 self._whitelist = whitelist
2073 self._whitelist = whitelist
2074 self._blacklist = blacklist
2074 self._blacklist = blacklist
2075 self._retest = retest
2075 self._retest = retest
2076 self._keywords = keywords
2076 self._keywords = keywords
2077 self._loop = loop
2077 self._loop = loop
2078 self._runs_per_test = runs_per_test
2078 self._runs_per_test = runs_per_test
2079 self._loadtest = loadtest
2079 self._loadtest = loadtest
2080 self._showchannels = showchannels
2080 self._showchannels = showchannels
2081
2081
2082 def run(self, result):
2082 def run(self, result):
2083 # We have a number of filters that need to be applied. We do this
2083 # We have a number of filters that need to be applied. We do this
2084 # here instead of inside Test because it makes the running logic for
2084 # here instead of inside Test because it makes the running logic for
2085 # Test simpler.
2085 # Test simpler.
2086 tests = []
2086 tests = []
2087 num_tests = [0]
2087 num_tests = [0]
2088 for test in self._tests:
2088 for test in self._tests:
2089 def get():
2089 def get():
2090 num_tests[0] += 1
2090 num_tests[0] += 1
2091 if getattr(test, 'should_reload', False):
2091 if getattr(test, 'should_reload', False):
2092 return self._loadtest(test, num_tests[0])
2092 return self._loadtest(test, num_tests[0])
2093 return test
2093 return test
2094 if not os.path.exists(test.path):
2094 if not os.path.exists(test.path):
2095 result.addSkip(test, "Doesn't exist")
2095 result.addSkip(test, "Doesn't exist")
2096 continue
2096 continue
2097
2097
2098 if not (self._whitelist and test.bname in self._whitelist):
2098 if not (self._whitelist and test.bname in self._whitelist):
2099 if self._blacklist and test.bname in self._blacklist:
2099 if self._blacklist and test.bname in self._blacklist:
2100 result.addSkip(test, 'blacklisted')
2100 result.addSkip(test, 'blacklisted')
2101 continue
2101 continue
2102
2102
2103 if self._retest and not os.path.exists(test.errpath):
2103 if self._retest and not os.path.exists(test.errpath):
2104 result.addIgnore(test, 'not retesting')
2104 result.addIgnore(test, 'not retesting')
2105 continue
2105 continue
2106
2106
2107 if self._keywords:
2107 if self._keywords:
2108 with open(test.path, 'rb') as f:
2108 with open(test.path, 'rb') as f:
2109 t = f.read().lower() + test.bname.lower()
2109 t = f.read().lower() + test.bname.lower()
2110 ignored = False
2110 ignored = False
2111 for k in self._keywords.lower().split():
2111 for k in self._keywords.lower().split():
2112 if k not in t:
2112 if k not in t:
2113 result.addIgnore(test, "doesn't match keyword")
2113 result.addIgnore(test, "doesn't match keyword")
2114 ignored = True
2114 ignored = True
2115 break
2115 break
2116
2116
2117 if ignored:
2117 if ignored:
2118 continue
2118 continue
2119 for _ in xrange(self._runs_per_test):
2119 for _ in xrange(self._runs_per_test):
2120 tests.append(get())
2120 tests.append(get())
2121
2121
2122 runtests = list(tests)
2122 runtests = list(tests)
2123 done = queue.Queue()
2123 done = queue.Queue()
2124 running = 0
2124 running = 0
2125
2125
2126 channels = [""] * self._jobs
2126 channels = [""] * self._jobs
2127
2127
2128 def job(test, result):
2128 def job(test, result):
2129 for n, v in enumerate(channels):
2129 for n, v in enumerate(channels):
2130 if not v:
2130 if not v:
2131 channel = n
2131 channel = n
2132 break
2132 break
2133 else:
2133 else:
2134 raise ValueError('Could not find output channel')
2134 raise ValueError('Could not find output channel')
2135 channels[channel] = "=" + test.name[5:].split(".")[0]
2135 channels[channel] = "=" + test.name[5:].split(".")[0]
2136 try:
2136 try:
2137 test(result)
2137 test(result)
2138 done.put(None)
2138 done.put(None)
2139 except KeyboardInterrupt:
2139 except KeyboardInterrupt:
2140 pass
2140 pass
2141 except: # re-raises
2141 except: # re-raises
2142 done.put(('!', test, 'run-test raised an error, see traceback'))
2142 done.put(('!', test, 'run-test raised an error, see traceback'))
2143 raise
2143 raise
2144 finally:
2144 finally:
2145 try:
2145 try:
2146 channels[channel] = ''
2146 channels[channel] = ''
2147 except IndexError:
2147 except IndexError:
2148 pass
2148 pass
2149
2149
2150 def stat():
2150 def stat():
2151 count = 0
2151 count = 0
2152 while channels:
2152 while channels:
2153 d = '\n%03s ' % count
2153 d = '\n%03s ' % count
2154 for n, v in enumerate(channels):
2154 for n, v in enumerate(channels):
2155 if v:
2155 if v:
2156 d += v[0]
2156 d += v[0]
2157 channels[n] = v[1:] or '.'
2157 channels[n] = v[1:] or '.'
2158 else:
2158 else:
2159 d += ' '
2159 d += ' '
2160 d += ' '
2160 d += ' '
2161 with iolock:
2161 with iolock:
2162 sys.stdout.write(d + ' ')
2162 sys.stdout.write(d + ' ')
2163 sys.stdout.flush()
2163 sys.stdout.flush()
2164 for x in xrange(10):
2164 for x in xrange(10):
2165 if channels:
2165 if channels:
2166 time.sleep(.1)
2166 time.sleep(.1)
2167 count += 1
2167 count += 1
2168
2168
2169 stoppedearly = False
2169 stoppedearly = False
2170
2170
2171 if self._showchannels:
2171 if self._showchannels:
2172 statthread = threading.Thread(target=stat, name="stat")
2172 statthread = threading.Thread(target=stat, name="stat")
2173 statthread.start()
2173 statthread.start()
2174
2174
2175 try:
2175 try:
2176 while tests or running:
2176 while tests or running:
2177 if not done.empty() or running == self._jobs or not tests:
2177 if not done.empty() or running == self._jobs or not tests:
2178 try:
2178 try:
2179 done.get(True, 1)
2179 done.get(True, 1)
2180 running -= 1
2180 running -= 1
2181 if result and result.shouldStop:
2181 if result and result.shouldStop:
2182 stoppedearly = True
2182 stoppedearly = True
2183 break
2183 break
2184 except queue.Empty:
2184 except queue.Empty:
2185 continue
2185 continue
2186 if tests and not running == self._jobs:
2186 if tests and not running == self._jobs:
2187 test = tests.pop(0)
2187 test = tests.pop(0)
2188 if self._loop:
2188 if self._loop:
2189 if getattr(test, 'should_reload', False):
2189 if getattr(test, 'should_reload', False):
2190 num_tests[0] += 1
2190 num_tests[0] += 1
2191 tests.append(
2191 tests.append(
2192 self._loadtest(test, num_tests[0]))
2192 self._loadtest(test, num_tests[0]))
2193 else:
2193 else:
2194 tests.append(test)
2194 tests.append(test)
2195 if self._jobs == 1:
2195 if self._jobs == 1:
2196 job(test, result)
2196 job(test, result)
2197 else:
2197 else:
2198 t = threading.Thread(target=job, name=test.name,
2198 t = threading.Thread(target=job, name=test.name,
2199 args=(test, result))
2199 args=(test, result))
2200 t.start()
2200 t.start()
2201 running += 1
2201 running += 1
2202
2202
2203 # If we stop early we still need to wait on started tests to
2203 # If we stop early we still need to wait on started tests to
2204 # finish. Otherwise, there is a race between the test completing
2204 # finish. Otherwise, there is a race between the test completing
2205 # and the test's cleanup code running. This could result in the
2205 # and the test's cleanup code running. This could result in the
2206 # test reporting incorrect.
2206 # test reporting incorrect.
2207 if stoppedearly:
2207 if stoppedearly:
2208 while running:
2208 while running:
2209 try:
2209 try:
2210 done.get(True, 1)
2210 done.get(True, 1)
2211 running -= 1
2211 running -= 1
2212 except queue.Empty:
2212 except queue.Empty:
2213 continue
2213 continue
2214 except KeyboardInterrupt:
2214 except KeyboardInterrupt:
2215 for test in runtests:
2215 for test in runtests:
2216 test.abort()
2216 test.abort()
2217
2217
2218 channels = []
2218 channels = []
2219
2219
2220 return result
2220 return result
2221
2221
2222 # Save the most recent 5 wall-clock runtimes of each test to a
2222 # Save the most recent 5 wall-clock runtimes of each test to a
2223 # human-readable text file named .testtimes. Tests are sorted
2223 # human-readable text file named .testtimes. Tests are sorted
2224 # alphabetically, while times for each test are listed from oldest to
2224 # alphabetically, while times for each test are listed from oldest to
2225 # newest.
2225 # newest.
2226
2226
2227 def loadtimes(outputdir):
2227 def loadtimes(outputdir):
2228 times = []
2228 times = []
2229 try:
2229 try:
2230 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2230 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2231 for line in fp:
2231 for line in fp:
2232 m = re.match('(.*?) ([0-9. ]+)', line)
2232 m = re.match('(.*?) ([0-9. ]+)', line)
2233 times.append((m.group(1),
2233 times.append((m.group(1),
2234 [float(t) for t in m.group(2).split()]))
2234 [float(t) for t in m.group(2).split()]))
2235 except IOError as err:
2235 except IOError as err:
2236 if err.errno != errno.ENOENT:
2236 if err.errno != errno.ENOENT:
2237 raise
2237 raise
2238 return times
2238 return times
2239
2239
2240 def savetimes(outputdir, result):
2240 def savetimes(outputdir, result):
2241 saved = dict(loadtimes(outputdir))
2241 saved = dict(loadtimes(outputdir))
2242 maxruns = 5
2242 maxruns = 5
2243 skipped = set([str(t[0]) for t in result.skipped])
2243 skipped = set([str(t[0]) for t in result.skipped])
2244 for tdata in result.times:
2244 for tdata in result.times:
2245 test, real = tdata[0], tdata[3]
2245 test, real = tdata[0], tdata[3]
2246 if test not in skipped:
2246 if test not in skipped:
2247 ts = saved.setdefault(test, [])
2247 ts = saved.setdefault(test, [])
2248 ts.append(real)
2248 ts.append(real)
2249 ts[:] = ts[-maxruns:]
2249 ts[:] = ts[-maxruns:]
2250
2250
2251 fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
2251 fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
2252 dir=outputdir, text=True)
2252 dir=outputdir, text=True)
2253 with os.fdopen(fd, 'w') as fp:
2253 with os.fdopen(fd, 'w') as fp:
2254 for name, ts in sorted(saved.items()):
2254 for name, ts in sorted(saved.items()):
2255 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2255 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2256 timepath = os.path.join(outputdir, b'.testtimes')
2256 timepath = os.path.join(outputdir, b'.testtimes')
2257 try:
2257 try:
2258 os.unlink(timepath)
2258 os.unlink(timepath)
2259 except OSError:
2259 except OSError:
2260 pass
2260 pass
2261 try:
2261 try:
2262 os.rename(tmpname, timepath)
2262 os.rename(tmpname, timepath)
2263 except OSError:
2263 except OSError:
2264 pass
2264 pass
2265
2265
2266 class TextTestRunner(unittest.TextTestRunner):
2266 class TextTestRunner(unittest.TextTestRunner):
2267 """Custom unittest test runner that uses appropriate settings."""
2267 """Custom unittest test runner that uses appropriate settings."""
2268
2268
2269 def __init__(self, runner, *args, **kwargs):
2269 def __init__(self, runner, *args, **kwargs):
2270 super(TextTestRunner, self).__init__(*args, **kwargs)
2270 super(TextTestRunner, self).__init__(*args, **kwargs)
2271
2271
2272 self._runner = runner
2272 self._runner = runner
2273
2273
2274 self._result = getTestResult()(self._runner.options, self.stream,
2274 self._result = getTestResult()(self._runner.options, self.stream,
2275 self.descriptions, self.verbosity)
2275 self.descriptions, self.verbosity)
2276
2276
2277 def listtests(self, test):
2277 def listtests(self, test):
2278 test = sorted(test, key=lambda t: t.name)
2278 test = sorted(test, key=lambda t: t.name)
2279
2279
2280 self._result.onStart(test)
2280 self._result.onStart(test)
2281
2281
2282 for t in test:
2282 for t in test:
2283 print(t.name)
2283 print(t.name)
2284 self._result.addSuccess(t)
2284 self._result.addSuccess(t)
2285
2285
2286 if self._runner.options.xunit:
2286 if self._runner.options.xunit:
2287 with open(self._runner.options.xunit, "wb") as xuf:
2287 with open(self._runner.options.xunit, "wb") as xuf:
2288 self._writexunit(self._result, xuf)
2288 self._writexunit(self._result, xuf)
2289
2289
2290 if self._runner.options.json:
2290 if self._runner.options.json:
2291 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2291 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2292 with open(jsonpath, 'w') as fp:
2292 with open(jsonpath, 'w') as fp:
2293 self._writejson(self._result, fp)
2293 self._writejson(self._result, fp)
2294
2294
2295 return self._result
2295 return self._result
2296
2296
2297 def run(self, test):
2297 def run(self, test):
2298 self._result.onStart(test)
2298 self._result.onStart(test)
2299 test(self._result)
2299 test(self._result)
2300
2300
2301 failed = len(self._result.failures)
2301 failed = len(self._result.failures)
2302 skipped = len(self._result.skipped)
2302 skipped = len(self._result.skipped)
2303 ignored = len(self._result.ignored)
2303 ignored = len(self._result.ignored)
2304
2304
2305 with iolock:
2305 with iolock:
2306 self.stream.writeln('')
2306 self.stream.writeln('')
2307
2307
2308 if not self._runner.options.noskips:
2308 if not self._runner.options.noskips:
2309 for test, msg in sorted(self._result.skipped,
2309 for test, msg in sorted(self._result.skipped,
2310 key=lambda s: s[0].name):
2310 key=lambda s: s[0].name):
2311 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2311 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2312 msg = highlightmsg(formatted, self._result.color)
2312 msg = highlightmsg(formatted, self._result.color)
2313 self.stream.write(msg)
2313 self.stream.write(msg)
2314 for test, msg in sorted(self._result.failures,
2314 for test, msg in sorted(self._result.failures,
2315 key=lambda f: f[0].name):
2315 key=lambda f: f[0].name):
2316 formatted = 'Failed %s: %s\n' % (test.name, msg)
2316 formatted = 'Failed %s: %s\n' % (test.name, msg)
2317 self.stream.write(highlightmsg(formatted, self._result.color))
2317 self.stream.write(highlightmsg(formatted, self._result.color))
2318 for test, msg in sorted(self._result.errors,
2318 for test, msg in sorted(self._result.errors,
2319 key=lambda e: e[0].name):
2319 key=lambda e: e[0].name):
2320 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2320 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2321
2321
2322 if self._runner.options.xunit:
2322 if self._runner.options.xunit:
2323 with open(self._runner.options.xunit, "wb") as xuf:
2323 with open(self._runner.options.xunit, "wb") as xuf:
2324 self._writexunit(self._result, xuf)
2324 self._writexunit(self._result, xuf)
2325
2325
2326 if self._runner.options.json:
2326 if self._runner.options.json:
2327 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2327 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2328 with open(jsonpath, 'w') as fp:
2328 with open(jsonpath, 'w') as fp:
2329 self._writejson(self._result, fp)
2329 self._writejson(self._result, fp)
2330
2330
2331 self._runner._checkhglib('Tested')
2331 self._runner._checkhglib('Tested')
2332
2332
2333 savetimes(self._runner._outputdir, self._result)
2333 savetimes(self._runner._outputdir, self._result)
2334
2334
2335 if failed and self._runner.options.known_good_rev:
2335 if failed and self._runner.options.known_good_rev:
2336 self._bisecttests(t for t, m in self._result.failures)
2336 self._bisecttests(t for t, m in self._result.failures)
2337 self.stream.writeln(
2337 self.stream.writeln(
2338 '# Ran %d tests, %d skipped, %d failed.'
2338 '# Ran %d tests, %d skipped, %d failed.'
2339 % (self._result.testsRun, skipped + ignored, failed))
2339 % (self._result.testsRun, skipped + ignored, failed))
2340 if failed:
2340 if failed:
2341 self.stream.writeln('python hash seed: %s' %
2341 self.stream.writeln('python hash seed: %s' %
2342 os.environ['PYTHONHASHSEED'])
2342 os.environ['PYTHONHASHSEED'])
2343 if self._runner.options.time:
2343 if self._runner.options.time:
2344 self.printtimes(self._result.times)
2344 self.printtimes(self._result.times)
2345
2345
2346 if self._runner.options.exceptions:
2346 if self._runner.options.exceptions:
2347 exceptions = aggregateexceptions(
2347 exceptions = aggregateexceptions(
2348 os.path.join(self._runner._outputdir, b'exceptions'))
2348 os.path.join(self._runner._outputdir, b'exceptions'))
2349
2349
2350 self.stream.writeln('Exceptions Report:')
2350 self.stream.writeln('Exceptions Report:')
2351 self.stream.writeln('%d total from %d frames' %
2351 self.stream.writeln('%d total from %d frames' %
2352 (exceptions['total'],
2352 (exceptions['total'],
2353 len(exceptions['exceptioncounts'])))
2353 len(exceptions['exceptioncounts'])))
2354 combined = exceptions['combined']
2354 combined = exceptions['combined']
2355 for key in sorted(combined, key=combined.get, reverse=True):
2355 for key in sorted(combined, key=combined.get, reverse=True):
2356 frame, line, exc = key
2356 frame, line, exc = key
2357 totalcount, testcount, leastcount, leasttest = combined[key]
2357 totalcount, testcount, leastcount, leasttest = combined[key]
2358
2358
2359 self.stream.writeln('%d (%d tests)\t%s: %s (%s - %d total)'
2359 self.stream.writeln('%d (%d tests)\t%s: %s (%s - %d total)'
2360 % (totalcount,
2360 % (totalcount,
2361 testcount,
2361 testcount,
2362 frame, exc,
2362 frame, exc,
2363 leasttest, leastcount))
2363 leasttest, leastcount))
2364
2364
2365 self.stream.flush()
2365 self.stream.flush()
2366
2366
2367 return self._result
2367 return self._result
2368
2368
2369 def _bisecttests(self, tests):
2369 def _bisecttests(self, tests):
2370 bisectcmd = ['hg', 'bisect']
2370 bisectcmd = ['hg', 'bisect']
2371 bisectrepo = self._runner.options.bisect_repo
2371 bisectrepo = self._runner.options.bisect_repo
2372 if bisectrepo:
2372 if bisectrepo:
2373 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2373 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2374 def pread(args):
2374 def pread(args):
2375 env = os.environ.copy()
2375 env = os.environ.copy()
2376 env['HGPLAIN'] = '1'
2376 env['HGPLAIN'] = '1'
2377 p = subprocess.Popen(args, stderr=subprocess.STDOUT,
2377 p = subprocess.Popen(args, stderr=subprocess.STDOUT,
2378 stdout=subprocess.PIPE, env=env)
2378 stdout=subprocess.PIPE, env=env)
2379 data = p.stdout.read()
2379 data = p.stdout.read()
2380 p.wait()
2380 p.wait()
2381 return data
2381 return data
2382 for test in tests:
2382 for test in tests:
2383 pread(bisectcmd + ['--reset']),
2383 pread(bisectcmd + ['--reset']),
2384 pread(bisectcmd + ['--bad', '.'])
2384 pread(bisectcmd + ['--bad', '.'])
2385 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2385 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2386 # TODO: we probably need to forward more options
2386 # TODO: we probably need to forward more options
2387 # that alter hg's behavior inside the tests.
2387 # that alter hg's behavior inside the tests.
2388 opts = ''
2388 opts = ''
2389 withhg = self._runner.options.with_hg
2389 withhg = self._runner.options.with_hg
2390 if withhg:
2390 if withhg:
2391 opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
2391 opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
2392 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts,
2392 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts,
2393 test)
2393 test)
2394 data = pread(bisectcmd + ['--command', rtc])
2394 data = pread(bisectcmd + ['--command', rtc])
2395 m = re.search(
2395 m = re.search(
2396 (br'\nThe first (?P<goodbad>bad|good) revision '
2396 (br'\nThe first (?P<goodbad>bad|good) revision '
2397 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2397 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2398 br'summary: +(?P<summary>[^\n]+)\n'),
2398 br'summary: +(?P<summary>[^\n]+)\n'),
2399 data, (re.MULTILINE | re.DOTALL))
2399 data, (re.MULTILINE | re.DOTALL))
2400 if m is None:
2400 if m is None:
2401 self.stream.writeln(
2401 self.stream.writeln(
2402 'Failed to identify failure point for %s' % test)
2402 'Failed to identify failure point for %s' % test)
2403 continue
2403 continue
2404 dat = m.groupdict()
2404 dat = m.groupdict()
2405 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2405 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2406 self.stream.writeln(
2406 self.stream.writeln(
2407 '%s %s by %s (%s)' % (
2407 '%s %s by %s (%s)' % (
2408 test, verb, dat['node'].decode('ascii'),
2408 test, verb, dat['node'].decode('ascii'),
2409 dat['summary'].decode('utf8', 'ignore')))
2409 dat['summary'].decode('utf8', 'ignore')))
2410
2410
2411 def printtimes(self, times):
2411 def printtimes(self, times):
2412 # iolock held by run
2412 # iolock held by run
2413 self.stream.writeln('# Producing time report')
2413 self.stream.writeln('# Producing time report')
2414 times.sort(key=lambda t: (t[3]))
2414 times.sort(key=lambda t: (t[3]))
2415 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2415 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2416 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
2416 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
2417 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
2417 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
2418 for tdata in times:
2418 for tdata in times:
2419 test = tdata[0]
2419 test = tdata[0]
2420 cuser, csys, real, start, end = tdata[1:6]
2420 cuser, csys, real, start, end = tdata[1:6]
2421 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2421 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2422
2422
2423 @staticmethod
2423 @staticmethod
2424 def _writexunit(result, outf):
2424 def _writexunit(result, outf):
2425 # See http://llg.cubic.org/docs/junit/ for a reference.
2425 # See http://llg.cubic.org/docs/junit/ for a reference.
2426 timesd = dict((t[0], t[3]) for t in result.times)
2426 timesd = dict((t[0], t[3]) for t in result.times)
2427 doc = minidom.Document()
2427 doc = minidom.Document()
2428 s = doc.createElement('testsuite')
2428 s = doc.createElement('testsuite')
2429 s.setAttribute('errors', "0") # TODO
2429 s.setAttribute('errors', "0") # TODO
2430 s.setAttribute('failures', str(len(result.failures)))
2430 s.setAttribute('failures', str(len(result.failures)))
2431 s.setAttribute('name', 'run-tests')
2431 s.setAttribute('name', 'run-tests')
2432 s.setAttribute('skipped', str(len(result.skipped) +
2432 s.setAttribute('skipped', str(len(result.skipped) +
2433 len(result.ignored)))
2433 len(result.ignored)))
2434 s.setAttribute('tests', str(result.testsRun))
2434 s.setAttribute('tests', str(result.testsRun))
2435 doc.appendChild(s)
2435 doc.appendChild(s)
2436 for tc in result.successes:
2436 for tc in result.successes:
2437 t = doc.createElement('testcase')
2437 t = doc.createElement('testcase')
2438 t.setAttribute('name', tc.name)
2438 t.setAttribute('name', tc.name)
2439 tctime = timesd.get(tc.name)
2439 tctime = timesd.get(tc.name)
2440 if tctime is not None:
2440 if tctime is not None:
2441 t.setAttribute('time', '%.3f' % tctime)
2441 t.setAttribute('time', '%.3f' % tctime)
2442 s.appendChild(t)
2442 s.appendChild(t)
2443 for tc, err in sorted(result.faildata.items()):
2443 for tc, err in sorted(result.faildata.items()):
2444 t = doc.createElement('testcase')
2444 t = doc.createElement('testcase')
2445 t.setAttribute('name', tc)
2445 t.setAttribute('name', tc)
2446 tctime = timesd.get(tc)
2446 tctime = timesd.get(tc)
2447 if tctime is not None:
2447 if tctime is not None:
2448 t.setAttribute('time', '%.3f' % tctime)
2448 t.setAttribute('time', '%.3f' % tctime)
2449 # createCDATASection expects a unicode or it will
2449 # createCDATASection expects a unicode or it will
2450 # convert using default conversion rules, which will
2450 # convert using default conversion rules, which will
2451 # fail if string isn't ASCII.
2451 # fail if string isn't ASCII.
2452 err = cdatasafe(err).decode('utf-8', 'replace')
2452 err = cdatasafe(err).decode('utf-8', 'replace')
2453 cd = doc.createCDATASection(err)
2453 cd = doc.createCDATASection(err)
2454 # Use 'failure' here instead of 'error' to match errors = 0,
2454 # Use 'failure' here instead of 'error' to match errors = 0,
2455 # failures = len(result.failures) in the testsuite element.
2455 # failures = len(result.failures) in the testsuite element.
2456 failelem = doc.createElement('failure')
2456 failelem = doc.createElement('failure')
2457 failelem.setAttribute('message', 'output changed')
2457 failelem.setAttribute('message', 'output changed')
2458 failelem.setAttribute('type', 'output-mismatch')
2458 failelem.setAttribute('type', 'output-mismatch')
2459 failelem.appendChild(cd)
2459 failelem.appendChild(cd)
2460 t.appendChild(failelem)
2460 t.appendChild(failelem)
2461 s.appendChild(t)
2461 s.appendChild(t)
2462 for tc, message in result.skipped:
2462 for tc, message in result.skipped:
2463 # According to the schema, 'skipped' has no attributes. So store
2463 # According to the schema, 'skipped' has no attributes. So store
2464 # the skip message as a text node instead.
2464 # the skip message as a text node instead.
2465 t = doc.createElement('testcase')
2465 t = doc.createElement('testcase')
2466 t.setAttribute('name', tc.name)
2466 t.setAttribute('name', tc.name)
2467 binmessage = message.encode('utf-8')
2467 binmessage = message.encode('utf-8')
2468 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2468 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2469 cd = doc.createCDATASection(message)
2469 cd = doc.createCDATASection(message)
2470 skipelem = doc.createElement('skipped')
2470 skipelem = doc.createElement('skipped')
2471 skipelem.appendChild(cd)
2471 skipelem.appendChild(cd)
2472 t.appendChild(skipelem)
2472 t.appendChild(skipelem)
2473 s.appendChild(t)
2473 s.appendChild(t)
2474 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2474 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2475
2475
2476 @staticmethod
2476 @staticmethod
2477 def _writejson(result, outf):
2477 def _writejson(result, outf):
2478 timesd = {}
2478 timesd = {}
2479 for tdata in result.times:
2479 for tdata in result.times:
2480 test = tdata[0]
2480 test = tdata[0]
2481 timesd[test] = tdata[1:]
2481 timesd[test] = tdata[1:]
2482
2482
2483 outcome = {}
2483 outcome = {}
2484 groups = [('success', ((tc, None)
2484 groups = [('success', ((tc, None)
2485 for tc in result.successes)),
2485 for tc in result.successes)),
2486 ('failure', result.failures),
2486 ('failure', result.failures),
2487 ('skip', result.skipped)]
2487 ('skip', result.skipped)]
2488 for res, testcases in groups:
2488 for res, testcases in groups:
2489 for tc, __ in testcases:
2489 for tc, __ in testcases:
2490 if tc.name in timesd:
2490 if tc.name in timesd:
2491 diff = result.faildata.get(tc.name, b'')
2491 diff = result.faildata.get(tc.name, b'')
2492 try:
2492 try:
2493 diff = diff.decode('unicode_escape')
2493 diff = diff.decode('unicode_escape')
2494 except UnicodeDecodeError as e:
2494 except UnicodeDecodeError as e:
2495 diff = '%r decoding diff, sorry' % e
2495 diff = '%r decoding diff, sorry' % e
2496 tres = {'result': res,
2496 tres = {'result': res,
2497 'time': ('%0.3f' % timesd[tc.name][2]),
2497 'time': ('%0.3f' % timesd[tc.name][2]),
2498 'cuser': ('%0.3f' % timesd[tc.name][0]),
2498 'cuser': ('%0.3f' % timesd[tc.name][0]),
2499 'csys': ('%0.3f' % timesd[tc.name][1]),
2499 'csys': ('%0.3f' % timesd[tc.name][1]),
2500 'start': ('%0.3f' % timesd[tc.name][3]),
2500 'start': ('%0.3f' % timesd[tc.name][3]),
2501 'end': ('%0.3f' % timesd[tc.name][4]),
2501 'end': ('%0.3f' % timesd[tc.name][4]),
2502 'diff': diff,
2502 'diff': diff,
2503 }
2503 }
2504 else:
2504 else:
2505 # blacklisted test
2505 # blacklisted test
2506 tres = {'result': res}
2506 tres = {'result': res}
2507
2507
2508 outcome[tc.name] = tres
2508 outcome[tc.name] = tres
2509 jsonout = json.dumps(outcome, sort_keys=True, indent=4,
2509 jsonout = json.dumps(outcome, sort_keys=True, indent=4,
2510 separators=(',', ': '))
2510 separators=(',', ': '))
2511 outf.writelines(("testreport =", jsonout))
2511 outf.writelines(("testreport =", jsonout))
2512
2512
2513 def sorttests(testdescs, previoustimes, shuffle=False):
2513 def sorttests(testdescs, previoustimes, shuffle=False):
2514 """Do an in-place sort of tests."""
2514 """Do an in-place sort of tests."""
2515 if shuffle:
2515 if shuffle:
2516 random.shuffle(testdescs)
2516 random.shuffle(testdescs)
2517 return
2517 return
2518
2518
2519 if previoustimes:
2519 if previoustimes:
2520 def sortkey(f):
2520 def sortkey(f):
2521 f = f['path']
2521 f = f['path']
2522 if f in previoustimes:
2522 if f in previoustimes:
2523 # Use most recent time as estimate
2523 # Use most recent time as estimate
2524 return -previoustimes[f][-1]
2524 return -previoustimes[f][-1]
2525 else:
2525 else:
2526 # Default to a rather arbitrary value of 1 second for new tests
2526 # Default to a rather arbitrary value of 1 second for new tests
2527 return -1.0
2527 return -1.0
2528 else:
2528 else:
2529 # keywords for slow tests
2529 # keywords for slow tests
2530 slow = {b'svn': 10,
2530 slow = {b'svn': 10,
2531 b'cvs': 10,
2531 b'cvs': 10,
2532 b'hghave': 10,
2532 b'hghave': 10,
2533 b'largefiles-update': 10,
2533 b'largefiles-update': 10,
2534 b'run-tests': 10,
2534 b'run-tests': 10,
2535 b'corruption': 10,
2535 b'corruption': 10,
2536 b'race': 10,
2536 b'race': 10,
2537 b'i18n': 10,
2537 b'i18n': 10,
2538 b'check': 100,
2538 b'check': 100,
2539 b'gendoc': 100,
2539 b'gendoc': 100,
2540 b'contrib-perf': 200,
2540 b'contrib-perf': 200,
2541 b'merge-combination': 100,
2541 }
2542 }
2542 perf = {}
2543 perf = {}
2543
2544
2544 def sortkey(f):
2545 def sortkey(f):
2545 # run largest tests first, as they tend to take the longest
2546 # run largest tests first, as they tend to take the longest
2546 f = f['path']
2547 f = f['path']
2547 try:
2548 try:
2548 return perf[f]
2549 return perf[f]
2549 except KeyError:
2550 except KeyError:
2550 try:
2551 try:
2551 val = -os.stat(f).st_size
2552 val = -os.stat(f).st_size
2552 except OSError as e:
2553 except OSError as e:
2553 if e.errno != errno.ENOENT:
2554 if e.errno != errno.ENOENT:
2554 raise
2555 raise
2555 perf[f] = -1e9 # file does not exist, tell early
2556 perf[f] = -1e9 # file does not exist, tell early
2556 return -1e9
2557 return -1e9
2557 for kw, mul in slow.items():
2558 for kw, mul in slow.items():
2558 if kw in f:
2559 if kw in f:
2559 val *= mul
2560 val *= mul
2560 if f.endswith(b'.py'):
2561 if f.endswith(b'.py'):
2561 val /= 10.0
2562 val /= 10.0
2562 perf[f] = val / 1000.0
2563 perf[f] = val / 1000.0
2563 return perf[f]
2564 return perf[f]
2564
2565
2565 testdescs.sort(key=sortkey)
2566 testdescs.sort(key=sortkey)
2566
2567
2567 class TestRunner(object):
2568 class TestRunner(object):
2568 """Holds context for executing tests.
2569 """Holds context for executing tests.
2569
2570
2570 Tests rely on a lot of state. This object holds it for them.
2571 Tests rely on a lot of state. This object holds it for them.
2571 """
2572 """
2572
2573
2573 # Programs required to run tests.
2574 # Programs required to run tests.
2574 REQUIREDTOOLS = [
2575 REQUIREDTOOLS = [
2575 b'diff',
2576 b'diff',
2576 b'grep',
2577 b'grep',
2577 b'unzip',
2578 b'unzip',
2578 b'gunzip',
2579 b'gunzip',
2579 b'bunzip2',
2580 b'bunzip2',
2580 b'sed',
2581 b'sed',
2581 ]
2582 ]
2582
2583
2583 # Maps file extensions to test class.
2584 # Maps file extensions to test class.
2584 TESTTYPES = [
2585 TESTTYPES = [
2585 (b'.py', PythonTest),
2586 (b'.py', PythonTest),
2586 (b'.t', TTest),
2587 (b'.t', TTest),
2587 ]
2588 ]
2588
2589
2589 def __init__(self):
2590 def __init__(self):
2590 self.options = None
2591 self.options = None
2591 self._hgroot = None
2592 self._hgroot = None
2592 self._testdir = None
2593 self._testdir = None
2593 self._outputdir = None
2594 self._outputdir = None
2594 self._hgtmp = None
2595 self._hgtmp = None
2595 self._installdir = None
2596 self._installdir = None
2596 self._bindir = None
2597 self._bindir = None
2597 self._tmpbinddir = None
2598 self._tmpbinddir = None
2598 self._pythondir = None
2599 self._pythondir = None
2599 self._coveragefile = None
2600 self._coveragefile = None
2600 self._createdfiles = []
2601 self._createdfiles = []
2601 self._hgcommand = None
2602 self._hgcommand = None
2602 self._hgpath = None
2603 self._hgpath = None
2603 self._portoffset = 0
2604 self._portoffset = 0
2604 self._ports = {}
2605 self._ports = {}
2605
2606
2606 def run(self, args, parser=None):
2607 def run(self, args, parser=None):
2607 """Run the test suite."""
2608 """Run the test suite."""
2608 oldmask = os.umask(0o22)
2609 oldmask = os.umask(0o22)
2609 try:
2610 try:
2610 parser = parser or getparser()
2611 parser = parser or getparser()
2611 options = parseargs(args, parser)
2612 options = parseargs(args, parser)
2612 tests = [_bytespath(a) for a in options.tests]
2613 tests = [_bytespath(a) for a in options.tests]
2613 if options.test_list is not None:
2614 if options.test_list is not None:
2614 for listfile in options.test_list:
2615 for listfile in options.test_list:
2615 with open(listfile, 'rb') as f:
2616 with open(listfile, 'rb') as f:
2616 tests.extend(t for t in f.read().splitlines() if t)
2617 tests.extend(t for t in f.read().splitlines() if t)
2617 self.options = options
2618 self.options = options
2618
2619
2619 self._checktools()
2620 self._checktools()
2620 testdescs = self.findtests(tests)
2621 testdescs = self.findtests(tests)
2621 if options.profile_runner:
2622 if options.profile_runner:
2622 import statprof
2623 import statprof
2623 statprof.start()
2624 statprof.start()
2624 result = self._run(testdescs)
2625 result = self._run(testdescs)
2625 if options.profile_runner:
2626 if options.profile_runner:
2626 statprof.stop()
2627 statprof.stop()
2627 statprof.display()
2628 statprof.display()
2628 return result
2629 return result
2629
2630
2630 finally:
2631 finally:
2631 os.umask(oldmask)
2632 os.umask(oldmask)
2632
2633
2633 def _run(self, testdescs):
2634 def _run(self, testdescs):
2634 testdir = getcwdb()
2635 testdir = getcwdb()
2635 self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
2636 self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
2636 # assume all tests in same folder for now
2637 # assume all tests in same folder for now
2637 if testdescs:
2638 if testdescs:
2638 pathname = os.path.dirname(testdescs[0]['path'])
2639 pathname = os.path.dirname(testdescs[0]['path'])
2639 if pathname:
2640 if pathname:
2640 testdir = os.path.join(testdir, pathname)
2641 testdir = os.path.join(testdir, pathname)
2641 self._testdir = osenvironb[b'TESTDIR'] = testdir
2642 self._testdir = osenvironb[b'TESTDIR'] = testdir
2642 if self.options.outputdir:
2643 if self.options.outputdir:
2643 self._outputdir = canonpath(_bytespath(self.options.outputdir))
2644 self._outputdir = canonpath(_bytespath(self.options.outputdir))
2644 else:
2645 else:
2645 self._outputdir = getcwdb()
2646 self._outputdir = getcwdb()
2646 if testdescs and pathname:
2647 if testdescs and pathname:
2647 self._outputdir = os.path.join(self._outputdir, pathname)
2648 self._outputdir = os.path.join(self._outputdir, pathname)
2648 previoustimes = {}
2649 previoustimes = {}
2649 if self.options.order_by_runtime:
2650 if self.options.order_by_runtime:
2650 previoustimes = dict(loadtimes(self._outputdir))
2651 previoustimes = dict(loadtimes(self._outputdir))
2651 sorttests(testdescs, previoustimes, shuffle=self.options.random)
2652 sorttests(testdescs, previoustimes, shuffle=self.options.random)
2652
2653
2653 if 'PYTHONHASHSEED' not in os.environ:
2654 if 'PYTHONHASHSEED' not in os.environ:
2654 # use a random python hash seed all the time
2655 # use a random python hash seed all the time
2655 # we do the randomness ourself to know what seed is used
2656 # we do the randomness ourself to know what seed is used
2656 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
2657 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
2657
2658
2658 if self.options.tmpdir:
2659 if self.options.tmpdir:
2659 self.options.keep_tmpdir = True
2660 self.options.keep_tmpdir = True
2660 tmpdir = _bytespath(self.options.tmpdir)
2661 tmpdir = _bytespath(self.options.tmpdir)
2661 if os.path.exists(tmpdir):
2662 if os.path.exists(tmpdir):
2662 # Meaning of tmpdir has changed since 1.3: we used to create
2663 # Meaning of tmpdir has changed since 1.3: we used to create
2663 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
2664 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
2664 # tmpdir already exists.
2665 # tmpdir already exists.
2665 print("error: temp dir %r already exists" % tmpdir)
2666 print("error: temp dir %r already exists" % tmpdir)
2666 return 1
2667 return 1
2667
2668
2668 os.makedirs(tmpdir)
2669 os.makedirs(tmpdir)
2669 else:
2670 else:
2670 d = None
2671 d = None
2671 if os.name == 'nt':
2672 if os.name == 'nt':
2672 # without this, we get the default temp dir location, but
2673 # without this, we get the default temp dir location, but
2673 # in all lowercase, which causes troubles with paths (issue3490)
2674 # in all lowercase, which causes troubles with paths (issue3490)
2674 d = osenvironb.get(b'TMP', None)
2675 d = osenvironb.get(b'TMP', None)
2675 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
2676 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
2676
2677
2677 self._hgtmp = osenvironb[b'HGTMP'] = (
2678 self._hgtmp = osenvironb[b'HGTMP'] = (
2678 os.path.realpath(tmpdir))
2679 os.path.realpath(tmpdir))
2679
2680
2680 if self.options.with_hg:
2681 if self.options.with_hg:
2681 self._installdir = None
2682 self._installdir = None
2682 whg = self.options.with_hg
2683 whg = self.options.with_hg
2683 self._bindir = os.path.dirname(os.path.realpath(whg))
2684 self._bindir = os.path.dirname(os.path.realpath(whg))
2684 assert isinstance(self._bindir, bytes)
2685 assert isinstance(self._bindir, bytes)
2685 self._hgcommand = os.path.basename(whg)
2686 self._hgcommand = os.path.basename(whg)
2686 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
2687 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
2687 os.makedirs(self._tmpbindir)
2688 os.makedirs(self._tmpbindir)
2688
2689
2689 normbin = os.path.normpath(os.path.abspath(whg))
2690 normbin = os.path.normpath(os.path.abspath(whg))
2690 normbin = normbin.replace(os.sep.encode('ascii'), b'/')
2691 normbin = normbin.replace(os.sep.encode('ascii'), b'/')
2691
2692
2692 # Other Python scripts in the test harness need to
2693 # Other Python scripts in the test harness need to
2693 # `import mercurial`. If `hg` is a Python script, we assume
2694 # `import mercurial`. If `hg` is a Python script, we assume
2694 # the Mercurial modules are relative to its path and tell the tests
2695 # the Mercurial modules are relative to its path and tell the tests
2695 # to load Python modules from its directory.
2696 # to load Python modules from its directory.
2696 with open(whg, 'rb') as fh:
2697 with open(whg, 'rb') as fh:
2697 initial = fh.read(1024)
2698 initial = fh.read(1024)
2698
2699
2699 if re.match(b'#!.*python', initial):
2700 if re.match(b'#!.*python', initial):
2700 self._pythondir = self._bindir
2701 self._pythondir = self._bindir
2701 # If it looks like our in-repo Rust binary, use the source root.
2702 # If it looks like our in-repo Rust binary, use the source root.
2702 # This is a bit hacky. But rhg is still not supported outside the
2703 # This is a bit hacky. But rhg is still not supported outside the
2703 # source directory. So until it is, do the simple thing.
2704 # source directory. So until it is, do the simple thing.
2704 elif re.search(b'/rust/target/[^/]+/hg', normbin):
2705 elif re.search(b'/rust/target/[^/]+/hg', normbin):
2705 self._pythondir = os.path.dirname(self._testdir)
2706 self._pythondir = os.path.dirname(self._testdir)
2706 # Fall back to the legacy behavior.
2707 # Fall back to the legacy behavior.
2707 else:
2708 else:
2708 self._pythondir = self._bindir
2709 self._pythondir = self._bindir
2709
2710
2710 else:
2711 else:
2711 self._installdir = os.path.join(self._hgtmp, b"install")
2712 self._installdir = os.path.join(self._hgtmp, b"install")
2712 self._bindir = os.path.join(self._installdir, b"bin")
2713 self._bindir = os.path.join(self._installdir, b"bin")
2713 self._hgcommand = b'hg'
2714 self._hgcommand = b'hg'
2714 self._tmpbindir = self._bindir
2715 self._tmpbindir = self._bindir
2715 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
2716 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
2716
2717
2717 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
2718 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
2718 # a python script and feed it to python.exe. Legacy stdio is force
2719 # a python script and feed it to python.exe. Legacy stdio is force
2719 # enabled by hg.exe, and this is a more realistic way to launch hg
2720 # enabled by hg.exe, and this is a more realistic way to launch hg
2720 # anyway.
2721 # anyway.
2721 if os.name == 'nt' and not self._hgcommand.endswith(b'.exe'):
2722 if os.name == 'nt' and not self._hgcommand.endswith(b'.exe'):
2722 self._hgcommand += b'.exe'
2723 self._hgcommand += b'.exe'
2723
2724
2724 # set CHGHG, then replace "hg" command by "chg"
2725 # set CHGHG, then replace "hg" command by "chg"
2725 chgbindir = self._bindir
2726 chgbindir = self._bindir
2726 if self.options.chg or self.options.with_chg:
2727 if self.options.chg or self.options.with_chg:
2727 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
2728 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
2728 else:
2729 else:
2729 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
2730 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
2730 if self.options.chg:
2731 if self.options.chg:
2731 self._hgcommand = b'chg'
2732 self._hgcommand = b'chg'
2732 elif self.options.with_chg:
2733 elif self.options.with_chg:
2733 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
2734 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
2734 self._hgcommand = os.path.basename(self.options.with_chg)
2735 self._hgcommand = os.path.basename(self.options.with_chg)
2735
2736
2736 osenvironb[b"BINDIR"] = self._bindir
2737 osenvironb[b"BINDIR"] = self._bindir
2737 osenvironb[b"PYTHON"] = PYTHON
2738 osenvironb[b"PYTHON"] = PYTHON
2738
2739
2739 fileb = _bytespath(__file__)
2740 fileb = _bytespath(__file__)
2740 runtestdir = os.path.abspath(os.path.dirname(fileb))
2741 runtestdir = os.path.abspath(os.path.dirname(fileb))
2741 osenvironb[b'RUNTESTDIR'] = runtestdir
2742 osenvironb[b'RUNTESTDIR'] = runtestdir
2742 if PYTHON3:
2743 if PYTHON3:
2743 sepb = _bytespath(os.pathsep)
2744 sepb = _bytespath(os.pathsep)
2744 else:
2745 else:
2745 sepb = os.pathsep
2746 sepb = os.pathsep
2746 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
2747 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
2747 if os.path.islink(__file__):
2748 if os.path.islink(__file__):
2748 # test helper will likely be at the end of the symlink
2749 # test helper will likely be at the end of the symlink
2749 realfile = os.path.realpath(fileb)
2750 realfile = os.path.realpath(fileb)
2750 realdir = os.path.abspath(os.path.dirname(realfile))
2751 realdir = os.path.abspath(os.path.dirname(realfile))
2751 path.insert(2, realdir)
2752 path.insert(2, realdir)
2752 if chgbindir != self._bindir:
2753 if chgbindir != self._bindir:
2753 path.insert(1, chgbindir)
2754 path.insert(1, chgbindir)
2754 if self._testdir != runtestdir:
2755 if self._testdir != runtestdir:
2755 path = [self._testdir] + path
2756 path = [self._testdir] + path
2756 if self._tmpbindir != self._bindir:
2757 if self._tmpbindir != self._bindir:
2757 path = [self._tmpbindir] + path
2758 path = [self._tmpbindir] + path
2758 osenvironb[b"PATH"] = sepb.join(path)
2759 osenvironb[b"PATH"] = sepb.join(path)
2759
2760
2760 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2761 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2761 # can run .../tests/run-tests.py test-foo where test-foo
2762 # can run .../tests/run-tests.py test-foo where test-foo
2762 # adds an extension to HGRC. Also include run-test.py directory to
2763 # adds an extension to HGRC. Also include run-test.py directory to
2763 # import modules like heredoctest.
2764 # import modules like heredoctest.
2764 pypath = [self._pythondir, self._testdir, runtestdir]
2765 pypath = [self._pythondir, self._testdir, runtestdir]
2765 # We have to augment PYTHONPATH, rather than simply replacing
2766 # We have to augment PYTHONPATH, rather than simply replacing
2766 # it, in case external libraries are only available via current
2767 # it, in case external libraries are only available via current
2767 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2768 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2768 # are in /opt/subversion.)
2769 # are in /opt/subversion.)
2769 oldpypath = osenvironb.get(IMPL_PATH)
2770 oldpypath = osenvironb.get(IMPL_PATH)
2770 if oldpypath:
2771 if oldpypath:
2771 pypath.append(oldpypath)
2772 pypath.append(oldpypath)
2772 osenvironb[IMPL_PATH] = sepb.join(pypath)
2773 osenvironb[IMPL_PATH] = sepb.join(pypath)
2773
2774
2774 if self.options.pure:
2775 if self.options.pure:
2775 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2776 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2776 os.environ["HGMODULEPOLICY"] = "py"
2777 os.environ["HGMODULEPOLICY"] = "py"
2777
2778
2778 if self.options.allow_slow_tests:
2779 if self.options.allow_slow_tests:
2779 os.environ["HGTEST_SLOW"] = "slow"
2780 os.environ["HGTEST_SLOW"] = "slow"
2780 elif 'HGTEST_SLOW' in os.environ:
2781 elif 'HGTEST_SLOW' in os.environ:
2781 del os.environ['HGTEST_SLOW']
2782 del os.environ['HGTEST_SLOW']
2782
2783
2783 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2784 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2784
2785
2785 if self.options.exceptions:
2786 if self.options.exceptions:
2786 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
2787 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
2787 try:
2788 try:
2788 os.makedirs(exceptionsdir)
2789 os.makedirs(exceptionsdir)
2789 except OSError as e:
2790 except OSError as e:
2790 if e.errno != errno.EEXIST:
2791 if e.errno != errno.EEXIST:
2791 raise
2792 raise
2792
2793
2793 # Remove all existing exception reports.
2794 # Remove all existing exception reports.
2794 for f in os.listdir(exceptionsdir):
2795 for f in os.listdir(exceptionsdir):
2795 os.unlink(os.path.join(exceptionsdir, f))
2796 os.unlink(os.path.join(exceptionsdir, f))
2796
2797
2797 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
2798 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
2798 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
2799 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
2799 self.options.extra_config_opt.append(
2800 self.options.extra_config_opt.append(
2800 'extensions.logexceptions=%s' % logexceptions.decode('utf-8'))
2801 'extensions.logexceptions=%s' % logexceptions.decode('utf-8'))
2801
2802
2802 vlog("# Using TESTDIR", self._testdir)
2803 vlog("# Using TESTDIR", self._testdir)
2803 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2804 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2804 vlog("# Using HGTMP", self._hgtmp)
2805 vlog("# Using HGTMP", self._hgtmp)
2805 vlog("# Using PATH", os.environ["PATH"])
2806 vlog("# Using PATH", os.environ["PATH"])
2806 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2807 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2807 vlog("# Writing to directory", self._outputdir)
2808 vlog("# Writing to directory", self._outputdir)
2808
2809
2809 try:
2810 try:
2810 return self._runtests(testdescs) or 0
2811 return self._runtests(testdescs) or 0
2811 finally:
2812 finally:
2812 time.sleep(.1)
2813 time.sleep(.1)
2813 self._cleanup()
2814 self._cleanup()
2814
2815
2815 def findtests(self, args):
2816 def findtests(self, args):
2816 """Finds possible test files from arguments.
2817 """Finds possible test files from arguments.
2817
2818
2818 If you wish to inject custom tests into the test harness, this would
2819 If you wish to inject custom tests into the test harness, this would
2819 be a good function to monkeypatch or override in a derived class.
2820 be a good function to monkeypatch or override in a derived class.
2820 """
2821 """
2821 if not args:
2822 if not args:
2822 if self.options.changed:
2823 if self.options.changed:
2823 proc = Popen4(b'hg st --rev "%s" -man0 .' %
2824 proc = Popen4(b'hg st --rev "%s" -man0 .' %
2824 _bytespath(self.options.changed), None, 0)
2825 _bytespath(self.options.changed), None, 0)
2825 stdout, stderr = proc.communicate()
2826 stdout, stderr = proc.communicate()
2826 args = stdout.strip(b'\0').split(b'\0')
2827 args = stdout.strip(b'\0').split(b'\0')
2827 else:
2828 else:
2828 args = os.listdir(b'.')
2829 args = os.listdir(b'.')
2829
2830
2830 expanded_args = []
2831 expanded_args = []
2831 for arg in args:
2832 for arg in args:
2832 if os.path.isdir(arg):
2833 if os.path.isdir(arg):
2833 if not arg.endswith(b'/'):
2834 if not arg.endswith(b'/'):
2834 arg += b'/'
2835 arg += b'/'
2835 expanded_args.extend([arg + a for a in os.listdir(arg)])
2836 expanded_args.extend([arg + a for a in os.listdir(arg)])
2836 else:
2837 else:
2837 expanded_args.append(arg)
2838 expanded_args.append(arg)
2838 args = expanded_args
2839 args = expanded_args
2839
2840
2840 testcasepattern = re.compile(
2841 testcasepattern = re.compile(
2841 br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-\.#]+))')
2842 br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-\.#]+))')
2842 tests = []
2843 tests = []
2843 for t in args:
2844 for t in args:
2844 case = []
2845 case = []
2845
2846
2846 if not (os.path.basename(t).startswith(b'test-')
2847 if not (os.path.basename(t).startswith(b'test-')
2847 and (t.endswith(b'.py') or t.endswith(b'.t'))):
2848 and (t.endswith(b'.py') or t.endswith(b'.t'))):
2848
2849
2849 m = testcasepattern.match(os.path.basename(t))
2850 m = testcasepattern.match(os.path.basename(t))
2850 if m is not None:
2851 if m is not None:
2851 t_basename, casestr = m.groups()
2852 t_basename, casestr = m.groups()
2852 t = os.path.join(os.path.dirname(t), t_basename)
2853 t = os.path.join(os.path.dirname(t), t_basename)
2853 if casestr:
2854 if casestr:
2854 case = casestr.split(b'#')
2855 case = casestr.split(b'#')
2855 else:
2856 else:
2856 continue
2857 continue
2857
2858
2858 if t.endswith(b'.t'):
2859 if t.endswith(b'.t'):
2859 # .t file may contain multiple test cases
2860 # .t file may contain multiple test cases
2860 casedimensions = parsettestcases(t)
2861 casedimensions = parsettestcases(t)
2861 if casedimensions:
2862 if casedimensions:
2862 cases = []
2863 cases = []
2863 def addcases(case, casedimensions):
2864 def addcases(case, casedimensions):
2864 if not casedimensions:
2865 if not casedimensions:
2865 cases.append(case)
2866 cases.append(case)
2866 else:
2867 else:
2867 for c in casedimensions[0]:
2868 for c in casedimensions[0]:
2868 addcases(case + [c], casedimensions[1:])
2869 addcases(case + [c], casedimensions[1:])
2869 addcases([], casedimensions)
2870 addcases([], casedimensions)
2870 if case and case in cases:
2871 if case and case in cases:
2871 cases = [case]
2872 cases = [case]
2872 elif case:
2873 elif case:
2873 # Ignore invalid cases
2874 # Ignore invalid cases
2874 cases = []
2875 cases = []
2875 else:
2876 else:
2876 pass
2877 pass
2877 tests += [{'path': t, 'case': c} for c in sorted(cases)]
2878 tests += [{'path': t, 'case': c} for c in sorted(cases)]
2878 else:
2879 else:
2879 tests.append({'path': t})
2880 tests.append({'path': t})
2880 else:
2881 else:
2881 tests.append({'path': t})
2882 tests.append({'path': t})
2882 return tests
2883 return tests
2883
2884
2884 def _runtests(self, testdescs):
2885 def _runtests(self, testdescs):
2885 def _reloadtest(test, i):
2886 def _reloadtest(test, i):
2886 # convert a test back to its description dict
2887 # convert a test back to its description dict
2887 desc = {'path': test.path}
2888 desc = {'path': test.path}
2888 case = getattr(test, '_case', [])
2889 case = getattr(test, '_case', [])
2889 if case:
2890 if case:
2890 desc['case'] = case
2891 desc['case'] = case
2891 return self._gettest(desc, i)
2892 return self._gettest(desc, i)
2892
2893
2893 try:
2894 try:
2894 if self.options.restart:
2895 if self.options.restart:
2895 orig = list(testdescs)
2896 orig = list(testdescs)
2896 while testdescs:
2897 while testdescs:
2897 desc = testdescs[0]
2898 desc = testdescs[0]
2898 # desc['path'] is a relative path
2899 # desc['path'] is a relative path
2899 if 'case' in desc:
2900 if 'case' in desc:
2900 casestr = b'#'.join(desc['case'])
2901 casestr = b'#'.join(desc['case'])
2901 errpath = b'%s#%s.err' % (desc['path'], casestr)
2902 errpath = b'%s#%s.err' % (desc['path'], casestr)
2902 else:
2903 else:
2903 errpath = b'%s.err' % desc['path']
2904 errpath = b'%s.err' % desc['path']
2904 errpath = os.path.join(self._outputdir, errpath)
2905 errpath = os.path.join(self._outputdir, errpath)
2905 if os.path.exists(errpath):
2906 if os.path.exists(errpath):
2906 break
2907 break
2907 testdescs.pop(0)
2908 testdescs.pop(0)
2908 if not testdescs:
2909 if not testdescs:
2909 print("running all tests")
2910 print("running all tests")
2910 testdescs = orig
2911 testdescs = orig
2911
2912
2912 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
2913 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
2913 num_tests = len(tests) * self.options.runs_per_test
2914 num_tests = len(tests) * self.options.runs_per_test
2914
2915
2915 jobs = min(num_tests, self.options.jobs)
2916 jobs = min(num_tests, self.options.jobs)
2916
2917
2917 failed = False
2918 failed = False
2918 kws = self.options.keywords
2919 kws = self.options.keywords
2919 if kws is not None and PYTHON3:
2920 if kws is not None and PYTHON3:
2920 kws = kws.encode('utf-8')
2921 kws = kws.encode('utf-8')
2921
2922
2922 suite = TestSuite(self._testdir,
2923 suite = TestSuite(self._testdir,
2923 jobs=jobs,
2924 jobs=jobs,
2924 whitelist=self.options.whitelisted,
2925 whitelist=self.options.whitelisted,
2925 blacklist=self.options.blacklist,
2926 blacklist=self.options.blacklist,
2926 retest=self.options.retest,
2927 retest=self.options.retest,
2927 keywords=kws,
2928 keywords=kws,
2928 loop=self.options.loop,
2929 loop=self.options.loop,
2929 runs_per_test=self.options.runs_per_test,
2930 runs_per_test=self.options.runs_per_test,
2930 showchannels=self.options.showchannels,
2931 showchannels=self.options.showchannels,
2931 tests=tests, loadtest=_reloadtest)
2932 tests=tests, loadtest=_reloadtest)
2932 verbosity = 1
2933 verbosity = 1
2933 if self.options.list_tests:
2934 if self.options.list_tests:
2934 verbosity = 0
2935 verbosity = 0
2935 elif self.options.verbose:
2936 elif self.options.verbose:
2936 verbosity = 2
2937 verbosity = 2
2937 runner = TextTestRunner(self, verbosity=verbosity)
2938 runner = TextTestRunner(self, verbosity=verbosity)
2938
2939
2939 if self.options.list_tests:
2940 if self.options.list_tests:
2940 result = runner.listtests(suite)
2941 result = runner.listtests(suite)
2941 else:
2942 else:
2942 if self._installdir:
2943 if self._installdir:
2943 self._installhg()
2944 self._installhg()
2944 self._checkhglib("Testing")
2945 self._checkhglib("Testing")
2945 else:
2946 else:
2946 self._usecorrectpython()
2947 self._usecorrectpython()
2947 if self.options.chg:
2948 if self.options.chg:
2948 assert self._installdir
2949 assert self._installdir
2949 self._installchg()
2950 self._installchg()
2950
2951
2951 log('running %d tests using %d parallel processes' % (
2952 log('running %d tests using %d parallel processes' % (
2952 num_tests, jobs))
2953 num_tests, jobs))
2953
2954
2954 result = runner.run(suite)
2955 result = runner.run(suite)
2955
2956
2956 if result.failures or result.errors:
2957 if result.failures or result.errors:
2957 failed = True
2958 failed = True
2958
2959
2959 result.onEnd()
2960 result.onEnd()
2960
2961
2961 if self.options.anycoverage:
2962 if self.options.anycoverage:
2962 self._outputcoverage()
2963 self._outputcoverage()
2963 except KeyboardInterrupt:
2964 except KeyboardInterrupt:
2964 failed = True
2965 failed = True
2965 print("\ninterrupted!")
2966 print("\ninterrupted!")
2966
2967
2967 if failed:
2968 if failed:
2968 return 1
2969 return 1
2969
2970
2970 def _getport(self, count):
2971 def _getport(self, count):
2971 port = self._ports.get(count) # do we have a cached entry?
2972 port = self._ports.get(count) # do we have a cached entry?
2972 if port is None:
2973 if port is None:
2973 portneeded = 3
2974 portneeded = 3
2974 # above 100 tries we just give up and let test reports failure
2975 # above 100 tries we just give up and let test reports failure
2975 for tries in xrange(100):
2976 for tries in xrange(100):
2976 allfree = True
2977 allfree = True
2977 port = self.options.port + self._portoffset
2978 port = self.options.port + self._portoffset
2978 for idx in xrange(portneeded):
2979 for idx in xrange(portneeded):
2979 if not checkportisavailable(port + idx):
2980 if not checkportisavailable(port + idx):
2980 allfree = False
2981 allfree = False
2981 break
2982 break
2982 self._portoffset += portneeded
2983 self._portoffset += portneeded
2983 if allfree:
2984 if allfree:
2984 break
2985 break
2985 self._ports[count] = port
2986 self._ports[count] = port
2986 return port
2987 return port
2987
2988
2988 def _gettest(self, testdesc, count):
2989 def _gettest(self, testdesc, count):
2989 """Obtain a Test by looking at its filename.
2990 """Obtain a Test by looking at its filename.
2990
2991
2991 Returns a Test instance. The Test may not be runnable if it doesn't
2992 Returns a Test instance. The Test may not be runnable if it doesn't
2992 map to a known type.
2993 map to a known type.
2993 """
2994 """
2994 path = testdesc['path']
2995 path = testdesc['path']
2995 lctest = path.lower()
2996 lctest = path.lower()
2996 testcls = Test
2997 testcls = Test
2997
2998
2998 for ext, cls in self.TESTTYPES:
2999 for ext, cls in self.TESTTYPES:
2999 if lctest.endswith(ext):
3000 if lctest.endswith(ext):
3000 testcls = cls
3001 testcls = cls
3001 break
3002 break
3002
3003
3003 refpath = os.path.join(getcwdb(), path)
3004 refpath = os.path.join(getcwdb(), path)
3004 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3005 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3005
3006
3006 # extra keyword parameters. 'case' is used by .t tests
3007 # extra keyword parameters. 'case' is used by .t tests
3007 kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
3008 kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
3008
3009
3009 t = testcls(refpath, self._outputdir, tmpdir,
3010 t = testcls(refpath, self._outputdir, tmpdir,
3010 keeptmpdir=self.options.keep_tmpdir,
3011 keeptmpdir=self.options.keep_tmpdir,
3011 debug=self.options.debug,
3012 debug=self.options.debug,
3012 first=self.options.first,
3013 first=self.options.first,
3013 timeout=self.options.timeout,
3014 timeout=self.options.timeout,
3014 startport=self._getport(count),
3015 startport=self._getport(count),
3015 extraconfigopts=self.options.extra_config_opt,
3016 extraconfigopts=self.options.extra_config_opt,
3016 py3warnings=self.options.py3_warnings,
3017 py3warnings=self.options.py3_warnings,
3017 shell=self.options.shell,
3018 shell=self.options.shell,
3018 hgcommand=self._hgcommand,
3019 hgcommand=self._hgcommand,
3019 usechg=bool(self.options.with_chg or self.options.chg),
3020 usechg=bool(self.options.with_chg or self.options.chg),
3020 useipv6=useipv6, **kwds)
3021 useipv6=useipv6, **kwds)
3021 t.should_reload = True
3022 t.should_reload = True
3022 return t
3023 return t
3023
3024
3024 def _cleanup(self):
3025 def _cleanup(self):
3025 """Clean up state from this test invocation."""
3026 """Clean up state from this test invocation."""
3026 if self.options.keep_tmpdir:
3027 if self.options.keep_tmpdir:
3027 return
3028 return
3028
3029
3029 vlog("# Cleaning up HGTMP", self._hgtmp)
3030 vlog("# Cleaning up HGTMP", self._hgtmp)
3030 shutil.rmtree(self._hgtmp, True)
3031 shutil.rmtree(self._hgtmp, True)
3031 for f in self._createdfiles:
3032 for f in self._createdfiles:
3032 try:
3033 try:
3033 os.remove(f)
3034 os.remove(f)
3034 except OSError:
3035 except OSError:
3035 pass
3036 pass
3036
3037
3037 def _usecorrectpython(self):
3038 def _usecorrectpython(self):
3038 """Configure the environment to use the appropriate Python in tests."""
3039 """Configure the environment to use the appropriate Python in tests."""
3039 # Tests must use the same interpreter as us or bad things will happen.
3040 # Tests must use the same interpreter as us or bad things will happen.
3040 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
3041 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
3041
3042
3042 # os.symlink() is a thing with py3 on Windows, but it requires
3043 # os.symlink() is a thing with py3 on Windows, but it requires
3043 # Administrator rights.
3044 # Administrator rights.
3044 if getattr(os, 'symlink', None) and os.name != 'nt':
3045 if getattr(os, 'symlink', None) and os.name != 'nt':
3045 vlog("# Making python executable in test path a symlink to '%s'" %
3046 vlog("# Making python executable in test path a symlink to '%s'" %
3046 sysexecutable)
3047 sysexecutable)
3047 mypython = os.path.join(self._tmpbindir, pyexename)
3048 mypython = os.path.join(self._tmpbindir, pyexename)
3048 try:
3049 try:
3049 if os.readlink(mypython) == sysexecutable:
3050 if os.readlink(mypython) == sysexecutable:
3050 return
3051 return
3051 os.unlink(mypython)
3052 os.unlink(mypython)
3052 except OSError as err:
3053 except OSError as err:
3053 if err.errno != errno.ENOENT:
3054 if err.errno != errno.ENOENT:
3054 raise
3055 raise
3055 if self._findprogram(pyexename) != sysexecutable:
3056 if self._findprogram(pyexename) != sysexecutable:
3056 try:
3057 try:
3057 os.symlink(sysexecutable, mypython)
3058 os.symlink(sysexecutable, mypython)
3058 self._createdfiles.append(mypython)
3059 self._createdfiles.append(mypython)
3059 except OSError as err:
3060 except OSError as err:
3060 # child processes may race, which is harmless
3061 # child processes may race, which is harmless
3061 if err.errno != errno.EEXIST:
3062 if err.errno != errno.EEXIST:
3062 raise
3063 raise
3063 else:
3064 else:
3064 exedir, exename = os.path.split(sysexecutable)
3065 exedir, exename = os.path.split(sysexecutable)
3065 vlog("# Modifying search path to find %s as %s in '%s'" %
3066 vlog("# Modifying search path to find %s as %s in '%s'" %
3066 (exename, pyexename, exedir))
3067 (exename, pyexename, exedir))
3067 path = os.environ['PATH'].split(os.pathsep)
3068 path = os.environ['PATH'].split(os.pathsep)
3068 while exedir in path:
3069 while exedir in path:
3069 path.remove(exedir)
3070 path.remove(exedir)
3070 os.environ['PATH'] = os.pathsep.join([exedir] + path)
3071 os.environ['PATH'] = os.pathsep.join([exedir] + path)
3071 if not self._findprogram(pyexename):
3072 if not self._findprogram(pyexename):
3072 print("WARNING: Cannot find %s in search path" % pyexename)
3073 print("WARNING: Cannot find %s in search path" % pyexename)
3073
3074
3074 def _installhg(self):
3075 def _installhg(self):
3075 """Install hg into the test environment.
3076 """Install hg into the test environment.
3076
3077
3077 This will also configure hg with the appropriate testing settings.
3078 This will also configure hg with the appropriate testing settings.
3078 """
3079 """
3079 vlog("# Performing temporary installation of HG")
3080 vlog("# Performing temporary installation of HG")
3080 installerrs = os.path.join(self._hgtmp, b"install.err")
3081 installerrs = os.path.join(self._hgtmp, b"install.err")
3081 compiler = ''
3082 compiler = ''
3082 if self.options.compiler:
3083 if self.options.compiler:
3083 compiler = '--compiler ' + self.options.compiler
3084 compiler = '--compiler ' + self.options.compiler
3084 if self.options.pure:
3085 if self.options.pure:
3085 pure = b"--pure"
3086 pure = b"--pure"
3086 else:
3087 else:
3087 pure = b""
3088 pure = b""
3088
3089
3089 # Run installer in hg root
3090 # Run installer in hg root
3090 script = os.path.realpath(sys.argv[0])
3091 script = os.path.realpath(sys.argv[0])
3091 exe = sysexecutable
3092 exe = sysexecutable
3092 if PYTHON3:
3093 if PYTHON3:
3093 compiler = _bytespath(compiler)
3094 compiler = _bytespath(compiler)
3094 script = _bytespath(script)
3095 script = _bytespath(script)
3095 exe = _bytespath(exe)
3096 exe = _bytespath(exe)
3096 hgroot = os.path.dirname(os.path.dirname(script))
3097 hgroot = os.path.dirname(os.path.dirname(script))
3097 self._hgroot = hgroot
3098 self._hgroot = hgroot
3098 os.chdir(hgroot)
3099 os.chdir(hgroot)
3099 nohome = b'--home=""'
3100 nohome = b'--home=""'
3100 if os.name == 'nt':
3101 if os.name == 'nt':
3101 # The --home="" trick works only on OS where os.sep == '/'
3102 # The --home="" trick works only on OS where os.sep == '/'
3102 # because of a distutils convert_path() fast-path. Avoid it at
3103 # because of a distutils convert_path() fast-path. Avoid it at
3103 # least on Windows for now, deal with .pydistutils.cfg bugs
3104 # least on Windows for now, deal with .pydistutils.cfg bugs
3104 # when they happen.
3105 # when they happen.
3105 nohome = b''
3106 nohome = b''
3106 cmd = (b'"%(exe)s" setup.py %(pure)s clean --all'
3107 cmd = (b'"%(exe)s" setup.py %(pure)s clean --all'
3107 b' build %(compiler)s --build-base="%(base)s"'
3108 b' build %(compiler)s --build-base="%(base)s"'
3108 b' install --force --prefix="%(prefix)s"'
3109 b' install --force --prefix="%(prefix)s"'
3109 b' --install-lib="%(libdir)s"'
3110 b' --install-lib="%(libdir)s"'
3110 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3111 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3111 % {b'exe': exe, b'pure': pure,
3112 % {b'exe': exe, b'pure': pure,
3112 b'compiler': compiler,
3113 b'compiler': compiler,
3113 b'base': os.path.join(self._hgtmp, b"build"),
3114 b'base': os.path.join(self._hgtmp, b"build"),
3114 b'prefix': self._installdir, b'libdir': self._pythondir,
3115 b'prefix': self._installdir, b'libdir': self._pythondir,
3115 b'bindir': self._bindir,
3116 b'bindir': self._bindir,
3116 b'nohome': nohome, b'logfile': installerrs})
3117 b'nohome': nohome, b'logfile': installerrs})
3117
3118
3118 # setuptools requires install directories to exist.
3119 # setuptools requires install directories to exist.
3119 def makedirs(p):
3120 def makedirs(p):
3120 try:
3121 try:
3121 os.makedirs(p)
3122 os.makedirs(p)
3122 except OSError as e:
3123 except OSError as e:
3123 if e.errno != errno.EEXIST:
3124 if e.errno != errno.EEXIST:
3124 raise
3125 raise
3125 makedirs(self._pythondir)
3126 makedirs(self._pythondir)
3126 makedirs(self._bindir)
3127 makedirs(self._bindir)
3127
3128
3128 vlog("# Running", cmd)
3129 vlog("# Running", cmd)
3129 if subprocess.call(_strpath(cmd), shell=True) == 0:
3130 if subprocess.call(_strpath(cmd), shell=True) == 0:
3130 if not self.options.verbose:
3131 if not self.options.verbose:
3131 try:
3132 try:
3132 os.remove(installerrs)
3133 os.remove(installerrs)
3133 except OSError as e:
3134 except OSError as e:
3134 if e.errno != errno.ENOENT:
3135 if e.errno != errno.ENOENT:
3135 raise
3136 raise
3136 else:
3137 else:
3137 with open(installerrs, 'rb') as f:
3138 with open(installerrs, 'rb') as f:
3138 for line in f:
3139 for line in f:
3139 if PYTHON3:
3140 if PYTHON3:
3140 sys.stdout.buffer.write(line)
3141 sys.stdout.buffer.write(line)
3141 else:
3142 else:
3142 sys.stdout.write(line)
3143 sys.stdout.write(line)
3143 sys.exit(1)
3144 sys.exit(1)
3144 os.chdir(self._testdir)
3145 os.chdir(self._testdir)
3145
3146
3146 self._usecorrectpython()
3147 self._usecorrectpython()
3147
3148
3148 if self.options.py3_warnings and not self.options.anycoverage:
3149 if self.options.py3_warnings and not self.options.anycoverage:
3149 vlog("# Updating hg command to enable Py3k Warnings switch")
3150 vlog("# Updating hg command to enable Py3k Warnings switch")
3150 with open(os.path.join(self._bindir, 'hg'), 'rb') as f:
3151 with open(os.path.join(self._bindir, 'hg'), 'rb') as f:
3151 lines = [line.rstrip() for line in f]
3152 lines = [line.rstrip() for line in f]
3152 lines[0] += ' -3'
3153 lines[0] += ' -3'
3153 with open(os.path.join(self._bindir, 'hg'), 'wb') as f:
3154 with open(os.path.join(self._bindir, 'hg'), 'wb') as f:
3154 for line in lines:
3155 for line in lines:
3155 f.write(line + '\n')
3156 f.write(line + '\n')
3156
3157
3157 hgbat = os.path.join(self._bindir, b'hg.bat')
3158 hgbat = os.path.join(self._bindir, b'hg.bat')
3158 if os.path.isfile(hgbat):
3159 if os.path.isfile(hgbat):
3159 # hg.bat expects to be put in bin/scripts while run-tests.py
3160 # hg.bat expects to be put in bin/scripts while run-tests.py
3160 # installation layout put it in bin/ directly. Fix it
3161 # installation layout put it in bin/ directly. Fix it
3161 with open(hgbat, 'rb') as f:
3162 with open(hgbat, 'rb') as f:
3162 data = f.read()
3163 data = f.read()
3163 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3164 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3164 data = data.replace(br'"%~dp0..\python" "%~dp0hg" %*',
3165 data = data.replace(br'"%~dp0..\python" "%~dp0hg" %*',
3165 b'"%~dp0python" "%~dp0hg" %*')
3166 b'"%~dp0python" "%~dp0hg" %*')
3166 with open(hgbat, 'wb') as f:
3167 with open(hgbat, 'wb') as f:
3167 f.write(data)
3168 f.write(data)
3168 else:
3169 else:
3169 print('WARNING: cannot fix hg.bat reference to python.exe')
3170 print('WARNING: cannot fix hg.bat reference to python.exe')
3170
3171
3171 if self.options.anycoverage:
3172 if self.options.anycoverage:
3172 custom = os.path.join(self._testdir, 'sitecustomize.py')
3173 custom = os.path.join(self._testdir, 'sitecustomize.py')
3173 target = os.path.join(self._pythondir, 'sitecustomize.py')
3174 target = os.path.join(self._pythondir, 'sitecustomize.py')
3174 vlog('# Installing coverage trigger to %s' % target)
3175 vlog('# Installing coverage trigger to %s' % target)
3175 shutil.copyfile(custom, target)
3176 shutil.copyfile(custom, target)
3176 rc = os.path.join(self._testdir, '.coveragerc')
3177 rc = os.path.join(self._testdir, '.coveragerc')
3177 vlog('# Installing coverage rc to %s' % rc)
3178 vlog('# Installing coverage rc to %s' % rc)
3178 os.environ['COVERAGE_PROCESS_START'] = rc
3179 os.environ['COVERAGE_PROCESS_START'] = rc
3179 covdir = os.path.join(self._installdir, '..', 'coverage')
3180 covdir = os.path.join(self._installdir, '..', 'coverage')
3180 try:
3181 try:
3181 os.mkdir(covdir)
3182 os.mkdir(covdir)
3182 except OSError as e:
3183 except OSError as e:
3183 if e.errno != errno.EEXIST:
3184 if e.errno != errno.EEXIST:
3184 raise
3185 raise
3185
3186
3186 os.environ['COVERAGE_DIR'] = covdir
3187 os.environ['COVERAGE_DIR'] = covdir
3187
3188
3188 def _checkhglib(self, verb):
3189 def _checkhglib(self, verb):
3189 """Ensure that the 'mercurial' package imported by python is
3190 """Ensure that the 'mercurial' package imported by python is
3190 the one we expect it to be. If not, print a warning to stderr."""
3191 the one we expect it to be. If not, print a warning to stderr."""
3191 if ((self._bindir == self._pythondir) and
3192 if ((self._bindir == self._pythondir) and
3192 (self._bindir != self._tmpbindir)):
3193 (self._bindir != self._tmpbindir)):
3193 # The pythondir has been inferred from --with-hg flag.
3194 # The pythondir has been inferred from --with-hg flag.
3194 # We cannot expect anything sensible here.
3195 # We cannot expect anything sensible here.
3195 return
3196 return
3196 expecthg = os.path.join(self._pythondir, b'mercurial')
3197 expecthg = os.path.join(self._pythondir, b'mercurial')
3197 actualhg = self._gethgpath()
3198 actualhg = self._gethgpath()
3198 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3199 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3199 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
3200 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
3200 ' (expected %s)\n'
3201 ' (expected %s)\n'
3201 % (verb, actualhg, expecthg))
3202 % (verb, actualhg, expecthg))
3202 def _gethgpath(self):
3203 def _gethgpath(self):
3203 """Return the path to the mercurial package that is actually found by
3204 """Return the path to the mercurial package that is actually found by
3204 the current Python interpreter."""
3205 the current Python interpreter."""
3205 if self._hgpath is not None:
3206 if self._hgpath is not None:
3206 return self._hgpath
3207 return self._hgpath
3207
3208
3208 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3209 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3209 cmd = cmd % PYTHON
3210 cmd = cmd % PYTHON
3210 if PYTHON3:
3211 if PYTHON3:
3211 cmd = _strpath(cmd)
3212 cmd = _strpath(cmd)
3212
3213
3213 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3214 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3214 out, err = p.communicate()
3215 out, err = p.communicate()
3215
3216
3216 self._hgpath = out.strip()
3217 self._hgpath = out.strip()
3217
3218
3218 return self._hgpath
3219 return self._hgpath
3219
3220
3220 def _installchg(self):
3221 def _installchg(self):
3221 """Install chg into the test environment"""
3222 """Install chg into the test environment"""
3222 vlog('# Performing temporary installation of CHG')
3223 vlog('# Performing temporary installation of CHG')
3223 assert os.path.dirname(self._bindir) == self._installdir
3224 assert os.path.dirname(self._bindir) == self._installdir
3224 assert self._hgroot, 'must be called after _installhg()'
3225 assert self._hgroot, 'must be called after _installhg()'
3225 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
3226 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
3226 % {b'make': b'make', # TODO: switch by option or environment?
3227 % {b'make': b'make', # TODO: switch by option or environment?
3227 b'prefix': self._installdir})
3228 b'prefix': self._installdir})
3228 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3229 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3229 vlog("# Running", cmd)
3230 vlog("# Running", cmd)
3230 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
3231 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
3231 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
3232 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
3232 stderr=subprocess.STDOUT)
3233 stderr=subprocess.STDOUT)
3233 out, _err = proc.communicate()
3234 out, _err = proc.communicate()
3234 if proc.returncode != 0:
3235 if proc.returncode != 0:
3235 if PYTHON3:
3236 if PYTHON3:
3236 sys.stdout.buffer.write(out)
3237 sys.stdout.buffer.write(out)
3237 else:
3238 else:
3238 sys.stdout.write(out)
3239 sys.stdout.write(out)
3239 sys.exit(1)
3240 sys.exit(1)
3240
3241
3241 def _outputcoverage(self):
3242 def _outputcoverage(self):
3242 """Produce code coverage output."""
3243 """Produce code coverage output."""
3243 import coverage
3244 import coverage
3244 coverage = coverage.coverage
3245 coverage = coverage.coverage
3245
3246
3246 vlog('# Producing coverage report')
3247 vlog('# Producing coverage report')
3247 # chdir is the easiest way to get short, relative paths in the
3248 # chdir is the easiest way to get short, relative paths in the
3248 # output.
3249 # output.
3249 os.chdir(self._hgroot)
3250 os.chdir(self._hgroot)
3250 covdir = os.path.join(self._installdir, '..', 'coverage')
3251 covdir = os.path.join(self._installdir, '..', 'coverage')
3251 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3252 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3252
3253
3253 # Map install directory paths back to source directory.
3254 # Map install directory paths back to source directory.
3254 cov.config.paths['srcdir'] = ['.', self._pythondir]
3255 cov.config.paths['srcdir'] = ['.', self._pythondir]
3255
3256
3256 cov.combine()
3257 cov.combine()
3257
3258
3258 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
3259 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
3259 cov.report(ignore_errors=True, omit=omit)
3260 cov.report(ignore_errors=True, omit=omit)
3260
3261
3261 if self.options.htmlcov:
3262 if self.options.htmlcov:
3262 htmldir = os.path.join(self._outputdir, 'htmlcov')
3263 htmldir = os.path.join(self._outputdir, 'htmlcov')
3263 cov.html_report(directory=htmldir, omit=omit)
3264 cov.html_report(directory=htmldir, omit=omit)
3264 if self.options.annotate:
3265 if self.options.annotate:
3265 adir = os.path.join(self._outputdir, 'annotated')
3266 adir = os.path.join(self._outputdir, 'annotated')
3266 if not os.path.isdir(adir):
3267 if not os.path.isdir(adir):
3267 os.mkdir(adir)
3268 os.mkdir(adir)
3268 cov.annotate(directory=adir, omit=omit)
3269 cov.annotate(directory=adir, omit=omit)
3269
3270
3270 def _findprogram(self, program):
3271 def _findprogram(self, program):
3271 """Search PATH for a executable program"""
3272 """Search PATH for a executable program"""
3272 dpb = _bytespath(os.defpath)
3273 dpb = _bytespath(os.defpath)
3273 sepb = _bytespath(os.pathsep)
3274 sepb = _bytespath(os.pathsep)
3274 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3275 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3275 name = os.path.join(p, program)
3276 name = os.path.join(p, program)
3276 if os.name == 'nt' or os.access(name, os.X_OK):
3277 if os.name == 'nt' or os.access(name, os.X_OK):
3277 return name
3278 return name
3278 return None
3279 return None
3279
3280
3280 def _checktools(self):
3281 def _checktools(self):
3281 """Ensure tools required to run tests are present."""
3282 """Ensure tools required to run tests are present."""
3282 for p in self.REQUIREDTOOLS:
3283 for p in self.REQUIREDTOOLS:
3283 if os.name == 'nt' and not p.endswith(b'.exe'):
3284 if os.name == 'nt' and not p.endswith(b'.exe'):
3284 p += b'.exe'
3285 p += b'.exe'
3285 found = self._findprogram(p)
3286 found = self._findprogram(p)
3286 if found:
3287 if found:
3287 vlog("# Found prerequisite", p, "at", found)
3288 vlog("# Found prerequisite", p, "at", found)
3288 else:
3289 else:
3289 print("WARNING: Did not find prerequisite tool: %s " %
3290 print("WARNING: Did not find prerequisite tool: %s " %
3290 p.decode("utf-8"))
3291 p.decode("utf-8"))
3291
3292
3292 def aggregateexceptions(path):
3293 def aggregateexceptions(path):
3293 exceptioncounts = collections.Counter()
3294 exceptioncounts = collections.Counter()
3294 testsbyfailure = collections.defaultdict(set)
3295 testsbyfailure = collections.defaultdict(set)
3295 failuresbytest = collections.defaultdict(set)
3296 failuresbytest = collections.defaultdict(set)
3296
3297
3297 for f in os.listdir(path):
3298 for f in os.listdir(path):
3298 with open(os.path.join(path, f), 'rb') as fh:
3299 with open(os.path.join(path, f), 'rb') as fh:
3299 data = fh.read().split(b'\0')
3300 data = fh.read().split(b'\0')
3300 if len(data) != 5:
3301 if len(data) != 5:
3301 continue
3302 continue
3302
3303
3303 exc, mainframe, hgframe, hgline, testname = data
3304 exc, mainframe, hgframe, hgline, testname = data
3304 exc = exc.decode('utf-8')
3305 exc = exc.decode('utf-8')
3305 mainframe = mainframe.decode('utf-8')
3306 mainframe = mainframe.decode('utf-8')
3306 hgframe = hgframe.decode('utf-8')
3307 hgframe = hgframe.decode('utf-8')
3307 hgline = hgline.decode('utf-8')
3308 hgline = hgline.decode('utf-8')
3308 testname = testname.decode('utf-8')
3309 testname = testname.decode('utf-8')
3309
3310
3310 key = (hgframe, hgline, exc)
3311 key = (hgframe, hgline, exc)
3311 exceptioncounts[key] += 1
3312 exceptioncounts[key] += 1
3312 testsbyfailure[key].add(testname)
3313 testsbyfailure[key].add(testname)
3313 failuresbytest[testname].add(key)
3314 failuresbytest[testname].add(key)
3314
3315
3315 # Find test having fewest failures for each failure.
3316 # Find test having fewest failures for each failure.
3316 leastfailing = {}
3317 leastfailing = {}
3317 for key, tests in testsbyfailure.items():
3318 for key, tests in testsbyfailure.items():
3318 fewesttest = None
3319 fewesttest = None
3319 fewestcount = 99999999
3320 fewestcount = 99999999
3320 for test in sorted(tests):
3321 for test in sorted(tests):
3321 if len(failuresbytest[test]) < fewestcount:
3322 if len(failuresbytest[test]) < fewestcount:
3322 fewesttest = test
3323 fewesttest = test
3323 fewestcount = len(failuresbytest[test])
3324 fewestcount = len(failuresbytest[test])
3324
3325
3325 leastfailing[key] = (fewestcount, fewesttest)
3326 leastfailing[key] = (fewestcount, fewesttest)
3326
3327
3327 # Create a combined counter so we can sort by total occurrences and
3328 # Create a combined counter so we can sort by total occurrences and
3328 # impacted tests.
3329 # impacted tests.
3329 combined = {}
3330 combined = {}
3330 for key in exceptioncounts:
3331 for key in exceptioncounts:
3331 combined[key] = (exceptioncounts[key],
3332 combined[key] = (exceptioncounts[key],
3332 len(testsbyfailure[key]),
3333 len(testsbyfailure[key]),
3333 leastfailing[key][0],
3334 leastfailing[key][0],
3334 leastfailing[key][1])
3335 leastfailing[key][1])
3335
3336
3336 return {
3337 return {
3337 'exceptioncounts': exceptioncounts,
3338 'exceptioncounts': exceptioncounts,
3338 'total': sum(exceptioncounts.values()),
3339 'total': sum(exceptioncounts.values()),
3339 'combined': combined,
3340 'combined': combined,
3340 'leastfailing': leastfailing,
3341 'leastfailing': leastfailing,
3341 'byfailure': testsbyfailure,
3342 'byfailure': testsbyfailure,
3342 'bytest': failuresbytest,
3343 'bytest': failuresbytest,
3343 }
3344 }
3344
3345
3345 if __name__ == '__main__':
3346 if __name__ == '__main__':
3346 runner = TestRunner()
3347 runner = TestRunner()
3347
3348
3348 try:
3349 try:
3349 import msvcrt
3350 import msvcrt
3350 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3351 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3351 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3352 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3352 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3353 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3353 except ImportError:
3354 except ImportError:
3354 pass
3355 pass
3355
3356
3356 sys.exit(runner.run(sys.argv[1:]))
3357 sys.exit(runner.run(sys.argv[1:]))
@@ -1,199 +1,199 b''
1 A new repository uses zlib storage, which doesn't need a requirement
1 A new repository uses zlib storage, which doesn't need a requirement
2
2
3 $ hg init default
3 $ hg init default
4 $ cd default
4 $ cd default
5 $ cat .hg/requires
5 $ cat .hg/requires
6 dotencode
6 dotencode
7 fncache
7 fncache
8 generaldelta
8 generaldelta
9 revlogv1
9 revlogv1
10 sparserevlog
10 sparserevlog
11 store
11 store
12 testonly-simplestore (reposimplestore !)
12 testonly-simplestore (reposimplestore !)
13
13
14 $ touch foo
14 $ touch foo
15 $ hg -q commit -A -m 'initial commit with a lot of repeated repeated repeated text to trigger compression'
15 $ hg -q commit -A -m 'initial commit with a lot of repeated repeated repeated text to trigger compression'
16 $ hg debugrevlog -c | grep 0x78
16 $ hg debugrevlog -c | grep 0x78
17 0x78 (x) : 1 (100.00%)
17 0x78 (x) : 1 (100.00%)
18 0x78 (x) : 110 (100.00%)
18 0x78 (x) : 110 (100.00%)
19
19
20 $ cd ..
20 $ cd ..
21
21
22 Unknown compression engine to format.compression aborts
22 Unknown compression engine to format.compression aborts
23
23
24 $ hg --config format.revlog-compression=unknown init unknown
24 $ hg --config format.revlog-compression=unknown init unknown
25 abort: compression engine unknown defined by format.revlog-compression not available
25 abort: compression engine unknown defined by format.revlog-compression not available
26 (run "hg debuginstall" to list available compression engines)
26 (run "hg debuginstall" to list available compression engines)
27 [255]
27 [255]
28
28
29 A requirement specifying an unknown compression engine results in bail
29 A requirement specifying an unknown compression engine results in bail
30
30
31 $ hg init unknownrequirement
31 $ hg init unknownrequirement
32 $ cd unknownrequirement
32 $ cd unknownrequirement
33 $ echo exp-compression-unknown >> .hg/requires
33 $ echo exp-compression-unknown >> .hg/requires
34 $ hg log
34 $ hg log
35 abort: repository requires features unknown to this Mercurial: exp-compression-unknown!
35 abort: repository requires features unknown to this Mercurial: exp-compression-unknown!
36 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
36 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
37 [255]
37 [255]
38
38
39 $ cd ..
39 $ cd ..
40
40
41 #if zstd
41 #if zstd
42
42
43 $ hg --config format.revlog-compression=zstd init zstd
43 $ hg --config format.revlog-compression=zstd init zstd
44 $ cd zstd
44 $ cd zstd
45 $ cat .hg/requires
45 $ cat .hg/requires
46 dotencode
46 dotencode
47 fncache
47 fncache
48 generaldelta
48 generaldelta
49 revlog-compression-zstd
49 revlog-compression-zstd
50 revlogv1
50 revlogv1
51 sparserevlog
51 sparserevlog
52 store
52 store
53 testonly-simplestore (reposimplestore !)
53 testonly-simplestore (reposimplestore !)
54
54
55 $ touch foo
55 $ touch foo
56 $ hg -q commit -A -m 'initial commit with a lot of repeated repeated repeated text'
56 $ hg -q commit -A -m 'initial commit with a lot of repeated repeated repeated text'
57
57
58 $ hg debugrevlog -c | grep 0x28
58 $ hg debugrevlog -c | grep 0x28
59 0x28 : 1 (100.00%)
59 0x28 : 1 (100.00%)
60 0x28 : 98 (100.00%)
60 0x28 : 98 (100.00%)
61
61
62 $ cd ..
62 $ cd ..
63
63
64 Specifying a new format.compression on an existing repo won't introduce data
64 Specifying a new format.compression on an existing repo won't introduce data
65 with that engine or a requirement
65 with that engine or a requirement
66
66
67 $ cd default
67 $ cd default
68 $ touch bar
68 $ touch bar
69 $ hg --config format.revlog-compression=zstd -q commit -A -m 'add bar with a lot of repeated repeated repeated text'
69 $ hg --config format.revlog-compression=zstd -q commit -A -m 'add bar with a lot of repeated repeated repeated text'
70
70
71 $ cat .hg/requires
71 $ cat .hg/requires
72 dotencode
72 dotencode
73 fncache
73 fncache
74 generaldelta
74 generaldelta
75 revlogv1
75 revlogv1
76 sparserevlog
76 sparserevlog
77 store
77 store
78 testonly-simplestore (reposimplestore !)
78 testonly-simplestore (reposimplestore !)
79
79
80 $ hg debugrevlog -c | grep 0x78
80 $ hg debugrevlog -c | grep 0x78
81 0x78 (x) : 2 (100.00%)
81 0x78 (x) : 2 (100.00%)
82 0x78 (x) : 199 (100.00%)
82 0x78 (x) : 199 (100.00%)
83
83
84 #endif
84 #endif
85
85
86 checking zlib options
86 checking zlib options
87 =====================
87 =====================
88
88
89 $ hg init zlib-level-default
89 $ hg init zlib-level-default
90 $ hg init zlib-level-1
90 $ hg init zlib-level-1
91 $ cat << EOF >> zlib-level-1/.hg/hgrc
91 $ cat << EOF >> zlib-level-1/.hg/hgrc
92 > [storage]
92 > [storage]
93 > revlog.zlib.level=1
93 > revlog.zlib.level=1
94 > EOF
94 > EOF
95 $ hg init zlib-level-9
95 $ hg init zlib-level-9
96 $ cat << EOF >> zlib-level-9/.hg/hgrc
96 $ cat << EOF >> zlib-level-9/.hg/hgrc
97 > [storage]
97 > [storage]
98 > revlog.zlib.level=9
98 > revlog.zlib.level=9
99 > EOF
99 > EOF
100
100
101
101
102 $ commitone() {
102 $ commitone() {
103 > repo=$1
103 > repo=$1
104 > cp $RUNTESTDIR/bundles/issue4438-r1.hg $repo/a
104 > cp $RUNTESTDIR/bundles/issue4438-r1.hg $repo/a
105 > hg -R $repo add $repo/a
105 > hg -R $repo add $repo/a
106 > hg -R $repo commit -m some-commit
106 > hg -R $repo commit -m some-commit
107 > }
107 > }
108
108
109 $ for repo in zlib-level-default zlib-level-1 zlib-level-9; do
109 $ for repo in zlib-level-default zlib-level-1 zlib-level-9; do
110 > commitone $repo
110 > commitone $repo
111 > done
111 > done
112
112
113 $ $RUNTESTDIR/f -s */.hg/store/data/*
113 $ $RUNTESTDIR/f -s */.hg/store/data/*
114 default/.hg/store/data/foo.i: size=64 (pure !)
114 default/.hg/store/data/foo.i: size=64 (pure !)
115 zlib-level-1/.hg/store/data/a.i: size=4146
115 zlib-level-1/.hg/store/data/a.i: size=4146
116 zlib-level-9/.hg/store/data/a.i: size=4138
116 zlib-level-9/.hg/store/data/a.i: size=4138
117 zlib-level-default/.hg/store/data/a.i: size=4138
117 zlib-level-default/.hg/store/data/a.i: size=4138
118
118
119 Test error cases
119 Test error cases
120
120
121 $ hg init zlib-level-invalid
121 $ hg init zlib-level-invalid
122 $ cat << EOF >> zlib-level-invalid/.hg/hgrc
122 $ cat << EOF >> zlib-level-invalid/.hg/hgrc
123 > [storage]
123 > [storage]
124 > revlog.zlib.level=foobar
124 > revlog.zlib.level=foobar
125 > EOF
125 > EOF
126 $ commitone zlib-level-invalid
126 $ commitone zlib-level-invalid
127 abort: storage.revlog.zlib.level is not a valid integer ('foobar')
127 abort: storage.revlog.zlib.level is not a valid integer ('foobar')
128 abort: storage.revlog.zlib.level is not a valid integer ('foobar')
128 abort: storage.revlog.zlib.level is not a valid integer ('foobar')
129 [255]
129 [255]
130
130
131 $ hg init zlib-level-out-of-range
131 $ hg init zlib-level-out-of-range
132 $ cat << EOF >> zlib-level-out-of-range/.hg/hgrc
132 $ cat << EOF >> zlib-level-out-of-range/.hg/hgrc
133 > [storage]
133 > [storage]
134 > revlog.zlib.level=42
134 > revlog.zlib.level=42
135 > EOF
135 > EOF
136
136
137 $ commitone zlib-level-out-of-range
137 $ commitone zlib-level-out-of-range
138 abort: invalid value for `storage.revlog.zlib.level` config: 42
138 abort: invalid value for `storage.revlog.zlib.level` config: 42
139 abort: invalid value for `storage.revlog.zlib.level` config: 42
139 abort: invalid value for `storage.revlog.zlib.level` config: 42
140 [255]
140 [255]
141
141
142 #if zstd
142 #if zstd
143
143
144 checking zstd options
144 checking zstd options
145 =====================
145 =====================
146
146
147 $ hg init zstd-level-default --config format.revlog-compression=zstd
147 $ hg init zstd-level-default --config format.revlog-compression=zstd
148 $ hg init zstd-level-1 --config format.revlog-compression=zstd
148 $ hg init zstd-level-1 --config format.revlog-compression=zstd
149 $ cat << EOF >> zstd-level-1/.hg/hgrc
149 $ cat << EOF >> zstd-level-1/.hg/hgrc
150 > [storage]
150 > [storage]
151 > revlog.zstd.level=1
151 > revlog.zstd.level=1
152 > EOF
152 > EOF
153 $ hg init zstd-level-22 --config format.revlog-compression=zstd
153 $ hg init zstd-level-22 --config format.revlog-compression=zstd
154 $ cat << EOF >> zstd-level-22/.hg/hgrc
154 $ cat << EOF >> zstd-level-22/.hg/hgrc
155 > [storage]
155 > [storage]
156 > revlog.zstd.level=22
156 > revlog.zstd.level=22
157 > EOF
157 > EOF
158
158
159
159
160 $ commitone() {
160 $ commitone() {
161 > repo=$1
161 > repo=$1
162 > cp $RUNTESTDIR/bundles/issue4438-r1.hg $repo/a
162 > cp $RUNTESTDIR/bundles/issue4438-r1.hg $repo/a
163 > hg -R $repo add $repo/a
163 > hg -R $repo add $repo/a
164 > hg -R $repo commit -m some-commit
164 > hg -R $repo commit -m some-commit
165 > }
165 > }
166
166
167 $ for repo in zstd-level-default zstd-level-1 zstd-level-22; do
167 $ for repo in zstd-level-default zstd-level-1 zstd-level-22; do
168 > commitone $repo
168 > commitone $repo
169 > done
169 > done
170
170
171 $ $RUNTESTDIR/f -s zstd-*/.hg/store/data/*
171 $ $RUNTESTDIR/f -s zstd-*/.hg/store/data/*
172 zstd-level-1/.hg/store/data/a.i: size=4097
172 zstd-level-1/.hg/store/data/a.i: size=4097
173 zstd-level-22/.hg/store/data/a.i: size=4091
173 zstd-level-22/.hg/store/data/a.i: size=4091
174 zstd-level-default/.hg/store/data/a.i: size=4094
174 zstd-level-default/\.hg/store/data/a\.i: size=(4094|4102) (re)
175
175
176 Test error cases
176 Test error cases
177
177
178 $ hg init zstd-level-invalid --config format.revlog-compression=zstd
178 $ hg init zstd-level-invalid --config format.revlog-compression=zstd
179 $ cat << EOF >> zstd-level-invalid/.hg/hgrc
179 $ cat << EOF >> zstd-level-invalid/.hg/hgrc
180 > [storage]
180 > [storage]
181 > revlog.zstd.level=foobar
181 > revlog.zstd.level=foobar
182 > EOF
182 > EOF
183 $ commitone zstd-level-invalid
183 $ commitone zstd-level-invalid
184 abort: storage.revlog.zstd.level is not a valid integer ('foobar')
184 abort: storage.revlog.zstd.level is not a valid integer ('foobar')
185 abort: storage.revlog.zstd.level is not a valid integer ('foobar')
185 abort: storage.revlog.zstd.level is not a valid integer ('foobar')
186 [255]
186 [255]
187
187
188 $ hg init zstd-level-out-of-range --config format.revlog-compression=zstd
188 $ hg init zstd-level-out-of-range --config format.revlog-compression=zstd
189 $ cat << EOF >> zstd-level-out-of-range/.hg/hgrc
189 $ cat << EOF >> zstd-level-out-of-range/.hg/hgrc
190 > [storage]
190 > [storage]
191 > revlog.zstd.level=42
191 > revlog.zstd.level=42
192 > EOF
192 > EOF
193
193
194 $ commitone zstd-level-out-of-range
194 $ commitone zstd-level-out-of-range
195 abort: invalid value for `storage.revlog.zstd.level` config: 42
195 abort: invalid value for `storage.revlog.zstd.level` config: 42
196 abort: invalid value for `storage.revlog.zstd.level` config: 42
196 abort: invalid value for `storage.revlog.zstd.level` config: 42
197 [255]
197 [255]
198
198
199 #endif
199 #endif
General Comments 0
You need to be logged in to leave comments. Login now