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

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

@@ -1,255 +1,256 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,271 +1,272 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
199 a4e32fd539ab41489a51b2aa88bda9a73b839562 5.1.1
200 181e52f2b62f4768aa0d988936c929dc7c4a41a0 5.1.2
200 181e52f2b62f4768aa0d988936c929dc7c4a41a0 5.1.2
201 59338f9561099de77c684c00f76507f11e46ebe8 5.2rc0
201 59338f9561099de77c684c00f76507f11e46ebe8 5.2rc0
202 ca3dca416f8d5863ca6f5a4a6a6bb835dcd5feeb 5.2
202 ca3dca416f8d5863ca6f5a4a6a6bb835dcd5feeb 5.2
203 a50fecefa691c9b72a99e49aa6fe9dd13943c2bf 5.2.1
203 a50fecefa691c9b72a99e49aa6fe9dd13943c2bf 5.2.1
204 b4c82b70418022e67cc0e69b1aa3c3aa43aa1d29 5.2.2
204 b4c82b70418022e67cc0e69b1aa3c3aa43aa1d29 5.2.2
205 84a0102c05c7852c8215ef6cf21d809927586b69 5.3rc0
205 84a0102c05c7852c8215ef6cf21d809927586b69 5.3rc0
206 e4344e463c0c888a2f437b78b5982ecdf3f6650a 5.3rc1
206 e4344e463c0c888a2f437b78b5982ecdf3f6650a 5.3rc1
207 7f5410dfc8a64bb587d19637deb95d378fd1eb5c 5.3
207 7f5410dfc8a64bb587d19637deb95d378fd1eb5c 5.3
208 6d121acbb82e65fe4dd3c2318a1b61981b958492 5.3.1
208 6d121acbb82e65fe4dd3c2318a1b61981b958492 5.3.1
209 8fca7e8449a847e3cf1054f2c07b51237699fad3 5.3.2
209 8fca7e8449a847e3cf1054f2c07b51237699fad3 5.3.2
210 26ce8e7515036d3431a03aaeb7bc72dd96cb1112 5.4rc0
210 26ce8e7515036d3431a03aaeb7bc72dd96cb1112 5.4rc0
211 cf3e07d7648a4371ce584d15dd692e7a6845792f 5.4
211 cf3e07d7648a4371ce584d15dd692e7a6845792f 5.4
212 065704cbdbdbb05dcd6bb814eb9bbdd982211b28 5.4.1
212 065704cbdbdbb05dcd6bb814eb9bbdd982211b28 5.4.1
213 0ea9c86fac8974cd74dc12ea681c8986eb6da6c4 5.4.2
213 0ea9c86fac8974cd74dc12ea681c8986eb6da6c4 5.4.2
214 28163c5de797e5416f9b588940f4608269b4d50a 5.5rc0
214 28163c5de797e5416f9b588940f4608269b4d50a 5.5rc0
215 7fc3c5fbc65f6fe85d70ea63923b8767dda4f2e0 5.5
215 7fc3c5fbc65f6fe85d70ea63923b8767dda4f2e0 5.5
216 f62bb5d07848ca598aa860a517394130b61bf2ee 5.5.1
216 f62bb5d07848ca598aa860a517394130b61bf2ee 5.5.1
217 07731064ac41dacdf0ec869ebd05c2e848c14fbf 5.5.2
217 07731064ac41dacdf0ec869ebd05c2e848c14fbf 5.5.2
218 0e06a7ab9e0d5c65af4e511aee1e0342998799df 5.6rc0
218 0e06a7ab9e0d5c65af4e511aee1e0342998799df 5.6rc0
219 18c17d63fdabd009e70bf994e5efb7db422f4f7f 5.6
219 18c17d63fdabd009e70bf994e5efb7db422f4f7f 5.6
220 1d5189a57405ceca5aa244052c9f948977f4699b 5.6.1
220 1d5189a57405ceca5aa244052c9f948977f4699b 5.6.1
221 9da65e3cf3706ff41e08b311381c588440c27baf 5.7rc0
221 9da65e3cf3706ff41e08b311381c588440c27baf 5.7rc0
222 0e2e7300f4302b02412b0b734717697049494c4c 5.7
222 0e2e7300f4302b02412b0b734717697049494c4c 5.7
223 d5d9177c0045d206db575bae6daa98e2cb2fe5bc 5.7.1
223 d5d9177c0045d206db575bae6daa98e2cb2fe5bc 5.7.1
224 f67b8946bb1b6cfa8328dbf8d6a9128b69ccdcb4 5.8rc0
224 f67b8946bb1b6cfa8328dbf8d6a9128b69ccdcb4 5.8rc0
225 8d2b62d716b095507effaa8d56f87cd27ba659ab 5.8rc1
225 8d2b62d716b095507effaa8d56f87cd27ba659ab 5.8rc1
226 067f2c53fb24506c9e9fb4639871b13b19a85f8a 5.8
226 067f2c53fb24506c9e9fb4639871b13b19a85f8a 5.8
227 411dc27fd9fd076d6a031a08fcaace659afe2fe3 5.8.1
227 411dc27fd9fd076d6a031a08fcaace659afe2fe3 5.8.1
228 d7515d29761d5ada7d9c765f517db67db75dea9a 5.9rc0
228 d7515d29761d5ada7d9c765f517db67db75dea9a 5.9rc0
229 2813d406b03607cdb8c06cb04c44efcc9a79d9a2 5.9rc1
229 2813d406b03607cdb8c06cb04c44efcc9a79d9a2 5.9rc1
230 53221078e0de65d1a821ce5311dec45a7a978301 5.9
230 53221078e0de65d1a821ce5311dec45a7a978301 5.9
231 86a60679cf619e14cee9442f865fcf31b142cb9f 5.9.1
231 86a60679cf619e14cee9442f865fcf31b142cb9f 5.9.1
232 750920b18aaaddd654756be40dec59d90f2643be 5.9.2
232 750920b18aaaddd654756be40dec59d90f2643be 5.9.2
233 6ee0244fc1cf889ae543d2ce0ec45201ae0be6e1 5.9.3
233 6ee0244fc1cf889ae543d2ce0ec45201ae0be6e1 5.9.3
234 a44bb185f6bdbecc754996d8386722e2f0123b0a 6.0rc0
234 a44bb185f6bdbecc754996d8386722e2f0123b0a 6.0rc0
235 5d08b289e2e526259d7d5ea32b70fe76d5b327d7 6.0
235 5d08b289e2e526259d7d5ea32b70fe76d5b327d7 6.0
236 799fdf4cca80cb9ae40537a90995e6bd163ebc0b 6.0.1
236 799fdf4cca80cb9ae40537a90995e6bd163ebc0b 6.0.1
237 75676122c2bf7594ac732b7388db4c74c648b365 6.0.2
237 75676122c2bf7594ac732b7388db4c74c648b365 6.0.2
238 dcec16e799ddb6d33fcd11b04af530250a417a58 6.0.3
238 dcec16e799ddb6d33fcd11b04af530250a417a58 6.0.3
239 c00d3ce4e94bb0ee8d809e25e1dcb2a5fab84e2c 6.1rc0
239 c00d3ce4e94bb0ee8d809e25e1dcb2a5fab84e2c 6.1rc0
240 d4486810a1795fba9521449b8885ced034f3a6dd 6.1
240 d4486810a1795fba9521449b8885ced034f3a6dd 6.1
241 5bd6bcd31dd1ebb63b8914b00064f96297267af7 6.1.1
241 5bd6bcd31dd1ebb63b8914b00064f96297267af7 6.1.1
242 0ddd5e1f5f67438af85d12e4ce6c39021dde9916 6.1.2
242 0ddd5e1f5f67438af85d12e4ce6c39021dde9916 6.1.2
243 6b10151b962108f65bfa12b3918b1021ca334f73 6.1.3
243 6b10151b962108f65bfa12b3918b1021ca334f73 6.1.3
244 0cc5f74ff7f0f4ac2427096bddbe102dbc2453ae 6.1.4
244 0cc5f74ff7f0f4ac2427096bddbe102dbc2453ae 6.1.4
245 288de6f5d724bba7bf1669e2838f196962bb7528 6.2rc0
245 288de6f5d724bba7bf1669e2838f196962bb7528 6.2rc0
246 094a5fa3cf52f936e0de3f1e507c818bee5ece6b 6.2
246 094a5fa3cf52f936e0de3f1e507c818bee5ece6b 6.2
247 f69bffd00abe3a1b94d1032eb2c92e611d16a192 6.2.1
247 f69bffd00abe3a1b94d1032eb2c92e611d16a192 6.2.1
248 b5c8524827d20fe2e0ca8fb1234a0fe35a1a36c7 6.2.2
248 b5c8524827d20fe2e0ca8fb1234a0fe35a1a36c7 6.2.2
249 dbdee8ac3e3fcdda1fa55b90c0a235125b7f8e6f 6.2.3
249 dbdee8ac3e3fcdda1fa55b90c0a235125b7f8e6f 6.2.3
250 a3356ab610fc50000cf0ba55c424a4d96da11db7 6.3rc0
250 a3356ab610fc50000cf0ba55c424a4d96da11db7 6.3rc0
251 04f1dba53c961dfdb875c8469adc96fa999cfbed 6.3.0
251 04f1dba53c961dfdb875c8469adc96fa999cfbed 6.3.0
252 04f1dba53c961dfdb875c8469adc96fa999cfbed 6.3
252 04f1dba53c961dfdb875c8469adc96fa999cfbed 6.3
253 04f1dba53c961dfdb875c8469adc96fa999cfbed 6.3.0
253 04f1dba53c961dfdb875c8469adc96fa999cfbed 6.3.0
254 0000000000000000000000000000000000000000 6.3.0
254 0000000000000000000000000000000000000000 6.3.0
255 c890d8b8bc59b18e5febf60caada629df5356ee2 6.3.1
255 c890d8b8bc59b18e5febf60caada629df5356ee2 6.3.1
256 59466b13a3ae0e29a5d4f485393e516cfbb057d0 6.3.2
256 59466b13a3ae0e29a5d4f485393e516cfbb057d0 6.3.2
257 8830004967ad865ead89c28a410405a6e71e0796 6.3.3
257 8830004967ad865ead89c28a410405a6e71e0796 6.3.3
258 05de4896508e8ec387b33eb30d8aab78d1c8e9e4 6.4rc0
258 05de4896508e8ec387b33eb30d8aab78d1c8e9e4 6.4rc0
259 f14864fffdcab725d9eac6d4f4c07be05a35f59a 6.4
259 f14864fffdcab725d9eac6d4f4c07be05a35f59a 6.4
260 83ea6ce48b4fd09fb79c4e34cc5750c805699a53 6.4.1
260 83ea6ce48b4fd09fb79c4e34cc5750c805699a53 6.4.1
261 f952be90b0514a576dcc8bbe758ce3847faba9bb 6.4.2
261 f952be90b0514a576dcc8bbe758ce3847faba9bb 6.4.2
262 fc445f8abcf90b33db7c463816a1b3560681767f 6.4.3
262 fc445f8abcf90b33db7c463816a1b3560681767f 6.4.3
263 da372c745e0f053bb7a64e74cccd15810d96341d 6.4.4
263 da372c745e0f053bb7a64e74cccd15810d96341d 6.4.4
264 271a4ab29605ffa0bae5d3208eaa21a95427ff92 6.4.5
264 271a4ab29605ffa0bae5d3208eaa21a95427ff92 6.4.5
265 bb42988c7e156931b0ff1e93732b98173ebbcb7f 6.5rc0
265 bb42988c7e156931b0ff1e93732b98173ebbcb7f 6.5rc0
266 3ffc7209bbae5804a53084c9dc2d41139e88c867 6.5
266 3ffc7209bbae5804a53084c9dc2d41139e88c867 6.5
267 787af4e0e8b787e1b77a8059926b123730a4cd01 6.5.1
267 787af4e0e8b787e1b77a8059926b123730a4cd01 6.5.1
268 5a8b5420103937fca97c584c5162178eed828ada 6.5.2
268 5a8b5420103937fca97c584c5162178eed828ada 6.5.2
269 c083d9776cb2fb6056715b2988d1ea48055f3162 6.5.3
269 c083d9776cb2fb6056715b2988d1ea48055f3162 6.5.3
270 27055614b68538576fb0439007009acf93fe0a49 6.6rc0
270 27055614b68538576fb0439007009acf93fe0a49 6.6rc0
271 26c57e7a0890b96e2c473b394de380d6753c9230 6.6
271 26c57e7a0890b96e2c473b394de380d6753c9230 6.6
272 71bd09bebbe36a09569cbfb388f371433360056b 6.6.1
@@ -1,4638 +1,4637 b''
1 # perf.py - performance test routines
1 # perf.py - performance test routines
2 '''helper extension to measure performance
2 '''helper extension to measure performance
3
3
4 Configurations
4 Configurations
5 ==============
5 ==============
6
6
7 ``perf``
7 ``perf``
8 --------
8 --------
9
9
10 ``all-timing``
10 ``all-timing``
11 When set, additional statistics will be reported for each benchmark: best,
11 When set, additional statistics will be reported for each benchmark: best,
12 worst, median average. If not set only the best timing is reported
12 worst, median average. If not set only the best timing is reported
13 (default: off).
13 (default: off).
14
14
15 ``presleep``
15 ``presleep``
16 number of second to wait before any group of runs (default: 1)
16 number of second to wait before any group of runs (default: 1)
17
17
18 ``pre-run``
18 ``pre-run``
19 number of run to perform before starting measurement.
19 number of run to perform before starting measurement.
20
20
21 ``profile-benchmark``
21 ``profile-benchmark``
22 Enable profiling for the benchmarked section.
22 Enable profiling for the benchmarked section.
23 (The first iteration is benchmarked)
23 (The first iteration is benchmarked)
24
24
25 ``run-limits``
25 ``run-limits``
26 Control the number of runs each benchmark will perform. The option value
26 Control the number of runs each benchmark will perform. The option value
27 should be a list of `<time>-<numberofrun>` pairs. After each run the
27 should be a list of `<time>-<numberofrun>` pairs. After each run the
28 conditions are considered in order with the following logic:
28 conditions are considered in order with the following logic:
29
29
30 If benchmark has been running for <time> seconds, and we have performed
30 If benchmark has been running for <time> seconds, and we have performed
31 <numberofrun> iterations, stop the benchmark,
31 <numberofrun> iterations, stop the benchmark,
32
32
33 The default value is: `3.0-100, 10.0-3`
33 The default value is: `3.0-100, 10.0-3`
34
34
35 ``stub``
35 ``stub``
36 When set, benchmarks will only be run once, useful for testing
36 When set, benchmarks will only be run once, useful for testing
37 (default: off)
37 (default: off)
38 '''
38 '''
39
39
40 # "historical portability" policy of perf.py:
40 # "historical portability" policy of perf.py:
41 #
41 #
42 # We have to do:
42 # We have to do:
43 # - make perf.py "loadable" with as wide Mercurial version as possible
43 # - make perf.py "loadable" with as wide Mercurial version as possible
44 # This doesn't mean that perf commands work correctly with that Mercurial.
44 # This doesn't mean that perf commands work correctly with that Mercurial.
45 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
45 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
46 # - make historical perf command work correctly with as wide Mercurial
46 # - make historical perf command work correctly with as wide Mercurial
47 # version as possible
47 # version as possible
48 #
48 #
49 # We have to do, if possible with reasonable cost:
49 # We have to do, if possible with reasonable cost:
50 # - make recent perf command for historical feature work correctly
50 # - make recent perf command for historical feature work correctly
51 # with early Mercurial
51 # with early Mercurial
52 #
52 #
53 # We don't have to do:
53 # We don't have to do:
54 # - make perf command for recent feature work correctly with early
54 # - make perf command for recent feature work correctly with early
55 # Mercurial
55 # Mercurial
56
56
57 import contextlib
57 import contextlib
58 import functools
58 import functools
59 import gc
59 import gc
60 import os
60 import os
61 import random
61 import random
62 import shutil
62 import shutil
63 import struct
63 import struct
64 import sys
64 import sys
65 import tempfile
65 import tempfile
66 import threading
66 import threading
67 import time
67 import time
68
68
69 import mercurial.revlog
69 import mercurial.revlog
70 from mercurial import (
70 from mercurial import (
71 changegroup,
71 changegroup,
72 cmdutil,
72 cmdutil,
73 commands,
73 commands,
74 copies,
74 copies,
75 error,
75 error,
76 extensions,
76 extensions,
77 hg,
77 hg,
78 mdiff,
78 mdiff,
79 merge,
79 merge,
80 util,
80 util,
81 )
81 )
82
82
83 # for "historical portability":
83 # for "historical portability":
84 # try to import modules separately (in dict order), and ignore
84 # try to import modules separately (in dict order), and ignore
85 # failure, because these aren't available with early Mercurial
85 # failure, because these aren't available with early Mercurial
86 try:
86 try:
87 from mercurial import branchmap # since 2.5 (or bcee63733aad)
87 from mercurial import branchmap # since 2.5 (or bcee63733aad)
88 except ImportError:
88 except ImportError:
89 pass
89 pass
90 try:
90 try:
91 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
91 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
92 except ImportError:
92 except ImportError:
93 pass
93 pass
94 try:
94 try:
95 from mercurial import registrar # since 3.7 (or 37d50250b696)
95 from mercurial import registrar # since 3.7 (or 37d50250b696)
96
96
97 dir(registrar) # forcibly load it
97 dir(registrar) # forcibly load it
98 except ImportError:
98 except ImportError:
99 registrar = None
99 registrar = None
100 try:
100 try:
101 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
101 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
102 except ImportError:
102 except ImportError:
103 pass
103 pass
104 try:
104 try:
105 from mercurial.utils import repoviewutil # since 5.0
105 from mercurial.utils import repoviewutil # since 5.0
106 except ImportError:
106 except ImportError:
107 repoviewutil = None
107 repoviewutil = None
108 try:
108 try:
109 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
109 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
110 except ImportError:
110 except ImportError:
111 pass
111 pass
112 try:
112 try:
113 from mercurial import setdiscovery # since 1.9 (or cb98fed52495)
113 from mercurial import setdiscovery # since 1.9 (or cb98fed52495)
114 except ImportError:
114 except ImportError:
115 pass
115 pass
116
116
117 try:
117 try:
118 from mercurial import profiling
118 from mercurial import profiling
119 except ImportError:
119 except ImportError:
120 profiling = None
120 profiling = None
121
121
122 try:
122 try:
123 from mercurial.revlogutils import constants as revlog_constants
123 from mercurial.revlogutils import constants as revlog_constants
124
124
125 perf_rl_kind = (revlog_constants.KIND_OTHER, b'created-by-perf')
125 perf_rl_kind = (revlog_constants.KIND_OTHER, b'created-by-perf')
126
126
127 def revlog(opener, *args, **kwargs):
127 def revlog(opener, *args, **kwargs):
128 return mercurial.revlog.revlog(opener, perf_rl_kind, *args, **kwargs)
128 return mercurial.revlog.revlog(opener, perf_rl_kind, *args, **kwargs)
129
129
130
130
131 except (ImportError, AttributeError):
131 except (ImportError, AttributeError):
132 perf_rl_kind = None
132 perf_rl_kind = None
133
133
134 def revlog(opener, *args, **kwargs):
134 def revlog(opener, *args, **kwargs):
135 return mercurial.revlog.revlog(opener, *args, **kwargs)
135 return mercurial.revlog.revlog(opener, *args, **kwargs)
136
136
137
137
138 def identity(a):
138 def identity(a):
139 return a
139 return a
140
140
141
141
142 try:
142 try:
143 from mercurial import pycompat
143 from mercurial import pycompat
144
144
145 getargspec = pycompat.getargspec # added to module after 4.5
145 getargspec = pycompat.getargspec # added to module after 4.5
146 _byteskwargs = pycompat.byteskwargs # since 4.1 (or fbc3f73dc802)
146 _byteskwargs = pycompat.byteskwargs # since 4.1 (or fbc3f73dc802)
147 _sysstr = pycompat.sysstr # since 4.0 (or 2219f4f82ede)
147 _sysstr = pycompat.sysstr # since 4.0 (or 2219f4f82ede)
148 _bytestr = pycompat.bytestr # since 4.2 (or b70407bd84d5)
148 _bytestr = pycompat.bytestr # since 4.2 (or b70407bd84d5)
149 _xrange = pycompat.xrange # since 4.8 (or 7eba8f83129b)
149 _xrange = pycompat.xrange # since 4.8 (or 7eba8f83129b)
150 fsencode = pycompat.fsencode # since 3.9 (or f4a5e0e86a7e)
150 fsencode = pycompat.fsencode # since 3.9 (or f4a5e0e86a7e)
151 if pycompat.ispy3:
151 if pycompat.ispy3:
152 _maxint = sys.maxsize # per py3 docs for replacing maxint
152 _maxint = sys.maxsize # per py3 docs for replacing maxint
153 else:
153 else:
154 _maxint = sys.maxint
154 _maxint = sys.maxint
155 except (NameError, ImportError, AttributeError):
155 except (NameError, ImportError, AttributeError):
156 import inspect
156 import inspect
157
157
158 getargspec = inspect.getargspec
158 getargspec = inspect.getargspec
159 _byteskwargs = identity
159 _byteskwargs = identity
160 _bytestr = str
160 _bytestr = str
161 fsencode = identity # no py3 support
161 fsencode = identity # no py3 support
162 _maxint = sys.maxint # no py3 support
162 _maxint = sys.maxint # no py3 support
163 _sysstr = lambda x: x # no py3 support
163 _sysstr = lambda x: x # no py3 support
164 _xrange = xrange
164 _xrange = xrange
165
165
166 try:
166 try:
167 # 4.7+
167 # 4.7+
168 queue = pycompat.queue.Queue
168 queue = pycompat.queue.Queue
169 except (NameError, AttributeError, ImportError):
169 except (NameError, AttributeError, ImportError):
170 # <4.7.
170 # <4.7.
171 try:
171 try:
172 queue = pycompat.queue
172 queue = pycompat.queue
173 except (NameError, AttributeError, ImportError):
173 except (NameError, AttributeError, ImportError):
174 import Queue as queue
174 import Queue as queue
175
175
176 try:
176 try:
177 from mercurial import logcmdutil
177 from mercurial import logcmdutil
178
178
179 makelogtemplater = logcmdutil.maketemplater
179 makelogtemplater = logcmdutil.maketemplater
180 except (AttributeError, ImportError):
180 except (AttributeError, ImportError):
181 try:
181 try:
182 makelogtemplater = cmdutil.makelogtemplater
182 makelogtemplater = cmdutil.makelogtemplater
183 except (AttributeError, ImportError):
183 except (AttributeError, ImportError):
184 makelogtemplater = None
184 makelogtemplater = None
185
185
186 # for "historical portability":
186 # for "historical portability":
187 # define util.safehasattr forcibly, because util.safehasattr has been
187 # define util.safehasattr forcibly, because util.safehasattr has been
188 # available since 1.9.3 (or 94b200a11cf7)
188 # available since 1.9.3 (or 94b200a11cf7)
189 _undefined = object()
189 _undefined = object()
190
190
191
191
192 def safehasattr(thing, attr):
192 def safehasattr(thing, attr):
193 return getattr(thing, _sysstr(attr), _undefined) is not _undefined
193 return getattr(thing, _sysstr(attr), _undefined) is not _undefined
194
194
195
195
196 setattr(util, 'safehasattr', safehasattr)
196 setattr(util, 'safehasattr', safehasattr)
197
197
198 # for "historical portability":
198 # for "historical portability":
199 # define util.timer forcibly, because util.timer has been available
199 # define util.timer forcibly, because util.timer has been available
200 # since ae5d60bb70c9
200 # since ae5d60bb70c9
201 if safehasattr(time, 'perf_counter'):
201 if safehasattr(time, 'perf_counter'):
202 util.timer = time.perf_counter
202 util.timer = time.perf_counter
203 elif os.name == b'nt':
203 elif os.name == b'nt':
204 util.timer = time.clock
204 util.timer = time.clock
205 else:
205 else:
206 util.timer = time.time
206 util.timer = time.time
207
207
208 # for "historical portability":
208 # for "historical portability":
209 # use locally defined empty option list, if formatteropts isn't
209 # use locally defined empty option list, if formatteropts isn't
210 # available, because commands.formatteropts has been available since
210 # available, because commands.formatteropts has been available since
211 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
211 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
212 # available since 2.2 (or ae5f92e154d3)
212 # available since 2.2 (or ae5f92e154d3)
213 formatteropts = getattr(
213 formatteropts = getattr(
214 cmdutil, "formatteropts", getattr(commands, "formatteropts", [])
214 cmdutil, "formatteropts", getattr(commands, "formatteropts", [])
215 )
215 )
216
216
217 # for "historical portability":
217 # for "historical portability":
218 # use locally defined option list, if debugrevlogopts isn't available,
218 # use locally defined option list, if debugrevlogopts isn't available,
219 # because commands.debugrevlogopts has been available since 3.7 (or
219 # because commands.debugrevlogopts has been available since 3.7 (or
220 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
220 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
221 # since 1.9 (or a79fea6b3e77).
221 # since 1.9 (or a79fea6b3e77).
222 revlogopts = getattr(
222 revlogopts = getattr(
223 cmdutil,
223 cmdutil,
224 "debugrevlogopts",
224 "debugrevlogopts",
225 getattr(
225 getattr(
226 commands,
226 commands,
227 "debugrevlogopts",
227 "debugrevlogopts",
228 [
228 [
229 (b'c', b'changelog', False, b'open changelog'),
229 (b'c', b'changelog', False, b'open changelog'),
230 (b'm', b'manifest', False, b'open manifest'),
230 (b'm', b'manifest', False, b'open manifest'),
231 (b'', b'dir', False, b'open directory manifest'),
231 (b'', b'dir', False, b'open directory manifest'),
232 ],
232 ],
233 ),
233 ),
234 )
234 )
235
235
236 cmdtable = {}
236 cmdtable = {}
237
237
238
238
239 # for "historical portability":
239 # for "historical portability":
240 # define parsealiases locally, because cmdutil.parsealiases has been
240 # define parsealiases locally, because cmdutil.parsealiases has been
241 # available since 1.5 (or 6252852b4332)
241 # available since 1.5 (or 6252852b4332)
242 def parsealiases(cmd):
242 def parsealiases(cmd):
243 return cmd.split(b"|")
243 return cmd.split(b"|")
244
244
245
245
246 if safehasattr(registrar, 'command'):
246 if safehasattr(registrar, 'command'):
247 command = registrar.command(cmdtable)
247 command = registrar.command(cmdtable)
248 elif safehasattr(cmdutil, 'command'):
248 elif safehasattr(cmdutil, 'command'):
249 command = cmdutil.command(cmdtable)
249 command = cmdutil.command(cmdtable)
250 if 'norepo' not in getargspec(command).args:
250 if 'norepo' not in getargspec(command).args:
251 # for "historical portability":
251 # for "historical portability":
252 # wrap original cmdutil.command, because "norepo" option has
252 # wrap original cmdutil.command, because "norepo" option has
253 # been available since 3.1 (or 75a96326cecb)
253 # been available since 3.1 (or 75a96326cecb)
254 _command = command
254 _command = command
255
255
256 def command(name, options=(), synopsis=None, norepo=False):
256 def command(name, options=(), synopsis=None, norepo=False):
257 if norepo:
257 if norepo:
258 commands.norepo += b' %s' % b' '.join(parsealiases(name))
258 commands.norepo += b' %s' % b' '.join(parsealiases(name))
259 return _command(name, list(options), synopsis)
259 return _command(name, list(options), synopsis)
260
260
261
261
262 else:
262 else:
263 # for "historical portability":
263 # for "historical portability":
264 # define "@command" annotation locally, because cmdutil.command
264 # define "@command" annotation locally, because cmdutil.command
265 # has been available since 1.9 (or 2daa5179e73f)
265 # has been available since 1.9 (or 2daa5179e73f)
266 def command(name, options=(), synopsis=None, norepo=False):
266 def command(name, options=(), synopsis=None, norepo=False):
267 def decorator(func):
267 def decorator(func):
268 if synopsis:
268 if synopsis:
269 cmdtable[name] = func, list(options), synopsis
269 cmdtable[name] = func, list(options), synopsis
270 else:
270 else:
271 cmdtable[name] = func, list(options)
271 cmdtable[name] = func, list(options)
272 if norepo:
272 if norepo:
273 commands.norepo += b' %s' % b' '.join(parsealiases(name))
273 commands.norepo += b' %s' % b' '.join(parsealiases(name))
274 return func
274 return func
275
275
276 return decorator
276 return decorator
277
277
278
278
279 try:
279 try:
280 import mercurial.registrar
280 import mercurial.registrar
281 import mercurial.configitems
281 import mercurial.configitems
282
282
283 configtable = {}
283 configtable = {}
284 configitem = mercurial.registrar.configitem(configtable)
284 configitem = mercurial.registrar.configitem(configtable)
285 configitem(
285 configitem(
286 b'perf',
286 b'perf',
287 b'presleep',
287 b'presleep',
288 default=mercurial.configitems.dynamicdefault,
288 default=mercurial.configitems.dynamicdefault,
289 experimental=True,
289 experimental=True,
290 )
290 )
291 configitem(
291 configitem(
292 b'perf',
292 b'perf',
293 b'stub',
293 b'stub',
294 default=mercurial.configitems.dynamicdefault,
294 default=mercurial.configitems.dynamicdefault,
295 experimental=True,
295 experimental=True,
296 )
296 )
297 configitem(
297 configitem(
298 b'perf',
298 b'perf',
299 b'parentscount',
299 b'parentscount',
300 default=mercurial.configitems.dynamicdefault,
300 default=mercurial.configitems.dynamicdefault,
301 experimental=True,
301 experimental=True,
302 )
302 )
303 configitem(
303 configitem(
304 b'perf',
304 b'perf',
305 b'all-timing',
305 b'all-timing',
306 default=mercurial.configitems.dynamicdefault,
306 default=mercurial.configitems.dynamicdefault,
307 experimental=True,
307 experimental=True,
308 )
308 )
309 configitem(
309 configitem(
310 b'perf',
310 b'perf',
311 b'pre-run',
311 b'pre-run',
312 default=mercurial.configitems.dynamicdefault,
312 default=mercurial.configitems.dynamicdefault,
313 )
313 )
314 configitem(
314 configitem(
315 b'perf',
315 b'perf',
316 b'profile-benchmark',
316 b'profile-benchmark',
317 default=mercurial.configitems.dynamicdefault,
317 default=mercurial.configitems.dynamicdefault,
318 )
318 )
319 configitem(
319 configitem(
320 b'perf',
320 b'perf',
321 b'run-limits',
321 b'run-limits',
322 default=mercurial.configitems.dynamicdefault,
322 default=mercurial.configitems.dynamicdefault,
323 experimental=True,
323 experimental=True,
324 )
324 )
325 except (ImportError, AttributeError):
325 except (ImportError, AttributeError):
326 pass
326 pass
327 except TypeError:
327 except TypeError:
328 # compatibility fix for a11fd395e83f
328 # compatibility fix for a11fd395e83f
329 # hg version: 5.2
329 # hg version: 5.2
330 configitem(
330 configitem(
331 b'perf',
331 b'perf',
332 b'presleep',
332 b'presleep',
333 default=mercurial.configitems.dynamicdefault,
333 default=mercurial.configitems.dynamicdefault,
334 )
334 )
335 configitem(
335 configitem(
336 b'perf',
336 b'perf',
337 b'stub',
337 b'stub',
338 default=mercurial.configitems.dynamicdefault,
338 default=mercurial.configitems.dynamicdefault,
339 )
339 )
340 configitem(
340 configitem(
341 b'perf',
341 b'perf',
342 b'parentscount',
342 b'parentscount',
343 default=mercurial.configitems.dynamicdefault,
343 default=mercurial.configitems.dynamicdefault,
344 )
344 )
345 configitem(
345 configitem(
346 b'perf',
346 b'perf',
347 b'all-timing',
347 b'all-timing',
348 default=mercurial.configitems.dynamicdefault,
348 default=mercurial.configitems.dynamicdefault,
349 )
349 )
350 configitem(
350 configitem(
351 b'perf',
351 b'perf',
352 b'pre-run',
352 b'pre-run',
353 default=mercurial.configitems.dynamicdefault,
353 default=mercurial.configitems.dynamicdefault,
354 )
354 )
355 configitem(
355 configitem(
356 b'perf',
356 b'perf',
357 b'profile-benchmark',
357 b'profile-benchmark',
358 default=mercurial.configitems.dynamicdefault,
358 default=mercurial.configitems.dynamicdefault,
359 )
359 )
360 configitem(
360 configitem(
361 b'perf',
361 b'perf',
362 b'run-limits',
362 b'run-limits',
363 default=mercurial.configitems.dynamicdefault,
363 default=mercurial.configitems.dynamicdefault,
364 )
364 )
365
365
366
366
367 def getlen(ui):
367 def getlen(ui):
368 if ui.configbool(b"perf", b"stub", False):
368 if ui.configbool(b"perf", b"stub", False):
369 return lambda x: 1
369 return lambda x: 1
370 return len
370 return len
371
371
372
372
373 class noop:
373 class noop:
374 """dummy context manager"""
374 """dummy context manager"""
375
375
376 def __enter__(self):
376 def __enter__(self):
377 pass
377 pass
378
378
379 def __exit__(self, *args):
379 def __exit__(self, *args):
380 pass
380 pass
381
381
382
382
383 NOOPCTX = noop()
383 NOOPCTX = noop()
384
384
385
385
386 def gettimer(ui, opts=None):
386 def gettimer(ui, opts=None):
387 """return a timer function and formatter: (timer, formatter)
387 """return a timer function and formatter: (timer, formatter)
388
388
389 This function exists to gather the creation of formatter in a single
389 This function exists to gather the creation of formatter in a single
390 place instead of duplicating it in all performance commands."""
390 place instead of duplicating it in all performance commands."""
391
391
392 # enforce an idle period before execution to counteract power management
392 # enforce an idle period before execution to counteract power management
393 # experimental config: perf.presleep
393 # experimental config: perf.presleep
394 time.sleep(getint(ui, b"perf", b"presleep", 1))
394 time.sleep(getint(ui, b"perf", b"presleep", 1))
395
395
396 if opts is None:
396 if opts is None:
397 opts = {}
397 opts = {}
398 # redirect all to stderr unless buffer api is in use
398 # redirect all to stderr unless buffer api is in use
399 if not ui._buffers:
399 if not ui._buffers:
400 ui = ui.copy()
400 ui = ui.copy()
401 uifout = safeattrsetter(ui, b'fout', ignoremissing=True)
401 uifout = safeattrsetter(ui, b'fout', ignoremissing=True)
402 if uifout:
402 if uifout:
403 # for "historical portability":
403 # for "historical portability":
404 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
404 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
405 uifout.set(ui.ferr)
405 uifout.set(ui.ferr)
406
406
407 # get a formatter
407 # get a formatter
408 uiformatter = getattr(ui, 'formatter', None)
408 uiformatter = getattr(ui, 'formatter', None)
409 if uiformatter:
409 if uiformatter:
410 fm = uiformatter(b'perf', opts)
410 fm = uiformatter(b'perf', opts)
411 else:
411 else:
412 # for "historical portability":
412 # for "historical portability":
413 # define formatter locally, because ui.formatter has been
413 # define formatter locally, because ui.formatter has been
414 # available since 2.2 (or ae5f92e154d3)
414 # available since 2.2 (or ae5f92e154d3)
415 from mercurial import node
415 from mercurial import node
416
416
417 class defaultformatter:
417 class defaultformatter:
418 """Minimized composition of baseformatter and plainformatter"""
418 """Minimized composition of baseformatter and plainformatter"""
419
419
420 def __init__(self, ui, topic, opts):
420 def __init__(self, ui, topic, opts):
421 self._ui = ui
421 self._ui = ui
422 if ui.debugflag:
422 if ui.debugflag:
423 self.hexfunc = node.hex
423 self.hexfunc = node.hex
424 else:
424 else:
425 self.hexfunc = node.short
425 self.hexfunc = node.short
426
426
427 def __nonzero__(self):
427 def __nonzero__(self):
428 return False
428 return False
429
429
430 __bool__ = __nonzero__
430 __bool__ = __nonzero__
431
431
432 def startitem(self):
432 def startitem(self):
433 pass
433 pass
434
434
435 def data(self, **data):
435 def data(self, **data):
436 pass
436 pass
437
437
438 def write(self, fields, deftext, *fielddata, **opts):
438 def write(self, fields, deftext, *fielddata, **opts):
439 self._ui.write(deftext % fielddata, **opts)
439 self._ui.write(deftext % fielddata, **opts)
440
440
441 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
441 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
442 if cond:
442 if cond:
443 self._ui.write(deftext % fielddata, **opts)
443 self._ui.write(deftext % fielddata, **opts)
444
444
445 def plain(self, text, **opts):
445 def plain(self, text, **opts):
446 self._ui.write(text, **opts)
446 self._ui.write(text, **opts)
447
447
448 def end(self):
448 def end(self):
449 pass
449 pass
450
450
451 fm = defaultformatter(ui, b'perf', opts)
451 fm = defaultformatter(ui, b'perf', opts)
452
452
453 # stub function, runs code only once instead of in a loop
453 # stub function, runs code only once instead of in a loop
454 # experimental config: perf.stub
454 # experimental config: perf.stub
455 if ui.configbool(b"perf", b"stub", False):
455 if ui.configbool(b"perf", b"stub", False):
456 return functools.partial(stub_timer, fm), fm
456 return functools.partial(stub_timer, fm), fm
457
457
458 # experimental config: perf.all-timing
458 # experimental config: perf.all-timing
459 displayall = ui.configbool(b"perf", b"all-timing", True)
459 displayall = ui.configbool(b"perf", b"all-timing", True)
460
460
461 # experimental config: perf.run-limits
461 # experimental config: perf.run-limits
462 limitspec = ui.configlist(b"perf", b"run-limits", [])
462 limitspec = ui.configlist(b"perf", b"run-limits", [])
463 limits = []
463 limits = []
464 for item in limitspec:
464 for item in limitspec:
465 parts = item.split(b'-', 1)
465 parts = item.split(b'-', 1)
466 if len(parts) < 2:
466 if len(parts) < 2:
467 ui.warn((b'malformatted run limit entry, missing "-": %s\n' % item))
467 ui.warn((b'malformatted run limit entry, missing "-": %s\n' % item))
468 continue
468 continue
469 try:
469 try:
470 time_limit = float(_sysstr(parts[0]))
470 time_limit = float(_sysstr(parts[0]))
471 except ValueError as e:
471 except ValueError as e:
472 ui.warn(
472 ui.warn(
473 (
473 (
474 b'malformatted run limit entry, %s: %s\n'
474 b'malformatted run limit entry, %s: %s\n'
475 % (_bytestr(e), item)
475 % (_bytestr(e), item)
476 )
476 )
477 )
477 )
478 continue
478 continue
479 try:
479 try:
480 run_limit = int(_sysstr(parts[1]))
480 run_limit = int(_sysstr(parts[1]))
481 except ValueError as e:
481 except ValueError as e:
482 ui.warn(
482 ui.warn(
483 (
483 (
484 b'malformatted run limit entry, %s: %s\n'
484 b'malformatted run limit entry, %s: %s\n'
485 % (_bytestr(e), item)
485 % (_bytestr(e), item)
486 )
486 )
487 )
487 )
488 continue
488 continue
489 limits.append((time_limit, run_limit))
489 limits.append((time_limit, run_limit))
490 if not limits:
490 if not limits:
491 limits = DEFAULTLIMITS
491 limits = DEFAULTLIMITS
492
492
493 profiler = None
493 profiler = None
494 if profiling is not None:
494 if profiling is not None:
495 if ui.configbool(b"perf", b"profile-benchmark", False):
495 if ui.configbool(b"perf", b"profile-benchmark", False):
496 profiler = profiling.profile(ui)
496 profiler = profiling.profile(ui)
497
497
498 prerun = getint(ui, b"perf", b"pre-run", 0)
498 prerun = getint(ui, b"perf", b"pre-run", 0)
499 t = functools.partial(
499 t = functools.partial(
500 _timer,
500 _timer,
501 fm,
501 fm,
502 displayall=displayall,
502 displayall=displayall,
503 limits=limits,
503 limits=limits,
504 prerun=prerun,
504 prerun=prerun,
505 profiler=profiler,
505 profiler=profiler,
506 )
506 )
507 return t, fm
507 return t, fm
508
508
509
509
510 def stub_timer(fm, func, setup=None, title=None):
510 def stub_timer(fm, func, setup=None, title=None):
511 if setup is not None:
511 if setup is not None:
512 setup()
512 setup()
513 func()
513 func()
514
514
515
515
516 @contextlib.contextmanager
516 @contextlib.contextmanager
517 def timeone():
517 def timeone():
518 r = []
518 r = []
519 ostart = os.times()
519 ostart = os.times()
520 cstart = util.timer()
520 cstart = util.timer()
521 yield r
521 yield r
522 cstop = util.timer()
522 cstop = util.timer()
523 ostop = os.times()
523 ostop = os.times()
524 a, b = ostart, ostop
524 a, b = ostart, ostop
525 r.append((cstop - cstart, b[0] - a[0], b[1] - a[1]))
525 r.append((cstop - cstart, b[0] - a[0], b[1] - a[1]))
526
526
527
527
528 # list of stop condition (elapsed time, minimal run count)
528 # list of stop condition (elapsed time, minimal run count)
529 DEFAULTLIMITS = (
529 DEFAULTLIMITS = (
530 (3.0, 100),
530 (3.0, 100),
531 (10.0, 3),
531 (10.0, 3),
532 )
532 )
533
533
534
534
535 @contextlib.contextmanager
535 @contextlib.contextmanager
536 def noop_context():
536 def noop_context():
537 yield
537 yield
538
538
539
539
540 def _timer(
540 def _timer(
541 fm,
541 fm,
542 func,
542 func,
543 setup=None,
543 setup=None,
544 context=noop_context,
544 context=noop_context,
545 title=None,
545 title=None,
546 displayall=False,
546 displayall=False,
547 limits=DEFAULTLIMITS,
547 limits=DEFAULTLIMITS,
548 prerun=0,
548 prerun=0,
549 profiler=None,
549 profiler=None,
550 ):
550 ):
551 gc.collect()
551 gc.collect()
552 results = []
552 results = []
553 begin = util.timer()
553 begin = util.timer()
554 count = 0
554 count = 0
555 if profiler is None:
555 if profiler is None:
556 profiler = NOOPCTX
556 profiler = NOOPCTX
557 for i in range(prerun):
557 for i in range(prerun):
558 if setup is not None:
558 if setup is not None:
559 setup()
559 setup()
560 with context():
560 with context():
561 func()
561 func()
562 keepgoing = True
562 keepgoing = True
563 while keepgoing:
563 while keepgoing:
564 if setup is not None:
564 if setup is not None:
565 setup()
565 setup()
566 with context():
566 with context():
567 with profiler:
567 with profiler:
568 with timeone() as item:
568 with timeone() as item:
569 r = func()
569 r = func()
570 profiler = NOOPCTX
570 profiler = NOOPCTX
571 count += 1
571 count += 1
572 results.append(item[0])
572 results.append(item[0])
573 cstop = util.timer()
573 cstop = util.timer()
574 # Look for a stop condition.
574 # Look for a stop condition.
575 elapsed = cstop - begin
575 elapsed = cstop - begin
576 for t, mincount in limits:
576 for t, mincount in limits:
577 if elapsed >= t and count >= mincount:
577 if elapsed >= t and count >= mincount:
578 keepgoing = False
578 keepgoing = False
579 break
579 break
580
580
581 formatone(fm, results, title=title, result=r, displayall=displayall)
581 formatone(fm, results, title=title, result=r, displayall=displayall)
582
582
583
583
584 def formatone(fm, timings, title=None, result=None, displayall=False):
584 def formatone(fm, timings, title=None, result=None, displayall=False):
585 count = len(timings)
585 count = len(timings)
586
586
587 fm.startitem()
587 fm.startitem()
588
588
589 if title:
589 if title:
590 fm.write(b'title', b'! %s\n', title)
590 fm.write(b'title', b'! %s\n', title)
591 if result:
591 if result:
592 fm.write(b'result', b'! result: %s\n', result)
592 fm.write(b'result', b'! result: %s\n', result)
593
593
594 def display(role, entry):
594 def display(role, entry):
595 prefix = b''
595 prefix = b''
596 if role != b'best':
596 if role != b'best':
597 prefix = b'%s.' % role
597 prefix = b'%s.' % role
598 fm.plain(b'!')
598 fm.plain(b'!')
599 fm.write(prefix + b'wall', b' wall %f', entry[0])
599 fm.write(prefix + b'wall', b' wall %f', entry[0])
600 fm.write(prefix + b'comb', b' comb %f', entry[1] + entry[2])
600 fm.write(prefix + b'comb', b' comb %f', entry[1] + entry[2])
601 fm.write(prefix + b'user', b' user %f', entry[1])
601 fm.write(prefix + b'user', b' user %f', entry[1])
602 fm.write(prefix + b'sys', b' sys %f', entry[2])
602 fm.write(prefix + b'sys', b' sys %f', entry[2])
603 fm.write(prefix + b'count', b' (%s of %%d)' % role, count)
603 fm.write(prefix + b'count', b' (%s of %%d)' % role, count)
604 fm.plain(b'\n')
604 fm.plain(b'\n')
605
605
606 timings.sort()
606 timings.sort()
607 min_val = timings[0]
607 min_val = timings[0]
608 display(b'best', min_val)
608 display(b'best', min_val)
609 if displayall:
609 if displayall:
610 max_val = timings[-1]
610 max_val = timings[-1]
611 display(b'max', max_val)
611 display(b'max', max_val)
612 avg = tuple([sum(x) / count for x in zip(*timings)])
612 avg = tuple([sum(x) / count for x in zip(*timings)])
613 display(b'avg', avg)
613 display(b'avg', avg)
614 median = timings[len(timings) // 2]
614 median = timings[len(timings) // 2]
615 display(b'median', median)
615 display(b'median', median)
616
616
617
617
618 # utilities for historical portability
618 # utilities for historical portability
619
619
620
620
621 def getint(ui, section, name, default):
621 def getint(ui, section, name, default):
622 # for "historical portability":
622 # for "historical portability":
623 # ui.configint has been available since 1.9 (or fa2b596db182)
623 # ui.configint has been available since 1.9 (or fa2b596db182)
624 v = ui.config(section, name, None)
624 v = ui.config(section, name, None)
625 if v is None:
625 if v is None:
626 return default
626 return default
627 try:
627 try:
628 return int(v)
628 return int(v)
629 except ValueError:
629 except ValueError:
630 raise error.ConfigError(
630 raise error.ConfigError(
631 b"%s.%s is not an integer ('%s')" % (section, name, v)
631 b"%s.%s is not an integer ('%s')" % (section, name, v)
632 )
632 )
633
633
634
634
635 def safeattrsetter(obj, name, ignoremissing=False):
635 def safeattrsetter(obj, name, ignoremissing=False):
636 """Ensure that 'obj' has 'name' attribute before subsequent setattr
636 """Ensure that 'obj' has 'name' attribute before subsequent setattr
637
637
638 This function is aborted, if 'obj' doesn't have 'name' attribute
638 This function is aborted, if 'obj' doesn't have 'name' attribute
639 at runtime. This avoids overlooking removal of an attribute, which
639 at runtime. This avoids overlooking removal of an attribute, which
640 breaks assumption of performance measurement, in the future.
640 breaks assumption of performance measurement, in the future.
641
641
642 This function returns the object to (1) assign a new value, and
642 This function returns the object to (1) assign a new value, and
643 (2) restore an original value to the attribute.
643 (2) restore an original value to the attribute.
644
644
645 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
645 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
646 abortion, and this function returns None. This is useful to
646 abortion, and this function returns None. This is useful to
647 examine an attribute, which isn't ensured in all Mercurial
647 examine an attribute, which isn't ensured in all Mercurial
648 versions.
648 versions.
649 """
649 """
650 if not util.safehasattr(obj, name):
650 if not util.safehasattr(obj, name):
651 if ignoremissing:
651 if ignoremissing:
652 return None
652 return None
653 raise error.Abort(
653 raise error.Abort(
654 (
654 (
655 b"missing attribute %s of %s might break assumption"
655 b"missing attribute %s of %s might break assumption"
656 b" of performance measurement"
656 b" of performance measurement"
657 )
657 )
658 % (name, obj)
658 % (name, obj)
659 )
659 )
660
660
661 origvalue = getattr(obj, _sysstr(name))
661 origvalue = getattr(obj, _sysstr(name))
662
662
663 class attrutil:
663 class attrutil:
664 def set(self, newvalue):
664 def set(self, newvalue):
665 setattr(obj, _sysstr(name), newvalue)
665 setattr(obj, _sysstr(name), newvalue)
666
666
667 def restore(self):
667 def restore(self):
668 setattr(obj, _sysstr(name), origvalue)
668 setattr(obj, _sysstr(name), origvalue)
669
669
670 return attrutil()
670 return attrutil()
671
671
672
672
673 # utilities to examine each internal API changes
673 # utilities to examine each internal API changes
674
674
675
675
676 def getbranchmapsubsettable():
676 def getbranchmapsubsettable():
677 # for "historical portability":
677 # for "historical portability":
678 # subsettable is defined in:
678 # subsettable is defined in:
679 # - branchmap since 2.9 (or 175c6fd8cacc)
679 # - branchmap since 2.9 (or 175c6fd8cacc)
680 # - repoview since 2.5 (or 59a9f18d4587)
680 # - repoview since 2.5 (or 59a9f18d4587)
681 # - repoviewutil since 5.0
681 # - repoviewutil since 5.0
682 for mod in (branchmap, repoview, repoviewutil):
682 for mod in (branchmap, repoview, repoviewutil):
683 subsettable = getattr(mod, 'subsettable', None)
683 subsettable = getattr(mod, 'subsettable', None)
684 if subsettable:
684 if subsettable:
685 return subsettable
685 return subsettable
686
686
687 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
687 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
688 # branchmap and repoview modules exist, but subsettable attribute
688 # branchmap and repoview modules exist, but subsettable attribute
689 # doesn't)
689 # doesn't)
690 raise error.Abort(
690 raise error.Abort(
691 b"perfbranchmap not available with this Mercurial",
691 b"perfbranchmap not available with this Mercurial",
692 hint=b"use 2.5 or later",
692 hint=b"use 2.5 or later",
693 )
693 )
694
694
695
695
696 def getsvfs(repo):
696 def getsvfs(repo):
697 """Return appropriate object to access files under .hg/store"""
697 """Return appropriate object to access files under .hg/store"""
698 # for "historical portability":
698 # for "historical portability":
699 # repo.svfs has been available since 2.3 (or 7034365089bf)
699 # repo.svfs has been available since 2.3 (or 7034365089bf)
700 svfs = getattr(repo, 'svfs', None)
700 svfs = getattr(repo, 'svfs', None)
701 if svfs:
701 if svfs:
702 return svfs
702 return svfs
703 else:
703 else:
704 return getattr(repo, 'sopener')
704 return getattr(repo, 'sopener')
705
705
706
706
707 def getvfs(repo):
707 def getvfs(repo):
708 """Return appropriate object to access files under .hg"""
708 """Return appropriate object to access files under .hg"""
709 # for "historical portability":
709 # for "historical portability":
710 # repo.vfs has been available since 2.3 (or 7034365089bf)
710 # repo.vfs has been available since 2.3 (or 7034365089bf)
711 vfs = getattr(repo, 'vfs', None)
711 vfs = getattr(repo, 'vfs', None)
712 if vfs:
712 if vfs:
713 return vfs
713 return vfs
714 else:
714 else:
715 return getattr(repo, 'opener')
715 return getattr(repo, 'opener')
716
716
717
717
718 def repocleartagscachefunc(repo):
718 def repocleartagscachefunc(repo):
719 """Return the function to clear tags cache according to repo internal API"""
719 """Return the function to clear tags cache according to repo internal API"""
720 if util.safehasattr(repo, b'_tagscache'): # since 2.0 (or 9dca7653b525)
720 if util.safehasattr(repo, b'_tagscache'): # since 2.0 (or 9dca7653b525)
721 # in this case, setattr(repo, '_tagscache', None) or so isn't
721 # in this case, setattr(repo, '_tagscache', None) or so isn't
722 # correct way to clear tags cache, because existing code paths
722 # correct way to clear tags cache, because existing code paths
723 # expect _tagscache to be a structured object.
723 # expect _tagscache to be a structured object.
724 def clearcache():
724 def clearcache():
725 # _tagscache has been filteredpropertycache since 2.5 (or
725 # _tagscache has been filteredpropertycache since 2.5 (or
726 # 98c867ac1330), and delattr() can't work in such case
726 # 98c867ac1330), and delattr() can't work in such case
727 if '_tagscache' in vars(repo):
727 if '_tagscache' in vars(repo):
728 del repo.__dict__['_tagscache']
728 del repo.__dict__['_tagscache']
729
729
730 return clearcache
730 return clearcache
731
731
732 repotags = safeattrsetter(repo, b'_tags', ignoremissing=True)
732 repotags = safeattrsetter(repo, b'_tags', ignoremissing=True)
733 if repotags: # since 1.4 (or 5614a628d173)
733 if repotags: # since 1.4 (or 5614a628d173)
734 return lambda: repotags.set(None)
734 return lambda: repotags.set(None)
735
735
736 repotagscache = safeattrsetter(repo, b'tagscache', ignoremissing=True)
736 repotagscache = safeattrsetter(repo, b'tagscache', ignoremissing=True)
737 if repotagscache: # since 0.6 (or d7df759d0e97)
737 if repotagscache: # since 0.6 (or d7df759d0e97)
738 return lambda: repotagscache.set(None)
738 return lambda: repotagscache.set(None)
739
739
740 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
740 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
741 # this point, but it isn't so problematic, because:
741 # this point, but it isn't so problematic, because:
742 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
742 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
743 # in perftags() causes failure soon
743 # in perftags() causes failure soon
744 # - perf.py itself has been available since 1.1 (or eb240755386d)
744 # - perf.py itself has been available since 1.1 (or eb240755386d)
745 raise error.Abort(b"tags API of this hg command is unknown")
745 raise error.Abort(b"tags API of this hg command is unknown")
746
746
747
747
748 # utilities to clear cache
748 # utilities to clear cache
749
749
750
750
751 def clearfilecache(obj, attrname):
751 def clearfilecache(obj, attrname):
752 unfiltered = getattr(obj, 'unfiltered', None)
752 unfiltered = getattr(obj, 'unfiltered', None)
753 if unfiltered is not None:
753 if unfiltered is not None:
754 obj = obj.unfiltered()
754 obj = obj.unfiltered()
755 if attrname in vars(obj):
755 if attrname in vars(obj):
756 delattr(obj, attrname)
756 delattr(obj, attrname)
757 obj._filecache.pop(attrname, None)
757 obj._filecache.pop(attrname, None)
758
758
759
759
760 def clearchangelog(repo):
760 def clearchangelog(repo):
761 if repo is not repo.unfiltered():
761 if repo is not repo.unfiltered():
762 object.__setattr__(repo, '_clcachekey', None)
762 object.__setattr__(repo, '_clcachekey', None)
763 object.__setattr__(repo, '_clcache', None)
763 object.__setattr__(repo, '_clcache', None)
764 clearfilecache(repo.unfiltered(), 'changelog')
764 clearfilecache(repo.unfiltered(), 'changelog')
765
765
766
766
767 # perf commands
767 # perf commands
768
768
769
769
770 @command(b'perf::walk|perfwalk', formatteropts)
770 @command(b'perf::walk|perfwalk', formatteropts)
771 def perfwalk(ui, repo, *pats, **opts):
771 def perfwalk(ui, repo, *pats, **opts):
772 opts = _byteskwargs(opts)
772 opts = _byteskwargs(opts)
773 timer, fm = gettimer(ui, opts)
773 timer, fm = gettimer(ui, opts)
774 m = scmutil.match(repo[None], pats, {})
774 m = scmutil.match(repo[None], pats, {})
775 timer(
775 timer(
776 lambda: len(
776 lambda: len(
777 list(
777 list(
778 repo.dirstate.walk(m, subrepos=[], unknown=True, ignored=False)
778 repo.dirstate.walk(m, subrepos=[], unknown=True, ignored=False)
779 )
779 )
780 )
780 )
781 )
781 )
782 fm.end()
782 fm.end()
783
783
784
784
785 @command(b'perf::annotate|perfannotate', formatteropts)
785 @command(b'perf::annotate|perfannotate', formatteropts)
786 def perfannotate(ui, repo, f, **opts):
786 def perfannotate(ui, repo, f, **opts):
787 opts = _byteskwargs(opts)
787 opts = _byteskwargs(opts)
788 timer, fm = gettimer(ui, opts)
788 timer, fm = gettimer(ui, opts)
789 fc = repo[b'.'][f]
789 fc = repo[b'.'][f]
790 timer(lambda: len(fc.annotate(True)))
790 timer(lambda: len(fc.annotate(True)))
791 fm.end()
791 fm.end()
792
792
793
793
794 @command(
794 @command(
795 b'perf::status|perfstatus',
795 b'perf::status|perfstatus',
796 [
796 [
797 (b'u', b'unknown', False, b'ask status to look for unknown files'),
797 (b'u', b'unknown', False, b'ask status to look for unknown files'),
798 (b'', b'dirstate', False, b'benchmark the internal dirstate call'),
798 (b'', b'dirstate', False, b'benchmark the internal dirstate call'),
799 ]
799 ]
800 + formatteropts,
800 + formatteropts,
801 )
801 )
802 def perfstatus(ui, repo, **opts):
802 def perfstatus(ui, repo, **opts):
803 """benchmark the performance of a single status call
803 """benchmark the performance of a single status call
804
804
805 The repository data are preserved between each call.
805 The repository data are preserved between each call.
806
806
807 By default, only the status of the tracked file are requested. If
807 By default, only the status of the tracked file are requested. If
808 `--unknown` is passed, the "unknown" files are also tracked.
808 `--unknown` is passed, the "unknown" files are also tracked.
809 """
809 """
810 opts = _byteskwargs(opts)
810 opts = _byteskwargs(opts)
811 # m = match.always(repo.root, repo.getcwd())
811 # m = match.always(repo.root, repo.getcwd())
812 # timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
812 # timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
813 # False))))
813 # False))))
814 timer, fm = gettimer(ui, opts)
814 timer, fm = gettimer(ui, opts)
815 if opts[b'dirstate']:
815 if opts[b'dirstate']:
816 dirstate = repo.dirstate
816 dirstate = repo.dirstate
817 m = scmutil.matchall(repo)
817 m = scmutil.matchall(repo)
818 unknown = opts[b'unknown']
818 unknown = opts[b'unknown']
819
819
820 def status_dirstate():
820 def status_dirstate():
821 s = dirstate.status(
821 s = dirstate.status(
822 m, subrepos=[], ignored=False, clean=False, unknown=unknown
822 m, subrepos=[], ignored=False, clean=False, unknown=unknown
823 )
823 )
824 sum(map(bool, s))
824 sum(map(bool, s))
825
825
826 if util.safehasattr(dirstate, 'running_status'):
826 if util.safehasattr(dirstate, 'running_status'):
827 with dirstate.running_status(repo):
827 with dirstate.running_status(repo):
828 timer(status_dirstate)
828 timer(status_dirstate)
829 dirstate.invalidate()
829 dirstate.invalidate()
830 else:
830 else:
831 timer(status_dirstate)
831 timer(status_dirstate)
832 else:
832 else:
833 timer(lambda: sum(map(len, repo.status(unknown=opts[b'unknown']))))
833 timer(lambda: sum(map(len, repo.status(unknown=opts[b'unknown']))))
834 fm.end()
834 fm.end()
835
835
836
836
837 @command(b'perf::addremove|perfaddremove', formatteropts)
837 @command(b'perf::addremove|perfaddremove', formatteropts)
838 def perfaddremove(ui, repo, **opts):
838 def perfaddremove(ui, repo, **opts):
839 opts = _byteskwargs(opts)
839 opts = _byteskwargs(opts)
840 timer, fm = gettimer(ui, opts)
840 timer, fm = gettimer(ui, opts)
841 try:
841 try:
842 oldquiet = repo.ui.quiet
842 oldquiet = repo.ui.quiet
843 repo.ui.quiet = True
843 repo.ui.quiet = True
844 matcher = scmutil.match(repo[None])
844 matcher = scmutil.match(repo[None])
845 opts[b'dry_run'] = True
845 opts[b'dry_run'] = True
846 if 'uipathfn' in getargspec(scmutil.addremove).args:
846 if 'uipathfn' in getargspec(scmutil.addremove).args:
847 uipathfn = scmutil.getuipathfn(repo)
847 uipathfn = scmutil.getuipathfn(repo)
848 timer(lambda: scmutil.addremove(repo, matcher, b"", uipathfn, opts))
848 timer(lambda: scmutil.addremove(repo, matcher, b"", uipathfn, opts))
849 else:
849 else:
850 timer(lambda: scmutil.addremove(repo, matcher, b"", opts))
850 timer(lambda: scmutil.addremove(repo, matcher, b"", opts))
851 finally:
851 finally:
852 repo.ui.quiet = oldquiet
852 repo.ui.quiet = oldquiet
853 fm.end()
853 fm.end()
854
854
855
855
856 def clearcaches(cl):
856 def clearcaches(cl):
857 # behave somewhat consistently across internal API changes
857 # behave somewhat consistently across internal API changes
858 if util.safehasattr(cl, b'clearcaches'):
858 if util.safehasattr(cl, b'clearcaches'):
859 cl.clearcaches()
859 cl.clearcaches()
860 elif util.safehasattr(cl, b'_nodecache'):
860 elif util.safehasattr(cl, b'_nodecache'):
861 # <= hg-5.2
861 # <= hg-5.2
862 from mercurial.node import nullid, nullrev
862 from mercurial.node import nullid, nullrev
863
863
864 cl._nodecache = {nullid: nullrev}
864 cl._nodecache = {nullid: nullrev}
865 cl._nodepos = None
865 cl._nodepos = None
866
866
867
867
868 @command(b'perf::heads|perfheads', formatteropts)
868 @command(b'perf::heads|perfheads', formatteropts)
869 def perfheads(ui, repo, **opts):
869 def perfheads(ui, repo, **opts):
870 """benchmark the computation of a changelog heads"""
870 """benchmark the computation of a changelog heads"""
871 opts = _byteskwargs(opts)
871 opts = _byteskwargs(opts)
872 timer, fm = gettimer(ui, opts)
872 timer, fm = gettimer(ui, opts)
873 cl = repo.changelog
873 cl = repo.changelog
874
874
875 def s():
875 def s():
876 clearcaches(cl)
876 clearcaches(cl)
877
877
878 def d():
878 def d():
879 len(cl.headrevs())
879 len(cl.headrevs())
880
880
881 timer(d, setup=s)
881 timer(d, setup=s)
882 fm.end()
882 fm.end()
883
883
884
884
885 def _default_clear_on_disk_tags_cache(repo):
885 def _default_clear_on_disk_tags_cache(repo):
886 from mercurial import tags
886 from mercurial import tags
887
887
888 repo.cachevfs.tryunlink(tags._filename(repo))
888 repo.cachevfs.tryunlink(tags._filename(repo))
889
889
890
890
891 def _default_clear_on_disk_tags_fnodes_cache(repo):
891 def _default_clear_on_disk_tags_fnodes_cache(repo):
892 from mercurial import tags
892 from mercurial import tags
893
893
894 repo.cachevfs.tryunlink(tags._fnodescachefile)
894 repo.cachevfs.tryunlink(tags._fnodescachefile)
895
895
896
896
897 def _default_forget_fnodes(repo, revs):
897 def _default_forget_fnodes(repo, revs):
898 """function used by the perf extension to prune some entries from the
898 """function used by the perf extension to prune some entries from the
899 fnodes cache"""
899 fnodes cache"""
900 from mercurial import tags
900 from mercurial import tags
901
901
902 missing_1 = b'\xff' * 4
902 missing_1 = b'\xff' * 4
903 missing_2 = b'\xff' * 20
903 missing_2 = b'\xff' * 20
904 cache = tags.hgtagsfnodescache(repo.unfiltered())
904 cache = tags.hgtagsfnodescache(repo.unfiltered())
905 for r in revs:
905 for r in revs:
906 cache._writeentry(r * tags._fnodesrecsize, missing_1, missing_2)
906 cache._writeentry(r * tags._fnodesrecsize, missing_1, missing_2)
907 cache.write()
907 cache.write()
908
908
909
909
910 @command(
910 @command(
911 b'perf::tags|perftags',
911 b'perf::tags|perftags',
912 formatteropts
912 formatteropts
913 + [
913 + [
914 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
914 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
915 (
915 (
916 b'',
916 b'',
917 b'clear-on-disk-cache',
917 b'clear-on-disk-cache',
918 False,
918 False,
919 b'clear on disk tags cache (DESTRUCTIVE)',
919 b'clear on disk tags cache (DESTRUCTIVE)',
920 ),
920 ),
921 (
921 (
922 b'',
922 b'',
923 b'clear-fnode-cache-all',
923 b'clear-fnode-cache-all',
924 False,
924 False,
925 b'clear on disk file node cache (DESTRUCTIVE),',
925 b'clear on disk file node cache (DESTRUCTIVE),',
926 ),
926 ),
927 (
927 (
928 b'',
928 b'',
929 b'clear-fnode-cache-rev',
929 b'clear-fnode-cache-rev',
930 [],
930 [],
931 b'clear on disk file node cache (DESTRUCTIVE),',
931 b'clear on disk file node cache (DESTRUCTIVE),',
932 b'REVS',
932 b'REVS',
933 ),
933 ),
934 (
934 (
935 b'',
935 b'',
936 b'update-last',
936 b'update-last',
937 b'',
937 b'',
938 b'simulate an update over the last N revisions (DESTRUCTIVE),',
938 b'simulate an update over the last N revisions (DESTRUCTIVE),',
939 b'N',
939 b'N',
940 ),
940 ),
941 ],
941 ],
942 )
942 )
943 def perftags(ui, repo, **opts):
943 def perftags(ui, repo, **opts):
944 """Benchmark tags retrieval in various situation
944 """Benchmark tags retrieval in various situation
945
945
946 The option marked as (DESTRUCTIVE) will alter the on-disk cache, possibly
946 The option marked as (DESTRUCTIVE) will alter the on-disk cache, possibly
947 altering performance after the command was run. However, it does not
947 altering performance after the command was run. However, it does not
948 destroy any stored data.
948 destroy any stored data.
949 """
949 """
950 from mercurial import tags
950 from mercurial import tags
951
951
952 opts = _byteskwargs(opts)
952 opts = _byteskwargs(opts)
953 timer, fm = gettimer(ui, opts)
953 timer, fm = gettimer(ui, opts)
954 repocleartagscache = repocleartagscachefunc(repo)
954 repocleartagscache = repocleartagscachefunc(repo)
955 clearrevlogs = opts[b'clear_revlogs']
955 clearrevlogs = opts[b'clear_revlogs']
956 clear_disk = opts[b'clear_on_disk_cache']
956 clear_disk = opts[b'clear_on_disk_cache']
957 clear_fnode = opts[b'clear_fnode_cache_all']
957 clear_fnode = opts[b'clear_fnode_cache_all']
958
958
959 clear_fnode_revs = opts[b'clear_fnode_cache_rev']
959 clear_fnode_revs = opts[b'clear_fnode_cache_rev']
960 update_last_str = opts[b'update_last']
960 update_last_str = opts[b'update_last']
961 update_last = None
961 update_last = None
962 if update_last_str:
962 if update_last_str:
963 try:
963 try:
964 update_last = int(update_last_str)
964 update_last = int(update_last_str)
965 except ValueError:
965 except ValueError:
966 msg = b'could not parse value for update-last: "%s"'
966 msg = b'could not parse value for update-last: "%s"'
967 msg %= update_last_str
967 msg %= update_last_str
968 hint = b'value should be an integer'
968 hint = b'value should be an integer'
969 raise error.Abort(msg, hint=hint)
969 raise error.Abort(msg, hint=hint)
970
970
971 clear_disk_fn = getattr(
971 clear_disk_fn = getattr(
972 tags,
972 tags,
973 "clear_cache_on_disk",
973 "clear_cache_on_disk",
974 _default_clear_on_disk_tags_cache,
974 _default_clear_on_disk_tags_cache,
975 )
975 )
976 clear_fnodes_fn = getattr(
976 if getattr(tags, 'clear_cache_fnodes_is_working', False):
977 tags,
977 clear_fnodes_fn = tags.clear_cache_fnodes
978 "clear_cache_fnodes",
978 else:
979 _default_clear_on_disk_tags_fnodes_cache,
979 clear_fnodes_fn = _default_clear_on_disk_tags_fnodes_cache
980 )
981 clear_fnodes_rev_fn = getattr(
980 clear_fnodes_rev_fn = getattr(
982 tags,
981 tags,
983 "forget_fnodes",
982 "forget_fnodes",
984 _default_forget_fnodes,
983 _default_forget_fnodes,
985 )
984 )
986
985
987 clear_revs = []
986 clear_revs = []
988 if clear_fnode_revs:
987 if clear_fnode_revs:
989 clear_revs.extends(scmutil.revrange(repo, clear_fnode_revs))
988 clear_revs.extend(scmutil.revrange(repo, clear_fnode_revs))
990
989
991 if update_last:
990 if update_last:
992 revset = b'last(all(), %d)' % update_last
991 revset = b'last(all(), %d)' % update_last
993 last_revs = repo.unfiltered().revs(revset)
992 last_revs = repo.unfiltered().revs(revset)
994 clear_revs.extend(last_revs)
993 clear_revs.extend(last_revs)
995
994
996 from mercurial import repoview
995 from mercurial import repoview
997
996
998 rev_filter = {(b'experimental', b'extra-filter-revs'): revset}
997 rev_filter = {(b'experimental', b'extra-filter-revs'): revset}
999 with repo.ui.configoverride(rev_filter, source=b"perf"):
998 with repo.ui.configoverride(rev_filter, source=b"perf"):
1000 filter_id = repoview.extrafilter(repo.ui)
999 filter_id = repoview.extrafilter(repo.ui)
1001
1000
1002 filter_name = b'%s%%%s' % (repo.filtername, filter_id)
1001 filter_name = b'%s%%%s' % (repo.filtername, filter_id)
1003 pre_repo = repo.filtered(filter_name)
1002 pre_repo = repo.filtered(filter_name)
1004 pre_repo.tags() # warm the cache
1003 pre_repo.tags() # warm the cache
1005 old_tags_path = repo.cachevfs.join(tags._filename(pre_repo))
1004 old_tags_path = repo.cachevfs.join(tags._filename(pre_repo))
1006 new_tags_path = repo.cachevfs.join(tags._filename(repo))
1005 new_tags_path = repo.cachevfs.join(tags._filename(repo))
1007
1006
1008 clear_revs = sorted(set(clear_revs))
1007 clear_revs = sorted(set(clear_revs))
1009
1008
1010 def s():
1009 def s():
1011 if update_last:
1010 if update_last:
1012 util.copyfile(old_tags_path, new_tags_path)
1011 util.copyfile(old_tags_path, new_tags_path)
1013 if clearrevlogs:
1012 if clearrevlogs:
1014 clearchangelog(repo)
1013 clearchangelog(repo)
1015 clearfilecache(repo.unfiltered(), 'manifest')
1014 clearfilecache(repo.unfiltered(), 'manifest')
1016 if clear_disk:
1015 if clear_disk:
1017 clear_disk_fn(repo)
1016 clear_disk_fn(repo)
1018 if clear_fnode:
1017 if clear_fnode:
1019 clear_fnodes_fn(repo)
1018 clear_fnodes_fn(repo)
1020 elif clear_revs:
1019 elif clear_revs:
1021 clear_fnodes_rev_fn(repo, clear_revs)
1020 clear_fnodes_rev_fn(repo, clear_revs)
1022 repocleartagscache()
1021 repocleartagscache()
1023
1022
1024 def t():
1023 def t():
1025 len(repo.tags())
1024 len(repo.tags())
1026
1025
1027 timer(t, setup=s)
1026 timer(t, setup=s)
1028 fm.end()
1027 fm.end()
1029
1028
1030
1029
1031 @command(b'perf::ancestors|perfancestors', formatteropts)
1030 @command(b'perf::ancestors|perfancestors', formatteropts)
1032 def perfancestors(ui, repo, **opts):
1031 def perfancestors(ui, repo, **opts):
1033 opts = _byteskwargs(opts)
1032 opts = _byteskwargs(opts)
1034 timer, fm = gettimer(ui, opts)
1033 timer, fm = gettimer(ui, opts)
1035 heads = repo.changelog.headrevs()
1034 heads = repo.changelog.headrevs()
1036
1035
1037 def d():
1036 def d():
1038 for a in repo.changelog.ancestors(heads):
1037 for a in repo.changelog.ancestors(heads):
1039 pass
1038 pass
1040
1039
1041 timer(d)
1040 timer(d)
1042 fm.end()
1041 fm.end()
1043
1042
1044
1043
1045 @command(b'perf::ancestorset|perfancestorset', formatteropts)
1044 @command(b'perf::ancestorset|perfancestorset', formatteropts)
1046 def perfancestorset(ui, repo, revset, **opts):
1045 def perfancestorset(ui, repo, revset, **opts):
1047 opts = _byteskwargs(opts)
1046 opts = _byteskwargs(opts)
1048 timer, fm = gettimer(ui, opts)
1047 timer, fm = gettimer(ui, opts)
1049 revs = repo.revs(revset)
1048 revs = repo.revs(revset)
1050 heads = repo.changelog.headrevs()
1049 heads = repo.changelog.headrevs()
1051
1050
1052 def d():
1051 def d():
1053 s = repo.changelog.ancestors(heads)
1052 s = repo.changelog.ancestors(heads)
1054 for rev in revs:
1053 for rev in revs:
1055 rev in s
1054 rev in s
1056
1055
1057 timer(d)
1056 timer(d)
1058 fm.end()
1057 fm.end()
1059
1058
1060
1059
1061 @command(
1060 @command(
1062 b'perf::delta-find',
1061 b'perf::delta-find',
1063 revlogopts + formatteropts,
1062 revlogopts + formatteropts,
1064 b'-c|-m|FILE REV',
1063 b'-c|-m|FILE REV',
1065 )
1064 )
1066 def perf_delta_find(ui, repo, arg_1, arg_2=None, **opts):
1065 def perf_delta_find(ui, repo, arg_1, arg_2=None, **opts):
1067 """benchmark the process of finding a valid delta for a revlog revision
1066 """benchmark the process of finding a valid delta for a revlog revision
1068
1067
1069 When a revlog receives a new revision (e.g. from a commit, or from an
1068 When a revlog receives a new revision (e.g. from a commit, or from an
1070 incoming bundle), it searches for a suitable delta-base to produce a delta.
1069 incoming bundle), it searches for a suitable delta-base to produce a delta.
1071 This perf command measures how much time we spend in this process. It
1070 This perf command measures how much time we spend in this process. It
1072 operates on an already stored revision.
1071 operates on an already stored revision.
1073
1072
1074 See `hg help debug-delta-find` for another related command.
1073 See `hg help debug-delta-find` for another related command.
1075 """
1074 """
1076 from mercurial import revlogutils
1075 from mercurial import revlogutils
1077 import mercurial.revlogutils.deltas as deltautil
1076 import mercurial.revlogutils.deltas as deltautil
1078
1077
1079 opts = _byteskwargs(opts)
1078 opts = _byteskwargs(opts)
1080 if arg_2 is None:
1079 if arg_2 is None:
1081 file_ = None
1080 file_ = None
1082 rev = arg_1
1081 rev = arg_1
1083 else:
1082 else:
1084 file_ = arg_1
1083 file_ = arg_1
1085 rev = arg_2
1084 rev = arg_2
1086
1085
1087 repo = repo.unfiltered()
1086 repo = repo.unfiltered()
1088
1087
1089 timer, fm = gettimer(ui, opts)
1088 timer, fm = gettimer(ui, opts)
1090
1089
1091 rev = int(rev)
1090 rev = int(rev)
1092
1091
1093 revlog = cmdutil.openrevlog(repo, b'perf::delta-find', file_, opts)
1092 revlog = cmdutil.openrevlog(repo, b'perf::delta-find', file_, opts)
1094
1093
1095 deltacomputer = deltautil.deltacomputer(revlog)
1094 deltacomputer = deltautil.deltacomputer(revlog)
1096
1095
1097 node = revlog.node(rev)
1096 node = revlog.node(rev)
1098 p1r, p2r = revlog.parentrevs(rev)
1097 p1r, p2r = revlog.parentrevs(rev)
1099 p1 = revlog.node(p1r)
1098 p1 = revlog.node(p1r)
1100 p2 = revlog.node(p2r)
1099 p2 = revlog.node(p2r)
1101 full_text = revlog.revision(rev)
1100 full_text = revlog.revision(rev)
1102 textlen = len(full_text)
1101 textlen = len(full_text)
1103 cachedelta = None
1102 cachedelta = None
1104 flags = revlog.flags(rev)
1103 flags = revlog.flags(rev)
1105
1104
1106 revinfo = revlogutils.revisioninfo(
1105 revinfo = revlogutils.revisioninfo(
1107 node,
1106 node,
1108 p1,
1107 p1,
1109 p2,
1108 p2,
1110 [full_text], # btext
1109 [full_text], # btext
1111 textlen,
1110 textlen,
1112 cachedelta,
1111 cachedelta,
1113 flags,
1112 flags,
1114 )
1113 )
1115
1114
1116 # Note: we should probably purge the potential caches (like the full
1115 # Note: we should probably purge the potential caches (like the full
1117 # manifest cache) between runs.
1116 # manifest cache) between runs.
1118 def find_one():
1117 def find_one():
1119 with revlog._datafp() as fh:
1118 with revlog._datafp() as fh:
1120 deltacomputer.finddeltainfo(revinfo, fh, target_rev=rev)
1119 deltacomputer.finddeltainfo(revinfo, fh, target_rev=rev)
1121
1120
1122 timer(find_one)
1121 timer(find_one)
1123 fm.end()
1122 fm.end()
1124
1123
1125
1124
1126 @command(b'perf::discovery|perfdiscovery', formatteropts, b'PATH')
1125 @command(b'perf::discovery|perfdiscovery', formatteropts, b'PATH')
1127 def perfdiscovery(ui, repo, path, **opts):
1126 def perfdiscovery(ui, repo, path, **opts):
1128 """benchmark discovery between local repo and the peer at given path"""
1127 """benchmark discovery between local repo and the peer at given path"""
1129 repos = [repo, None]
1128 repos = [repo, None]
1130 timer, fm = gettimer(ui, opts)
1129 timer, fm = gettimer(ui, opts)
1131
1130
1132 try:
1131 try:
1133 from mercurial.utils.urlutil import get_unique_pull_path_obj
1132 from mercurial.utils.urlutil import get_unique_pull_path_obj
1134
1133
1135 path = get_unique_pull_path_obj(b'perfdiscovery', ui, path)
1134 path = get_unique_pull_path_obj(b'perfdiscovery', ui, path)
1136 except ImportError:
1135 except ImportError:
1137 try:
1136 try:
1138 from mercurial.utils.urlutil import get_unique_pull_path
1137 from mercurial.utils.urlutil import get_unique_pull_path
1139
1138
1140 path = get_unique_pull_path(b'perfdiscovery', repo, ui, path)[0]
1139 path = get_unique_pull_path(b'perfdiscovery', repo, ui, path)[0]
1141 except ImportError:
1140 except ImportError:
1142 path = ui.expandpath(path)
1141 path = ui.expandpath(path)
1143
1142
1144 def s():
1143 def s():
1145 repos[1] = hg.peer(ui, opts, path)
1144 repos[1] = hg.peer(ui, opts, path)
1146
1145
1147 def d():
1146 def d():
1148 setdiscovery.findcommonheads(ui, *repos)
1147 setdiscovery.findcommonheads(ui, *repos)
1149
1148
1150 timer(d, setup=s)
1149 timer(d, setup=s)
1151 fm.end()
1150 fm.end()
1152
1151
1153
1152
1154 @command(
1153 @command(
1155 b'perf::bookmarks|perfbookmarks',
1154 b'perf::bookmarks|perfbookmarks',
1156 formatteropts
1155 formatteropts
1157 + [
1156 + [
1158 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
1157 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
1159 ],
1158 ],
1160 )
1159 )
1161 def perfbookmarks(ui, repo, **opts):
1160 def perfbookmarks(ui, repo, **opts):
1162 """benchmark parsing bookmarks from disk to memory"""
1161 """benchmark parsing bookmarks from disk to memory"""
1163 opts = _byteskwargs(opts)
1162 opts = _byteskwargs(opts)
1164 timer, fm = gettimer(ui, opts)
1163 timer, fm = gettimer(ui, opts)
1165
1164
1166 clearrevlogs = opts[b'clear_revlogs']
1165 clearrevlogs = opts[b'clear_revlogs']
1167
1166
1168 def s():
1167 def s():
1169 if clearrevlogs:
1168 if clearrevlogs:
1170 clearchangelog(repo)
1169 clearchangelog(repo)
1171 clearfilecache(repo, b'_bookmarks')
1170 clearfilecache(repo, b'_bookmarks')
1172
1171
1173 def d():
1172 def d():
1174 repo._bookmarks
1173 repo._bookmarks
1175
1174
1176 timer(d, setup=s)
1175 timer(d, setup=s)
1177 fm.end()
1176 fm.end()
1178
1177
1179
1178
1180 @command(
1179 @command(
1181 b'perf::bundle',
1180 b'perf::bundle',
1182 [
1181 [
1183 (
1182 (
1184 b'r',
1183 b'r',
1185 b'rev',
1184 b'rev',
1186 [],
1185 [],
1187 b'changesets to bundle',
1186 b'changesets to bundle',
1188 b'REV',
1187 b'REV',
1189 ),
1188 ),
1190 (
1189 (
1191 b't',
1190 b't',
1192 b'type',
1191 b'type',
1193 b'none',
1192 b'none',
1194 b'bundlespec to use (see `hg help bundlespec`)',
1193 b'bundlespec to use (see `hg help bundlespec`)',
1195 b'TYPE',
1194 b'TYPE',
1196 ),
1195 ),
1197 ]
1196 ]
1198 + formatteropts,
1197 + formatteropts,
1199 b'REVS',
1198 b'REVS',
1200 )
1199 )
1201 def perfbundle(ui, repo, *revs, **opts):
1200 def perfbundle(ui, repo, *revs, **opts):
1202 """benchmark the creation of a bundle from a repository
1201 """benchmark the creation of a bundle from a repository
1203
1202
1204 For now, this only supports "none" compression.
1203 For now, this only supports "none" compression.
1205 """
1204 """
1206 try:
1205 try:
1207 from mercurial import bundlecaches
1206 from mercurial import bundlecaches
1208
1207
1209 parsebundlespec = bundlecaches.parsebundlespec
1208 parsebundlespec = bundlecaches.parsebundlespec
1210 except ImportError:
1209 except ImportError:
1211 from mercurial import exchange
1210 from mercurial import exchange
1212
1211
1213 parsebundlespec = exchange.parsebundlespec
1212 parsebundlespec = exchange.parsebundlespec
1214
1213
1215 from mercurial import discovery
1214 from mercurial import discovery
1216 from mercurial import bundle2
1215 from mercurial import bundle2
1217
1216
1218 opts = _byteskwargs(opts)
1217 opts = _byteskwargs(opts)
1219 timer, fm = gettimer(ui, opts)
1218 timer, fm = gettimer(ui, opts)
1220
1219
1221 cl = repo.changelog
1220 cl = repo.changelog
1222 revs = list(revs)
1221 revs = list(revs)
1223 revs.extend(opts.get(b'rev', ()))
1222 revs.extend(opts.get(b'rev', ()))
1224 revs = scmutil.revrange(repo, revs)
1223 revs = scmutil.revrange(repo, revs)
1225 if not revs:
1224 if not revs:
1226 raise error.Abort(b"not revision specified")
1225 raise error.Abort(b"not revision specified")
1227 # make it a consistent set (ie: without topological gaps)
1226 # make it a consistent set (ie: without topological gaps)
1228 old_len = len(revs)
1227 old_len = len(revs)
1229 revs = list(repo.revs(b"%ld::%ld", revs, revs))
1228 revs = list(repo.revs(b"%ld::%ld", revs, revs))
1230 if old_len != len(revs):
1229 if old_len != len(revs):
1231 new_count = len(revs) - old_len
1230 new_count = len(revs) - old_len
1232 msg = b"add %d new revisions to make it a consistent set\n"
1231 msg = b"add %d new revisions to make it a consistent set\n"
1233 ui.write_err(msg % new_count)
1232 ui.write_err(msg % new_count)
1234
1233
1235 targets = [cl.node(r) for r in repo.revs(b"heads(::%ld)", revs)]
1234 targets = [cl.node(r) for r in repo.revs(b"heads(::%ld)", revs)]
1236 bases = [cl.node(r) for r in repo.revs(b"heads(::%ld - %ld)", revs, revs)]
1235 bases = [cl.node(r) for r in repo.revs(b"heads(::%ld - %ld)", revs, revs)]
1237 outgoing = discovery.outgoing(repo, bases, targets)
1236 outgoing = discovery.outgoing(repo, bases, targets)
1238
1237
1239 bundle_spec = opts.get(b'type')
1238 bundle_spec = opts.get(b'type')
1240
1239
1241 bundle_spec = parsebundlespec(repo, bundle_spec, strict=False)
1240 bundle_spec = parsebundlespec(repo, bundle_spec, strict=False)
1242
1241
1243 cgversion = bundle_spec.params.get(b"cg.version")
1242 cgversion = bundle_spec.params.get(b"cg.version")
1244 if cgversion is None:
1243 if cgversion is None:
1245 if bundle_spec.version == b'v1':
1244 if bundle_spec.version == b'v1':
1246 cgversion = b'01'
1245 cgversion = b'01'
1247 if bundle_spec.version == b'v2':
1246 if bundle_spec.version == b'v2':
1248 cgversion = b'02'
1247 cgversion = b'02'
1249 if cgversion not in changegroup.supportedoutgoingversions(repo):
1248 if cgversion not in changegroup.supportedoutgoingversions(repo):
1250 err = b"repository does not support bundle version %s"
1249 err = b"repository does not support bundle version %s"
1251 raise error.Abort(err % cgversion)
1250 raise error.Abort(err % cgversion)
1252
1251
1253 if cgversion == b'01': # bundle1
1252 if cgversion == b'01': # bundle1
1254 bversion = b'HG10' + bundle_spec.wirecompression
1253 bversion = b'HG10' + bundle_spec.wirecompression
1255 bcompression = None
1254 bcompression = None
1256 elif cgversion in (b'02', b'03'):
1255 elif cgversion in (b'02', b'03'):
1257 bversion = b'HG20'
1256 bversion = b'HG20'
1258 bcompression = bundle_spec.wirecompression
1257 bcompression = bundle_spec.wirecompression
1259 else:
1258 else:
1260 err = b'perf::bundle: unexpected changegroup version %s'
1259 err = b'perf::bundle: unexpected changegroup version %s'
1261 raise error.ProgrammingError(err % cgversion)
1260 raise error.ProgrammingError(err % cgversion)
1262
1261
1263 if bcompression is None:
1262 if bcompression is None:
1264 bcompression = b'UN'
1263 bcompression = b'UN'
1265
1264
1266 if bcompression != b'UN':
1265 if bcompression != b'UN':
1267 err = b'perf::bundle: compression currently unsupported: %s'
1266 err = b'perf::bundle: compression currently unsupported: %s'
1268 raise error.ProgrammingError(err % bcompression)
1267 raise error.ProgrammingError(err % bcompression)
1269
1268
1270 def do_bundle():
1269 def do_bundle():
1271 bundle2.writenewbundle(
1270 bundle2.writenewbundle(
1272 ui,
1271 ui,
1273 repo,
1272 repo,
1274 b'perf::bundle',
1273 b'perf::bundle',
1275 os.devnull,
1274 os.devnull,
1276 bversion,
1275 bversion,
1277 outgoing,
1276 outgoing,
1278 bundle_spec.params,
1277 bundle_spec.params,
1279 )
1278 )
1280
1279
1281 timer(do_bundle)
1280 timer(do_bundle)
1282 fm.end()
1281 fm.end()
1283
1282
1284
1283
1285 @command(b'perf::bundleread|perfbundleread', formatteropts, b'BUNDLE')
1284 @command(b'perf::bundleread|perfbundleread', formatteropts, b'BUNDLE')
1286 def perfbundleread(ui, repo, bundlepath, **opts):
1285 def perfbundleread(ui, repo, bundlepath, **opts):
1287 """Benchmark reading of bundle files.
1286 """Benchmark reading of bundle files.
1288
1287
1289 This command is meant to isolate the I/O part of bundle reading as
1288 This command is meant to isolate the I/O part of bundle reading as
1290 much as possible.
1289 much as possible.
1291 """
1290 """
1292 from mercurial import (
1291 from mercurial import (
1293 bundle2,
1292 bundle2,
1294 exchange,
1293 exchange,
1295 streamclone,
1294 streamclone,
1296 )
1295 )
1297
1296
1298 opts = _byteskwargs(opts)
1297 opts = _byteskwargs(opts)
1299
1298
1300 def makebench(fn):
1299 def makebench(fn):
1301 def run():
1300 def run():
1302 with open(bundlepath, b'rb') as fh:
1301 with open(bundlepath, b'rb') as fh:
1303 bundle = exchange.readbundle(ui, fh, bundlepath)
1302 bundle = exchange.readbundle(ui, fh, bundlepath)
1304 fn(bundle)
1303 fn(bundle)
1305
1304
1306 return run
1305 return run
1307
1306
1308 def makereadnbytes(size):
1307 def makereadnbytes(size):
1309 def run():
1308 def run():
1310 with open(bundlepath, b'rb') as fh:
1309 with open(bundlepath, b'rb') as fh:
1311 bundle = exchange.readbundle(ui, fh, bundlepath)
1310 bundle = exchange.readbundle(ui, fh, bundlepath)
1312 while bundle.read(size):
1311 while bundle.read(size):
1313 pass
1312 pass
1314
1313
1315 return run
1314 return run
1316
1315
1317 def makestdioread(size):
1316 def makestdioread(size):
1318 def run():
1317 def run():
1319 with open(bundlepath, b'rb') as fh:
1318 with open(bundlepath, b'rb') as fh:
1320 while fh.read(size):
1319 while fh.read(size):
1321 pass
1320 pass
1322
1321
1323 return run
1322 return run
1324
1323
1325 # bundle1
1324 # bundle1
1326
1325
1327 def deltaiter(bundle):
1326 def deltaiter(bundle):
1328 for delta in bundle.deltaiter():
1327 for delta in bundle.deltaiter():
1329 pass
1328 pass
1330
1329
1331 def iterchunks(bundle):
1330 def iterchunks(bundle):
1332 for chunk in bundle.getchunks():
1331 for chunk in bundle.getchunks():
1333 pass
1332 pass
1334
1333
1335 # bundle2
1334 # bundle2
1336
1335
1337 def forwardchunks(bundle):
1336 def forwardchunks(bundle):
1338 for chunk in bundle._forwardchunks():
1337 for chunk in bundle._forwardchunks():
1339 pass
1338 pass
1340
1339
1341 def iterparts(bundle):
1340 def iterparts(bundle):
1342 for part in bundle.iterparts():
1341 for part in bundle.iterparts():
1343 pass
1342 pass
1344
1343
1345 def iterpartsseekable(bundle):
1344 def iterpartsseekable(bundle):
1346 for part in bundle.iterparts(seekable=True):
1345 for part in bundle.iterparts(seekable=True):
1347 pass
1346 pass
1348
1347
1349 def seek(bundle):
1348 def seek(bundle):
1350 for part in bundle.iterparts(seekable=True):
1349 for part in bundle.iterparts(seekable=True):
1351 part.seek(0, os.SEEK_END)
1350 part.seek(0, os.SEEK_END)
1352
1351
1353 def makepartreadnbytes(size):
1352 def makepartreadnbytes(size):
1354 def run():
1353 def run():
1355 with open(bundlepath, b'rb') as fh:
1354 with open(bundlepath, b'rb') as fh:
1356 bundle = exchange.readbundle(ui, fh, bundlepath)
1355 bundle = exchange.readbundle(ui, fh, bundlepath)
1357 for part in bundle.iterparts():
1356 for part in bundle.iterparts():
1358 while part.read(size):
1357 while part.read(size):
1359 pass
1358 pass
1360
1359
1361 return run
1360 return run
1362
1361
1363 benches = [
1362 benches = [
1364 (makestdioread(8192), b'read(8k)'),
1363 (makestdioread(8192), b'read(8k)'),
1365 (makestdioread(16384), b'read(16k)'),
1364 (makestdioread(16384), b'read(16k)'),
1366 (makestdioread(32768), b'read(32k)'),
1365 (makestdioread(32768), b'read(32k)'),
1367 (makestdioread(131072), b'read(128k)'),
1366 (makestdioread(131072), b'read(128k)'),
1368 ]
1367 ]
1369
1368
1370 with open(bundlepath, b'rb') as fh:
1369 with open(bundlepath, b'rb') as fh:
1371 bundle = exchange.readbundle(ui, fh, bundlepath)
1370 bundle = exchange.readbundle(ui, fh, bundlepath)
1372
1371
1373 if isinstance(bundle, changegroup.cg1unpacker):
1372 if isinstance(bundle, changegroup.cg1unpacker):
1374 benches.extend(
1373 benches.extend(
1375 [
1374 [
1376 (makebench(deltaiter), b'cg1 deltaiter()'),
1375 (makebench(deltaiter), b'cg1 deltaiter()'),
1377 (makebench(iterchunks), b'cg1 getchunks()'),
1376 (makebench(iterchunks), b'cg1 getchunks()'),
1378 (makereadnbytes(8192), b'cg1 read(8k)'),
1377 (makereadnbytes(8192), b'cg1 read(8k)'),
1379 (makereadnbytes(16384), b'cg1 read(16k)'),
1378 (makereadnbytes(16384), b'cg1 read(16k)'),
1380 (makereadnbytes(32768), b'cg1 read(32k)'),
1379 (makereadnbytes(32768), b'cg1 read(32k)'),
1381 (makereadnbytes(131072), b'cg1 read(128k)'),
1380 (makereadnbytes(131072), b'cg1 read(128k)'),
1382 ]
1381 ]
1383 )
1382 )
1384 elif isinstance(bundle, bundle2.unbundle20):
1383 elif isinstance(bundle, bundle2.unbundle20):
1385 benches.extend(
1384 benches.extend(
1386 [
1385 [
1387 (makebench(forwardchunks), b'bundle2 forwardchunks()'),
1386 (makebench(forwardchunks), b'bundle2 forwardchunks()'),
1388 (makebench(iterparts), b'bundle2 iterparts()'),
1387 (makebench(iterparts), b'bundle2 iterparts()'),
1389 (
1388 (
1390 makebench(iterpartsseekable),
1389 makebench(iterpartsseekable),
1391 b'bundle2 iterparts() seekable',
1390 b'bundle2 iterparts() seekable',
1392 ),
1391 ),
1393 (makebench(seek), b'bundle2 part seek()'),
1392 (makebench(seek), b'bundle2 part seek()'),
1394 (makepartreadnbytes(8192), b'bundle2 part read(8k)'),
1393 (makepartreadnbytes(8192), b'bundle2 part read(8k)'),
1395 (makepartreadnbytes(16384), b'bundle2 part read(16k)'),
1394 (makepartreadnbytes(16384), b'bundle2 part read(16k)'),
1396 (makepartreadnbytes(32768), b'bundle2 part read(32k)'),
1395 (makepartreadnbytes(32768), b'bundle2 part read(32k)'),
1397 (makepartreadnbytes(131072), b'bundle2 part read(128k)'),
1396 (makepartreadnbytes(131072), b'bundle2 part read(128k)'),
1398 ]
1397 ]
1399 )
1398 )
1400 elif isinstance(bundle, streamclone.streamcloneapplier):
1399 elif isinstance(bundle, streamclone.streamcloneapplier):
1401 raise error.Abort(b'stream clone bundles not supported')
1400 raise error.Abort(b'stream clone bundles not supported')
1402 else:
1401 else:
1403 raise error.Abort(b'unhandled bundle type: %s' % type(bundle))
1402 raise error.Abort(b'unhandled bundle type: %s' % type(bundle))
1404
1403
1405 for fn, title in benches:
1404 for fn, title in benches:
1406 timer, fm = gettimer(ui, opts)
1405 timer, fm = gettimer(ui, opts)
1407 timer(fn, title=title)
1406 timer(fn, title=title)
1408 fm.end()
1407 fm.end()
1409
1408
1410
1409
1411 @command(
1410 @command(
1412 b'perf::changegroupchangelog|perfchangegroupchangelog',
1411 b'perf::changegroupchangelog|perfchangegroupchangelog',
1413 formatteropts
1412 formatteropts
1414 + [
1413 + [
1415 (b'', b'cgversion', b'02', b'changegroup version'),
1414 (b'', b'cgversion', b'02', b'changegroup version'),
1416 (b'r', b'rev', b'', b'revisions to add to changegroup'),
1415 (b'r', b'rev', b'', b'revisions to add to changegroup'),
1417 ],
1416 ],
1418 )
1417 )
1419 def perfchangegroupchangelog(ui, repo, cgversion=b'02', rev=None, **opts):
1418 def perfchangegroupchangelog(ui, repo, cgversion=b'02', rev=None, **opts):
1420 """Benchmark producing a changelog group for a changegroup.
1419 """Benchmark producing a changelog group for a changegroup.
1421
1420
1422 This measures the time spent processing the changelog during a
1421 This measures the time spent processing the changelog during a
1423 bundle operation. This occurs during `hg bundle` and on a server
1422 bundle operation. This occurs during `hg bundle` and on a server
1424 processing a `getbundle` wire protocol request (handles clones
1423 processing a `getbundle` wire protocol request (handles clones
1425 and pull requests).
1424 and pull requests).
1426
1425
1427 By default, all revisions are added to the changegroup.
1426 By default, all revisions are added to the changegroup.
1428 """
1427 """
1429 opts = _byteskwargs(opts)
1428 opts = _byteskwargs(opts)
1430 cl = repo.changelog
1429 cl = repo.changelog
1431 nodes = [cl.lookup(r) for r in repo.revs(rev or b'all()')]
1430 nodes = [cl.lookup(r) for r in repo.revs(rev or b'all()')]
1432 bundler = changegroup.getbundler(cgversion, repo)
1431 bundler = changegroup.getbundler(cgversion, repo)
1433
1432
1434 def d():
1433 def d():
1435 state, chunks = bundler._generatechangelog(cl, nodes)
1434 state, chunks = bundler._generatechangelog(cl, nodes)
1436 for chunk in chunks:
1435 for chunk in chunks:
1437 pass
1436 pass
1438
1437
1439 timer, fm = gettimer(ui, opts)
1438 timer, fm = gettimer(ui, opts)
1440
1439
1441 # Terminal printing can interfere with timing. So disable it.
1440 # Terminal printing can interfere with timing. So disable it.
1442 with ui.configoverride({(b'progress', b'disable'): True}):
1441 with ui.configoverride({(b'progress', b'disable'): True}):
1443 timer(d)
1442 timer(d)
1444
1443
1445 fm.end()
1444 fm.end()
1446
1445
1447
1446
1448 @command(b'perf::dirs|perfdirs', formatteropts)
1447 @command(b'perf::dirs|perfdirs', formatteropts)
1449 def perfdirs(ui, repo, **opts):
1448 def perfdirs(ui, repo, **opts):
1450 opts = _byteskwargs(opts)
1449 opts = _byteskwargs(opts)
1451 timer, fm = gettimer(ui, opts)
1450 timer, fm = gettimer(ui, opts)
1452 dirstate = repo.dirstate
1451 dirstate = repo.dirstate
1453 b'a' in dirstate
1452 b'a' in dirstate
1454
1453
1455 def d():
1454 def d():
1456 dirstate.hasdir(b'a')
1455 dirstate.hasdir(b'a')
1457 try:
1456 try:
1458 del dirstate._map._dirs
1457 del dirstate._map._dirs
1459 except AttributeError:
1458 except AttributeError:
1460 pass
1459 pass
1461
1460
1462 timer(d)
1461 timer(d)
1463 fm.end()
1462 fm.end()
1464
1463
1465
1464
1466 @command(
1465 @command(
1467 b'perf::dirstate|perfdirstate',
1466 b'perf::dirstate|perfdirstate',
1468 [
1467 [
1469 (
1468 (
1470 b'',
1469 b'',
1471 b'iteration',
1470 b'iteration',
1472 None,
1471 None,
1473 b'benchmark a full iteration for the dirstate',
1472 b'benchmark a full iteration for the dirstate',
1474 ),
1473 ),
1475 (
1474 (
1476 b'',
1475 b'',
1477 b'contains',
1476 b'contains',
1478 None,
1477 None,
1479 b'benchmark a large amount of `nf in dirstate` calls',
1478 b'benchmark a large amount of `nf in dirstate` calls',
1480 ),
1479 ),
1481 ]
1480 ]
1482 + formatteropts,
1481 + formatteropts,
1483 )
1482 )
1484 def perfdirstate(ui, repo, **opts):
1483 def perfdirstate(ui, repo, **opts):
1485 """benchmap the time of various distate operations
1484 """benchmap the time of various distate operations
1486
1485
1487 By default benchmark the time necessary to load a dirstate from scratch.
1486 By default benchmark the time necessary to load a dirstate from scratch.
1488 The dirstate is loaded to the point were a "contains" request can be
1487 The dirstate is loaded to the point were a "contains" request can be
1489 answered.
1488 answered.
1490 """
1489 """
1491 opts = _byteskwargs(opts)
1490 opts = _byteskwargs(opts)
1492 timer, fm = gettimer(ui, opts)
1491 timer, fm = gettimer(ui, opts)
1493 b"a" in repo.dirstate
1492 b"a" in repo.dirstate
1494
1493
1495 if opts[b'iteration'] and opts[b'contains']:
1494 if opts[b'iteration'] and opts[b'contains']:
1496 msg = b'only specify one of --iteration or --contains'
1495 msg = b'only specify one of --iteration or --contains'
1497 raise error.Abort(msg)
1496 raise error.Abort(msg)
1498
1497
1499 if opts[b'iteration']:
1498 if opts[b'iteration']:
1500 setup = None
1499 setup = None
1501 dirstate = repo.dirstate
1500 dirstate = repo.dirstate
1502
1501
1503 def d():
1502 def d():
1504 for f in dirstate:
1503 for f in dirstate:
1505 pass
1504 pass
1506
1505
1507 elif opts[b'contains']:
1506 elif opts[b'contains']:
1508 setup = None
1507 setup = None
1509 dirstate = repo.dirstate
1508 dirstate = repo.dirstate
1510 allfiles = list(dirstate)
1509 allfiles = list(dirstate)
1511 # also add file path that will be "missing" from the dirstate
1510 # also add file path that will be "missing" from the dirstate
1512 allfiles.extend([f[::-1] for f in allfiles])
1511 allfiles.extend([f[::-1] for f in allfiles])
1513
1512
1514 def d():
1513 def d():
1515 for f in allfiles:
1514 for f in allfiles:
1516 f in dirstate
1515 f in dirstate
1517
1516
1518 else:
1517 else:
1519
1518
1520 def setup():
1519 def setup():
1521 repo.dirstate.invalidate()
1520 repo.dirstate.invalidate()
1522
1521
1523 def d():
1522 def d():
1524 b"a" in repo.dirstate
1523 b"a" in repo.dirstate
1525
1524
1526 timer(d, setup=setup)
1525 timer(d, setup=setup)
1527 fm.end()
1526 fm.end()
1528
1527
1529
1528
1530 @command(b'perf::dirstatedirs|perfdirstatedirs', formatteropts)
1529 @command(b'perf::dirstatedirs|perfdirstatedirs', formatteropts)
1531 def perfdirstatedirs(ui, repo, **opts):
1530 def perfdirstatedirs(ui, repo, **opts):
1532 """benchmap a 'dirstate.hasdir' call from an empty `dirs` cache"""
1531 """benchmap a 'dirstate.hasdir' call from an empty `dirs` cache"""
1533 opts = _byteskwargs(opts)
1532 opts = _byteskwargs(opts)
1534 timer, fm = gettimer(ui, opts)
1533 timer, fm = gettimer(ui, opts)
1535 repo.dirstate.hasdir(b"a")
1534 repo.dirstate.hasdir(b"a")
1536
1535
1537 def setup():
1536 def setup():
1538 try:
1537 try:
1539 del repo.dirstate._map._dirs
1538 del repo.dirstate._map._dirs
1540 except AttributeError:
1539 except AttributeError:
1541 pass
1540 pass
1542
1541
1543 def d():
1542 def d():
1544 repo.dirstate.hasdir(b"a")
1543 repo.dirstate.hasdir(b"a")
1545
1544
1546 timer(d, setup=setup)
1545 timer(d, setup=setup)
1547 fm.end()
1546 fm.end()
1548
1547
1549
1548
1550 @command(b'perf::dirstatefoldmap|perfdirstatefoldmap', formatteropts)
1549 @command(b'perf::dirstatefoldmap|perfdirstatefoldmap', formatteropts)
1551 def perfdirstatefoldmap(ui, repo, **opts):
1550 def perfdirstatefoldmap(ui, repo, **opts):
1552 """benchmap a `dirstate._map.filefoldmap.get()` request
1551 """benchmap a `dirstate._map.filefoldmap.get()` request
1553
1552
1554 The dirstate filefoldmap cache is dropped between every request.
1553 The dirstate filefoldmap cache is dropped between every request.
1555 """
1554 """
1556 opts = _byteskwargs(opts)
1555 opts = _byteskwargs(opts)
1557 timer, fm = gettimer(ui, opts)
1556 timer, fm = gettimer(ui, opts)
1558 dirstate = repo.dirstate
1557 dirstate = repo.dirstate
1559 dirstate._map.filefoldmap.get(b'a')
1558 dirstate._map.filefoldmap.get(b'a')
1560
1559
1561 def setup():
1560 def setup():
1562 del dirstate._map.filefoldmap
1561 del dirstate._map.filefoldmap
1563
1562
1564 def d():
1563 def d():
1565 dirstate._map.filefoldmap.get(b'a')
1564 dirstate._map.filefoldmap.get(b'a')
1566
1565
1567 timer(d, setup=setup)
1566 timer(d, setup=setup)
1568 fm.end()
1567 fm.end()
1569
1568
1570
1569
1571 @command(b'perf::dirfoldmap|perfdirfoldmap', formatteropts)
1570 @command(b'perf::dirfoldmap|perfdirfoldmap', formatteropts)
1572 def perfdirfoldmap(ui, repo, **opts):
1571 def perfdirfoldmap(ui, repo, **opts):
1573 """benchmap a `dirstate._map.dirfoldmap.get()` request
1572 """benchmap a `dirstate._map.dirfoldmap.get()` request
1574
1573
1575 The dirstate dirfoldmap cache is dropped between every request.
1574 The dirstate dirfoldmap cache is dropped between every request.
1576 """
1575 """
1577 opts = _byteskwargs(opts)
1576 opts = _byteskwargs(opts)
1578 timer, fm = gettimer(ui, opts)
1577 timer, fm = gettimer(ui, opts)
1579 dirstate = repo.dirstate
1578 dirstate = repo.dirstate
1580 dirstate._map.dirfoldmap.get(b'a')
1579 dirstate._map.dirfoldmap.get(b'a')
1581
1580
1582 def setup():
1581 def setup():
1583 del dirstate._map.dirfoldmap
1582 del dirstate._map.dirfoldmap
1584 try:
1583 try:
1585 del dirstate._map._dirs
1584 del dirstate._map._dirs
1586 except AttributeError:
1585 except AttributeError:
1587 pass
1586 pass
1588
1587
1589 def d():
1588 def d():
1590 dirstate._map.dirfoldmap.get(b'a')
1589 dirstate._map.dirfoldmap.get(b'a')
1591
1590
1592 timer(d, setup=setup)
1591 timer(d, setup=setup)
1593 fm.end()
1592 fm.end()
1594
1593
1595
1594
1596 @command(b'perf::dirstatewrite|perfdirstatewrite', formatteropts)
1595 @command(b'perf::dirstatewrite|perfdirstatewrite', formatteropts)
1597 def perfdirstatewrite(ui, repo, **opts):
1596 def perfdirstatewrite(ui, repo, **opts):
1598 """benchmap the time it take to write a dirstate on disk"""
1597 """benchmap the time it take to write a dirstate on disk"""
1599 opts = _byteskwargs(opts)
1598 opts = _byteskwargs(opts)
1600 timer, fm = gettimer(ui, opts)
1599 timer, fm = gettimer(ui, opts)
1601 ds = repo.dirstate
1600 ds = repo.dirstate
1602 b"a" in ds
1601 b"a" in ds
1603
1602
1604 def setup():
1603 def setup():
1605 ds._dirty = True
1604 ds._dirty = True
1606
1605
1607 def d():
1606 def d():
1608 ds.write(repo.currenttransaction())
1607 ds.write(repo.currenttransaction())
1609
1608
1610 with repo.wlock():
1609 with repo.wlock():
1611 timer(d, setup=setup)
1610 timer(d, setup=setup)
1612 fm.end()
1611 fm.end()
1613
1612
1614
1613
1615 def _getmergerevs(repo, opts):
1614 def _getmergerevs(repo, opts):
1616 """parse command argument to return rev involved in merge
1615 """parse command argument to return rev involved in merge
1617
1616
1618 input: options dictionnary with `rev`, `from` and `bse`
1617 input: options dictionnary with `rev`, `from` and `bse`
1619 output: (localctx, otherctx, basectx)
1618 output: (localctx, otherctx, basectx)
1620 """
1619 """
1621 if opts[b'from']:
1620 if opts[b'from']:
1622 fromrev = scmutil.revsingle(repo, opts[b'from'])
1621 fromrev = scmutil.revsingle(repo, opts[b'from'])
1623 wctx = repo[fromrev]
1622 wctx = repo[fromrev]
1624 else:
1623 else:
1625 wctx = repo[None]
1624 wctx = repo[None]
1626 # we don't want working dir files to be stat'd in the benchmark, so
1625 # we don't want working dir files to be stat'd in the benchmark, so
1627 # prime that cache
1626 # prime that cache
1628 wctx.dirty()
1627 wctx.dirty()
1629 rctx = scmutil.revsingle(repo, opts[b'rev'], opts[b'rev'])
1628 rctx = scmutil.revsingle(repo, opts[b'rev'], opts[b'rev'])
1630 if opts[b'base']:
1629 if opts[b'base']:
1631 fromrev = scmutil.revsingle(repo, opts[b'base'])
1630 fromrev = scmutil.revsingle(repo, opts[b'base'])
1632 ancestor = repo[fromrev]
1631 ancestor = repo[fromrev]
1633 else:
1632 else:
1634 ancestor = wctx.ancestor(rctx)
1633 ancestor = wctx.ancestor(rctx)
1635 return (wctx, rctx, ancestor)
1634 return (wctx, rctx, ancestor)
1636
1635
1637
1636
1638 @command(
1637 @command(
1639 b'perf::mergecalculate|perfmergecalculate',
1638 b'perf::mergecalculate|perfmergecalculate',
1640 [
1639 [
1641 (b'r', b'rev', b'.', b'rev to merge against'),
1640 (b'r', b'rev', b'.', b'rev to merge against'),
1642 (b'', b'from', b'', b'rev to merge from'),
1641 (b'', b'from', b'', b'rev to merge from'),
1643 (b'', b'base', b'', b'the revision to use as base'),
1642 (b'', b'base', b'', b'the revision to use as base'),
1644 ]
1643 ]
1645 + formatteropts,
1644 + formatteropts,
1646 )
1645 )
1647 def perfmergecalculate(ui, repo, **opts):
1646 def perfmergecalculate(ui, repo, **opts):
1648 opts = _byteskwargs(opts)
1647 opts = _byteskwargs(opts)
1649 timer, fm = gettimer(ui, opts)
1648 timer, fm = gettimer(ui, opts)
1650
1649
1651 wctx, rctx, ancestor = _getmergerevs(repo, opts)
1650 wctx, rctx, ancestor = _getmergerevs(repo, opts)
1652
1651
1653 def d():
1652 def d():
1654 # acceptremote is True because we don't want prompts in the middle of
1653 # acceptremote is True because we don't want prompts in the middle of
1655 # our benchmark
1654 # our benchmark
1656 merge.calculateupdates(
1655 merge.calculateupdates(
1657 repo,
1656 repo,
1658 wctx,
1657 wctx,
1659 rctx,
1658 rctx,
1660 [ancestor],
1659 [ancestor],
1661 branchmerge=False,
1660 branchmerge=False,
1662 force=False,
1661 force=False,
1663 acceptremote=True,
1662 acceptremote=True,
1664 followcopies=True,
1663 followcopies=True,
1665 )
1664 )
1666
1665
1667 timer(d)
1666 timer(d)
1668 fm.end()
1667 fm.end()
1669
1668
1670
1669
1671 @command(
1670 @command(
1672 b'perf::mergecopies|perfmergecopies',
1671 b'perf::mergecopies|perfmergecopies',
1673 [
1672 [
1674 (b'r', b'rev', b'.', b'rev to merge against'),
1673 (b'r', b'rev', b'.', b'rev to merge against'),
1675 (b'', b'from', b'', b'rev to merge from'),
1674 (b'', b'from', b'', b'rev to merge from'),
1676 (b'', b'base', b'', b'the revision to use as base'),
1675 (b'', b'base', b'', b'the revision to use as base'),
1677 ]
1676 ]
1678 + formatteropts,
1677 + formatteropts,
1679 )
1678 )
1680 def perfmergecopies(ui, repo, **opts):
1679 def perfmergecopies(ui, repo, **opts):
1681 """measure runtime of `copies.mergecopies`"""
1680 """measure runtime of `copies.mergecopies`"""
1682 opts = _byteskwargs(opts)
1681 opts = _byteskwargs(opts)
1683 timer, fm = gettimer(ui, opts)
1682 timer, fm = gettimer(ui, opts)
1684 wctx, rctx, ancestor = _getmergerevs(repo, opts)
1683 wctx, rctx, ancestor = _getmergerevs(repo, opts)
1685
1684
1686 def d():
1685 def d():
1687 # acceptremote is True because we don't want prompts in the middle of
1686 # acceptremote is True because we don't want prompts in the middle of
1688 # our benchmark
1687 # our benchmark
1689 copies.mergecopies(repo, wctx, rctx, ancestor)
1688 copies.mergecopies(repo, wctx, rctx, ancestor)
1690
1689
1691 timer(d)
1690 timer(d)
1692 fm.end()
1691 fm.end()
1693
1692
1694
1693
1695 @command(b'perf::pathcopies|perfpathcopies', [], b"REV REV")
1694 @command(b'perf::pathcopies|perfpathcopies', [], b"REV REV")
1696 def perfpathcopies(ui, repo, rev1, rev2, **opts):
1695 def perfpathcopies(ui, repo, rev1, rev2, **opts):
1697 """benchmark the copy tracing logic"""
1696 """benchmark the copy tracing logic"""
1698 opts = _byteskwargs(opts)
1697 opts = _byteskwargs(opts)
1699 timer, fm = gettimer(ui, opts)
1698 timer, fm = gettimer(ui, opts)
1700 ctx1 = scmutil.revsingle(repo, rev1, rev1)
1699 ctx1 = scmutil.revsingle(repo, rev1, rev1)
1701 ctx2 = scmutil.revsingle(repo, rev2, rev2)
1700 ctx2 = scmutil.revsingle(repo, rev2, rev2)
1702
1701
1703 def d():
1702 def d():
1704 copies.pathcopies(ctx1, ctx2)
1703 copies.pathcopies(ctx1, ctx2)
1705
1704
1706 timer(d)
1705 timer(d)
1707 fm.end()
1706 fm.end()
1708
1707
1709
1708
1710 @command(
1709 @command(
1711 b'perf::phases|perfphases',
1710 b'perf::phases|perfphases',
1712 [
1711 [
1713 (b'', b'full', False, b'include file reading time too'),
1712 (b'', b'full', False, b'include file reading time too'),
1714 ],
1713 ],
1715 b"",
1714 b"",
1716 )
1715 )
1717 def perfphases(ui, repo, **opts):
1716 def perfphases(ui, repo, **opts):
1718 """benchmark phasesets computation"""
1717 """benchmark phasesets computation"""
1719 opts = _byteskwargs(opts)
1718 opts = _byteskwargs(opts)
1720 timer, fm = gettimer(ui, opts)
1719 timer, fm = gettimer(ui, opts)
1721 _phases = repo._phasecache
1720 _phases = repo._phasecache
1722 full = opts.get(b'full')
1721 full = opts.get(b'full')
1723
1722
1724 def d():
1723 def d():
1725 phases = _phases
1724 phases = _phases
1726 if full:
1725 if full:
1727 clearfilecache(repo, b'_phasecache')
1726 clearfilecache(repo, b'_phasecache')
1728 phases = repo._phasecache
1727 phases = repo._phasecache
1729 phases.invalidate()
1728 phases.invalidate()
1730 phases.loadphaserevs(repo)
1729 phases.loadphaserevs(repo)
1731
1730
1732 timer(d)
1731 timer(d)
1733 fm.end()
1732 fm.end()
1734
1733
1735
1734
1736 @command(b'perf::phasesremote|perfphasesremote', [], b"[DEST]")
1735 @command(b'perf::phasesremote|perfphasesremote', [], b"[DEST]")
1737 def perfphasesremote(ui, repo, dest=None, **opts):
1736 def perfphasesremote(ui, repo, dest=None, **opts):
1738 """benchmark time needed to analyse phases of the remote server"""
1737 """benchmark time needed to analyse phases of the remote server"""
1739 from mercurial.node import bin
1738 from mercurial.node import bin
1740 from mercurial import (
1739 from mercurial import (
1741 exchange,
1740 exchange,
1742 hg,
1741 hg,
1743 phases,
1742 phases,
1744 )
1743 )
1745
1744
1746 opts = _byteskwargs(opts)
1745 opts = _byteskwargs(opts)
1747 timer, fm = gettimer(ui, opts)
1746 timer, fm = gettimer(ui, opts)
1748
1747
1749 path = ui.getpath(dest, default=(b'default-push', b'default'))
1748 path = ui.getpath(dest, default=(b'default-push', b'default'))
1750 if not path:
1749 if not path:
1751 raise error.Abort(
1750 raise error.Abort(
1752 b'default repository not configured!',
1751 b'default repository not configured!',
1753 hint=b"see 'hg help config.paths'",
1752 hint=b"see 'hg help config.paths'",
1754 )
1753 )
1755 if util.safehasattr(path, 'main_path'):
1754 if util.safehasattr(path, 'main_path'):
1756 path = path.get_push_variant()
1755 path = path.get_push_variant()
1757 dest = path.loc
1756 dest = path.loc
1758 else:
1757 else:
1759 dest = path.pushloc or path.loc
1758 dest = path.pushloc or path.loc
1760 ui.statusnoi18n(b'analysing phase of %s\n' % util.hidepassword(dest))
1759 ui.statusnoi18n(b'analysing phase of %s\n' % util.hidepassword(dest))
1761 other = hg.peer(repo, opts, dest)
1760 other = hg.peer(repo, opts, dest)
1762
1761
1763 # easier to perform discovery through the operation
1762 # easier to perform discovery through the operation
1764 op = exchange.pushoperation(repo, other)
1763 op = exchange.pushoperation(repo, other)
1765 exchange._pushdiscoverychangeset(op)
1764 exchange._pushdiscoverychangeset(op)
1766
1765
1767 remotesubset = op.fallbackheads
1766 remotesubset = op.fallbackheads
1768
1767
1769 with other.commandexecutor() as e:
1768 with other.commandexecutor() as e:
1770 remotephases = e.callcommand(
1769 remotephases = e.callcommand(
1771 b'listkeys', {b'namespace': b'phases'}
1770 b'listkeys', {b'namespace': b'phases'}
1772 ).result()
1771 ).result()
1773 del other
1772 del other
1774 publishing = remotephases.get(b'publishing', False)
1773 publishing = remotephases.get(b'publishing', False)
1775 if publishing:
1774 if publishing:
1776 ui.statusnoi18n(b'publishing: yes\n')
1775 ui.statusnoi18n(b'publishing: yes\n')
1777 else:
1776 else:
1778 ui.statusnoi18n(b'publishing: no\n')
1777 ui.statusnoi18n(b'publishing: no\n')
1779
1778
1780 has_node = getattr(repo.changelog.index, 'has_node', None)
1779 has_node = getattr(repo.changelog.index, 'has_node', None)
1781 if has_node is None:
1780 if has_node is None:
1782 has_node = repo.changelog.nodemap.__contains__
1781 has_node = repo.changelog.nodemap.__contains__
1783 nonpublishroots = 0
1782 nonpublishroots = 0
1784 for nhex, phase in remotephases.iteritems():
1783 for nhex, phase in remotephases.iteritems():
1785 if nhex == b'publishing': # ignore data related to publish option
1784 if nhex == b'publishing': # ignore data related to publish option
1786 continue
1785 continue
1787 node = bin(nhex)
1786 node = bin(nhex)
1788 if has_node(node) and int(phase):
1787 if has_node(node) and int(phase):
1789 nonpublishroots += 1
1788 nonpublishroots += 1
1790 ui.statusnoi18n(b'number of roots: %d\n' % len(remotephases))
1789 ui.statusnoi18n(b'number of roots: %d\n' % len(remotephases))
1791 ui.statusnoi18n(b'number of known non public roots: %d\n' % nonpublishroots)
1790 ui.statusnoi18n(b'number of known non public roots: %d\n' % nonpublishroots)
1792
1791
1793 def d():
1792 def d():
1794 phases.remotephasessummary(repo, remotesubset, remotephases)
1793 phases.remotephasessummary(repo, remotesubset, remotephases)
1795
1794
1796 timer(d)
1795 timer(d)
1797 fm.end()
1796 fm.end()
1798
1797
1799
1798
1800 @command(
1799 @command(
1801 b'perf::manifest|perfmanifest',
1800 b'perf::manifest|perfmanifest',
1802 [
1801 [
1803 (b'm', b'manifest-rev', False, b'Look up a manifest node revision'),
1802 (b'm', b'manifest-rev', False, b'Look up a manifest node revision'),
1804 (b'', b'clear-disk', False, b'clear on-disk caches too'),
1803 (b'', b'clear-disk', False, b'clear on-disk caches too'),
1805 ]
1804 ]
1806 + formatteropts,
1805 + formatteropts,
1807 b'REV|NODE',
1806 b'REV|NODE',
1808 )
1807 )
1809 def perfmanifest(ui, repo, rev, manifest_rev=False, clear_disk=False, **opts):
1808 def perfmanifest(ui, repo, rev, manifest_rev=False, clear_disk=False, **opts):
1810 """benchmark the time to read a manifest from disk and return a usable
1809 """benchmark the time to read a manifest from disk and return a usable
1811 dict-like object
1810 dict-like object
1812
1811
1813 Manifest caches are cleared before retrieval."""
1812 Manifest caches are cleared before retrieval."""
1814 opts = _byteskwargs(opts)
1813 opts = _byteskwargs(opts)
1815 timer, fm = gettimer(ui, opts)
1814 timer, fm = gettimer(ui, opts)
1816 if not manifest_rev:
1815 if not manifest_rev:
1817 ctx = scmutil.revsingle(repo, rev, rev)
1816 ctx = scmutil.revsingle(repo, rev, rev)
1818 t = ctx.manifestnode()
1817 t = ctx.manifestnode()
1819 else:
1818 else:
1820 from mercurial.node import bin
1819 from mercurial.node import bin
1821
1820
1822 if len(rev) == 40:
1821 if len(rev) == 40:
1823 t = bin(rev)
1822 t = bin(rev)
1824 else:
1823 else:
1825 try:
1824 try:
1826 rev = int(rev)
1825 rev = int(rev)
1827
1826
1828 if util.safehasattr(repo.manifestlog, b'getstorage'):
1827 if util.safehasattr(repo.manifestlog, b'getstorage'):
1829 t = repo.manifestlog.getstorage(b'').node(rev)
1828 t = repo.manifestlog.getstorage(b'').node(rev)
1830 else:
1829 else:
1831 t = repo.manifestlog._revlog.lookup(rev)
1830 t = repo.manifestlog._revlog.lookup(rev)
1832 except ValueError:
1831 except ValueError:
1833 raise error.Abort(
1832 raise error.Abort(
1834 b'manifest revision must be integer or full node'
1833 b'manifest revision must be integer or full node'
1835 )
1834 )
1836
1835
1837 def d():
1836 def d():
1838 repo.manifestlog.clearcaches(clear_persisted_data=clear_disk)
1837 repo.manifestlog.clearcaches(clear_persisted_data=clear_disk)
1839 repo.manifestlog[t].read()
1838 repo.manifestlog[t].read()
1840
1839
1841 timer(d)
1840 timer(d)
1842 fm.end()
1841 fm.end()
1843
1842
1844
1843
1845 @command(b'perf::changeset|perfchangeset', formatteropts)
1844 @command(b'perf::changeset|perfchangeset', formatteropts)
1846 def perfchangeset(ui, repo, rev, **opts):
1845 def perfchangeset(ui, repo, rev, **opts):
1847 opts = _byteskwargs(opts)
1846 opts = _byteskwargs(opts)
1848 timer, fm = gettimer(ui, opts)
1847 timer, fm = gettimer(ui, opts)
1849 n = scmutil.revsingle(repo, rev).node()
1848 n = scmutil.revsingle(repo, rev).node()
1850
1849
1851 def d():
1850 def d():
1852 repo.changelog.read(n)
1851 repo.changelog.read(n)
1853 # repo.changelog._cache = None
1852 # repo.changelog._cache = None
1854
1853
1855 timer(d)
1854 timer(d)
1856 fm.end()
1855 fm.end()
1857
1856
1858
1857
1859 @command(b'perf::ignore|perfignore', formatteropts)
1858 @command(b'perf::ignore|perfignore', formatteropts)
1860 def perfignore(ui, repo, **opts):
1859 def perfignore(ui, repo, **opts):
1861 """benchmark operation related to computing ignore"""
1860 """benchmark operation related to computing ignore"""
1862 opts = _byteskwargs(opts)
1861 opts = _byteskwargs(opts)
1863 timer, fm = gettimer(ui, opts)
1862 timer, fm = gettimer(ui, opts)
1864 dirstate = repo.dirstate
1863 dirstate = repo.dirstate
1865
1864
1866 def setupone():
1865 def setupone():
1867 dirstate.invalidate()
1866 dirstate.invalidate()
1868 clearfilecache(dirstate, b'_ignore')
1867 clearfilecache(dirstate, b'_ignore')
1869
1868
1870 def runone():
1869 def runone():
1871 dirstate._ignore
1870 dirstate._ignore
1872
1871
1873 timer(runone, setup=setupone, title=b"load")
1872 timer(runone, setup=setupone, title=b"load")
1874 fm.end()
1873 fm.end()
1875
1874
1876
1875
1877 @command(
1876 @command(
1878 b'perf::index|perfindex',
1877 b'perf::index|perfindex',
1879 [
1878 [
1880 (b'', b'rev', [], b'revision to be looked up (default tip)'),
1879 (b'', b'rev', [], b'revision to be looked up (default tip)'),
1881 (b'', b'no-lookup', None, b'do not revision lookup post creation'),
1880 (b'', b'no-lookup', None, b'do not revision lookup post creation'),
1882 ]
1881 ]
1883 + formatteropts,
1882 + formatteropts,
1884 )
1883 )
1885 def perfindex(ui, repo, **opts):
1884 def perfindex(ui, repo, **opts):
1886 """benchmark index creation time followed by a lookup
1885 """benchmark index creation time followed by a lookup
1887
1886
1888 The default is to look `tip` up. Depending on the index implementation,
1887 The default is to look `tip` up. Depending on the index implementation,
1889 the revision looked up can matters. For example, an implementation
1888 the revision looked up can matters. For example, an implementation
1890 scanning the index will have a faster lookup time for `--rev tip` than for
1889 scanning the index will have a faster lookup time for `--rev tip` than for
1891 `--rev 0`. The number of looked up revisions and their order can also
1890 `--rev 0`. The number of looked up revisions and their order can also
1892 matters.
1891 matters.
1893
1892
1894 Example of useful set to test:
1893 Example of useful set to test:
1895
1894
1896 * tip
1895 * tip
1897 * 0
1896 * 0
1898 * -10:
1897 * -10:
1899 * :10
1898 * :10
1900 * -10: + :10
1899 * -10: + :10
1901 * :10: + -10:
1900 * :10: + -10:
1902 * -10000:
1901 * -10000:
1903 * -10000: + 0
1902 * -10000: + 0
1904
1903
1905 It is not currently possible to check for lookup of a missing node. For
1904 It is not currently possible to check for lookup of a missing node. For
1906 deeper lookup benchmarking, checkout the `perfnodemap` command."""
1905 deeper lookup benchmarking, checkout the `perfnodemap` command."""
1907 import mercurial.revlog
1906 import mercurial.revlog
1908
1907
1909 opts = _byteskwargs(opts)
1908 opts = _byteskwargs(opts)
1910 timer, fm = gettimer(ui, opts)
1909 timer, fm = gettimer(ui, opts)
1911 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1910 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1912 if opts[b'no_lookup']:
1911 if opts[b'no_lookup']:
1913 if opts['rev']:
1912 if opts['rev']:
1914 raise error.Abort('--no-lookup and --rev are mutually exclusive')
1913 raise error.Abort('--no-lookup and --rev are mutually exclusive')
1915 nodes = []
1914 nodes = []
1916 elif not opts[b'rev']:
1915 elif not opts[b'rev']:
1917 nodes = [repo[b"tip"].node()]
1916 nodes = [repo[b"tip"].node()]
1918 else:
1917 else:
1919 revs = scmutil.revrange(repo, opts[b'rev'])
1918 revs = scmutil.revrange(repo, opts[b'rev'])
1920 cl = repo.changelog
1919 cl = repo.changelog
1921 nodes = [cl.node(r) for r in revs]
1920 nodes = [cl.node(r) for r in revs]
1922
1921
1923 unfi = repo.unfiltered()
1922 unfi = repo.unfiltered()
1924 # find the filecache func directly
1923 # find the filecache func directly
1925 # This avoid polluting the benchmark with the filecache logic
1924 # This avoid polluting the benchmark with the filecache logic
1926 makecl = unfi.__class__.changelog.func
1925 makecl = unfi.__class__.changelog.func
1927
1926
1928 def setup():
1927 def setup():
1929 # probably not necessary, but for good measure
1928 # probably not necessary, but for good measure
1930 clearchangelog(unfi)
1929 clearchangelog(unfi)
1931
1930
1932 def d():
1931 def d():
1933 cl = makecl(unfi)
1932 cl = makecl(unfi)
1934 for n in nodes:
1933 for n in nodes:
1935 cl.rev(n)
1934 cl.rev(n)
1936
1935
1937 timer(d, setup=setup)
1936 timer(d, setup=setup)
1938 fm.end()
1937 fm.end()
1939
1938
1940
1939
1941 @command(
1940 @command(
1942 b'perf::nodemap|perfnodemap',
1941 b'perf::nodemap|perfnodemap',
1943 [
1942 [
1944 (b'', b'rev', [], b'revision to be looked up (default tip)'),
1943 (b'', b'rev', [], b'revision to be looked up (default tip)'),
1945 (b'', b'clear-caches', True, b'clear revlog cache between calls'),
1944 (b'', b'clear-caches', True, b'clear revlog cache between calls'),
1946 ]
1945 ]
1947 + formatteropts,
1946 + formatteropts,
1948 )
1947 )
1949 def perfnodemap(ui, repo, **opts):
1948 def perfnodemap(ui, repo, **opts):
1950 """benchmark the time necessary to look up revision from a cold nodemap
1949 """benchmark the time necessary to look up revision from a cold nodemap
1951
1950
1952 Depending on the implementation, the amount and order of revision we look
1951 Depending on the implementation, the amount and order of revision we look
1953 up can varies. Example of useful set to test:
1952 up can varies. Example of useful set to test:
1954 * tip
1953 * tip
1955 * 0
1954 * 0
1956 * -10:
1955 * -10:
1957 * :10
1956 * :10
1958 * -10: + :10
1957 * -10: + :10
1959 * :10: + -10:
1958 * :10: + -10:
1960 * -10000:
1959 * -10000:
1961 * -10000: + 0
1960 * -10000: + 0
1962
1961
1963 The command currently focus on valid binary lookup. Benchmarking for
1962 The command currently focus on valid binary lookup. Benchmarking for
1964 hexlookup, prefix lookup and missing lookup would also be valuable.
1963 hexlookup, prefix lookup and missing lookup would also be valuable.
1965 """
1964 """
1966 import mercurial.revlog
1965 import mercurial.revlog
1967
1966
1968 opts = _byteskwargs(opts)
1967 opts = _byteskwargs(opts)
1969 timer, fm = gettimer(ui, opts)
1968 timer, fm = gettimer(ui, opts)
1970 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1969 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1971
1970
1972 unfi = repo.unfiltered()
1971 unfi = repo.unfiltered()
1973 clearcaches = opts[b'clear_caches']
1972 clearcaches = opts[b'clear_caches']
1974 # find the filecache func directly
1973 # find the filecache func directly
1975 # This avoid polluting the benchmark with the filecache logic
1974 # This avoid polluting the benchmark with the filecache logic
1976 makecl = unfi.__class__.changelog.func
1975 makecl = unfi.__class__.changelog.func
1977 if not opts[b'rev']:
1976 if not opts[b'rev']:
1978 raise error.Abort(b'use --rev to specify revisions to look up')
1977 raise error.Abort(b'use --rev to specify revisions to look up')
1979 revs = scmutil.revrange(repo, opts[b'rev'])
1978 revs = scmutil.revrange(repo, opts[b'rev'])
1980 cl = repo.changelog
1979 cl = repo.changelog
1981 nodes = [cl.node(r) for r in revs]
1980 nodes = [cl.node(r) for r in revs]
1982
1981
1983 # use a list to pass reference to a nodemap from one closure to the next
1982 # use a list to pass reference to a nodemap from one closure to the next
1984 nodeget = [None]
1983 nodeget = [None]
1985
1984
1986 def setnodeget():
1985 def setnodeget():
1987 # probably not necessary, but for good measure
1986 # probably not necessary, but for good measure
1988 clearchangelog(unfi)
1987 clearchangelog(unfi)
1989 cl = makecl(unfi)
1988 cl = makecl(unfi)
1990 if util.safehasattr(cl.index, 'get_rev'):
1989 if util.safehasattr(cl.index, 'get_rev'):
1991 nodeget[0] = cl.index.get_rev
1990 nodeget[0] = cl.index.get_rev
1992 else:
1991 else:
1993 nodeget[0] = cl.nodemap.get
1992 nodeget[0] = cl.nodemap.get
1994
1993
1995 def d():
1994 def d():
1996 get = nodeget[0]
1995 get = nodeget[0]
1997 for n in nodes:
1996 for n in nodes:
1998 get(n)
1997 get(n)
1999
1998
2000 setup = None
1999 setup = None
2001 if clearcaches:
2000 if clearcaches:
2002
2001
2003 def setup():
2002 def setup():
2004 setnodeget()
2003 setnodeget()
2005
2004
2006 else:
2005 else:
2007 setnodeget()
2006 setnodeget()
2008 d() # prewarm the data structure
2007 d() # prewarm the data structure
2009 timer(d, setup=setup)
2008 timer(d, setup=setup)
2010 fm.end()
2009 fm.end()
2011
2010
2012
2011
2013 @command(b'perf::startup|perfstartup', formatteropts)
2012 @command(b'perf::startup|perfstartup', formatteropts)
2014 def perfstartup(ui, repo, **opts):
2013 def perfstartup(ui, repo, **opts):
2015 opts = _byteskwargs(opts)
2014 opts = _byteskwargs(opts)
2016 timer, fm = gettimer(ui, opts)
2015 timer, fm = gettimer(ui, opts)
2017
2016
2018 def d():
2017 def d():
2019 if os.name != 'nt':
2018 if os.name != 'nt':
2020 os.system(
2019 os.system(
2021 b"HGRCPATH= %s version -q > /dev/null" % fsencode(sys.argv[0])
2020 b"HGRCPATH= %s version -q > /dev/null" % fsencode(sys.argv[0])
2022 )
2021 )
2023 else:
2022 else:
2024 os.environ['HGRCPATH'] = r' '
2023 os.environ['HGRCPATH'] = r' '
2025 os.system("%s version -q > NUL" % sys.argv[0])
2024 os.system("%s version -q > NUL" % sys.argv[0])
2026
2025
2027 timer(d)
2026 timer(d)
2028 fm.end()
2027 fm.end()
2029
2028
2030
2029
2031 def _find_stream_generator(version):
2030 def _find_stream_generator(version):
2032 """find the proper generator function for this stream version"""
2031 """find the proper generator function for this stream version"""
2033 import mercurial.streamclone
2032 import mercurial.streamclone
2034
2033
2035 available = {}
2034 available = {}
2036
2035
2037 # try to fetch a v1 generator
2036 # try to fetch a v1 generator
2038 generatev1 = getattr(mercurial.streamclone, "generatev1", None)
2037 generatev1 = getattr(mercurial.streamclone, "generatev1", None)
2039 if generatev1 is not None:
2038 if generatev1 is not None:
2040
2039
2041 def generate(repo):
2040 def generate(repo):
2042 entries, bytes, data = generatev2(repo, None, None, True)
2041 entries, bytes, data = generatev2(repo, None, None, True)
2043 return data
2042 return data
2044
2043
2045 available[b'v1'] = generatev1
2044 available[b'v1'] = generatev1
2046 # try to fetch a v2 generator
2045 # try to fetch a v2 generator
2047 generatev2 = getattr(mercurial.streamclone, "generatev2", None)
2046 generatev2 = getattr(mercurial.streamclone, "generatev2", None)
2048 if generatev2 is not None:
2047 if generatev2 is not None:
2049
2048
2050 def generate(repo):
2049 def generate(repo):
2051 entries, bytes, data = generatev2(repo, None, None, True)
2050 entries, bytes, data = generatev2(repo, None, None, True)
2052 return data
2051 return data
2053
2052
2054 available[b'v2'] = generate
2053 available[b'v2'] = generate
2055 # try to fetch a v3 generator
2054 # try to fetch a v3 generator
2056 generatev3 = getattr(mercurial.streamclone, "generatev3", None)
2055 generatev3 = getattr(mercurial.streamclone, "generatev3", None)
2057 if generatev3 is not None:
2056 if generatev3 is not None:
2058
2057
2059 def generate(repo):
2058 def generate(repo):
2060 entries, bytes, data = generatev3(repo, None, None, True)
2059 entries, bytes, data = generatev3(repo, None, None, True)
2061 return data
2060 return data
2062
2061
2063 available[b'v3-exp'] = generate
2062 available[b'v3-exp'] = generate
2064
2063
2065 # resolve the request
2064 # resolve the request
2066 if version == b"latest":
2065 if version == b"latest":
2067 # latest is the highest non experimental version
2066 # latest is the highest non experimental version
2068 latest_key = max(v for v in available if b'-exp' not in v)
2067 latest_key = max(v for v in available if b'-exp' not in v)
2069 return available[latest_key]
2068 return available[latest_key]
2070 elif version in available:
2069 elif version in available:
2071 return available[version]
2070 return available[version]
2072 else:
2071 else:
2073 msg = b"unkown or unavailable version: %s"
2072 msg = b"unkown or unavailable version: %s"
2074 msg %= version
2073 msg %= version
2075 hint = b"available versions: %s"
2074 hint = b"available versions: %s"
2076 hint %= b', '.join(sorted(available))
2075 hint %= b', '.join(sorted(available))
2077 raise error.Abort(msg, hint=hint)
2076 raise error.Abort(msg, hint=hint)
2078
2077
2079
2078
2080 @command(
2079 @command(
2081 b'perf::stream-locked-section',
2080 b'perf::stream-locked-section',
2082 [
2081 [
2083 (
2082 (
2084 b'',
2083 b'',
2085 b'stream-version',
2084 b'stream-version',
2086 b'latest',
2085 b'latest',
2087 b'stream version to use ("v1", "v2", "v3" or "latest", (the default))',
2086 b'stream version to use ("v1", "v2", "v3" or "latest", (the default))',
2088 ),
2087 ),
2089 ]
2088 ]
2090 + formatteropts,
2089 + formatteropts,
2091 )
2090 )
2092 def perf_stream_clone_scan(ui, repo, stream_version, **opts):
2091 def perf_stream_clone_scan(ui, repo, stream_version, **opts):
2093 """benchmark the initial, repo-locked, section of a stream-clone"""
2092 """benchmark the initial, repo-locked, section of a stream-clone"""
2094
2093
2095 opts = _byteskwargs(opts)
2094 opts = _byteskwargs(opts)
2096 timer, fm = gettimer(ui, opts)
2095 timer, fm = gettimer(ui, opts)
2097
2096
2098 # deletion of the generator may trigger some cleanup that we do not want to
2097 # deletion of the generator may trigger some cleanup that we do not want to
2099 # measure
2098 # measure
2100 result_holder = [None]
2099 result_holder = [None]
2101
2100
2102 def setupone():
2101 def setupone():
2103 result_holder[0] = None
2102 result_holder[0] = None
2104
2103
2105 generate = _find_stream_generator(stream_version)
2104 generate = _find_stream_generator(stream_version)
2106
2105
2107 def runone():
2106 def runone():
2108 # the lock is held for the duration the initialisation
2107 # the lock is held for the duration the initialisation
2109 result_holder[0] = generate(repo)
2108 result_holder[0] = generate(repo)
2110
2109
2111 timer(runone, setup=setupone, title=b"load")
2110 timer(runone, setup=setupone, title=b"load")
2112 fm.end()
2111 fm.end()
2113
2112
2114
2113
2115 @command(
2114 @command(
2116 b'perf::stream-generate',
2115 b'perf::stream-generate',
2117 [
2116 [
2118 (
2117 (
2119 b'',
2118 b'',
2120 b'stream-version',
2119 b'stream-version',
2121 b'latest',
2120 b'latest',
2122 b'stream version to us ("v1", "v2" or "latest", (the default))',
2121 b'stream version to us ("v1", "v2" or "latest", (the default))',
2123 ),
2122 ),
2124 ]
2123 ]
2125 + formatteropts,
2124 + formatteropts,
2126 )
2125 )
2127 def perf_stream_clone_generate(ui, repo, stream_version, **opts):
2126 def perf_stream_clone_generate(ui, repo, stream_version, **opts):
2128 """benchmark the full generation of a stream clone"""
2127 """benchmark the full generation of a stream clone"""
2129
2128
2130 opts = _byteskwargs(opts)
2129 opts = _byteskwargs(opts)
2131 timer, fm = gettimer(ui, opts)
2130 timer, fm = gettimer(ui, opts)
2132
2131
2133 # deletion of the generator may trigger some cleanup that we do not want to
2132 # deletion of the generator may trigger some cleanup that we do not want to
2134 # measure
2133 # measure
2135
2134
2136 generate = _find_stream_generator(stream_version)
2135 generate = _find_stream_generator(stream_version)
2137
2136
2138 def runone():
2137 def runone():
2139 # the lock is held for the duration the initialisation
2138 # the lock is held for the duration the initialisation
2140 for chunk in generate(repo):
2139 for chunk in generate(repo):
2141 pass
2140 pass
2142
2141
2143 timer(runone, title=b"generate")
2142 timer(runone, title=b"generate")
2144 fm.end()
2143 fm.end()
2145
2144
2146
2145
2147 @command(
2146 @command(
2148 b'perf::stream-consume',
2147 b'perf::stream-consume',
2149 formatteropts,
2148 formatteropts,
2150 )
2149 )
2151 def perf_stream_clone_consume(ui, repo, filename, **opts):
2150 def perf_stream_clone_consume(ui, repo, filename, **opts):
2152 """benchmark the full application of a stream clone
2151 """benchmark the full application of a stream clone
2153
2152
2154 This include the creation of the repository
2153 This include the creation of the repository
2155 """
2154 """
2156 # try except to appease check code
2155 # try except to appease check code
2157 msg = b"mercurial too old, missing necessary module: %s"
2156 msg = b"mercurial too old, missing necessary module: %s"
2158 try:
2157 try:
2159 from mercurial import bundle2
2158 from mercurial import bundle2
2160 except ImportError as exc:
2159 except ImportError as exc:
2161 msg %= _bytestr(exc)
2160 msg %= _bytestr(exc)
2162 raise error.Abort(msg)
2161 raise error.Abort(msg)
2163 try:
2162 try:
2164 from mercurial import exchange
2163 from mercurial import exchange
2165 except ImportError as exc:
2164 except ImportError as exc:
2166 msg %= _bytestr(exc)
2165 msg %= _bytestr(exc)
2167 raise error.Abort(msg)
2166 raise error.Abort(msg)
2168 try:
2167 try:
2169 from mercurial import hg
2168 from mercurial import hg
2170 except ImportError as exc:
2169 except ImportError as exc:
2171 msg %= _bytestr(exc)
2170 msg %= _bytestr(exc)
2172 raise error.Abort(msg)
2171 raise error.Abort(msg)
2173 try:
2172 try:
2174 from mercurial import localrepo
2173 from mercurial import localrepo
2175 except ImportError as exc:
2174 except ImportError as exc:
2176 msg %= _bytestr(exc)
2175 msg %= _bytestr(exc)
2177 raise error.Abort(msg)
2176 raise error.Abort(msg)
2178
2177
2179 opts = _byteskwargs(opts)
2178 opts = _byteskwargs(opts)
2180 timer, fm = gettimer(ui, opts)
2179 timer, fm = gettimer(ui, opts)
2181
2180
2182 # deletion of the generator may trigger some cleanup that we do not want to
2181 # deletion of the generator may trigger some cleanup that we do not want to
2183 # measure
2182 # measure
2184 if not (os.path.isfile(filename) and os.access(filename, os.R_OK)):
2183 if not (os.path.isfile(filename) and os.access(filename, os.R_OK)):
2185 raise error.Abort("not a readable file: %s" % filename)
2184 raise error.Abort("not a readable file: %s" % filename)
2186
2185
2187 run_variables = [None, None]
2186 run_variables = [None, None]
2188
2187
2189 @contextlib.contextmanager
2188 @contextlib.contextmanager
2190 def context():
2189 def context():
2191 with open(filename, mode='rb') as bundle:
2190 with open(filename, mode='rb') as bundle:
2192 with tempfile.TemporaryDirectory() as tmp_dir:
2191 with tempfile.TemporaryDirectory() as tmp_dir:
2193 tmp_dir = fsencode(tmp_dir)
2192 tmp_dir = fsencode(tmp_dir)
2194 run_variables[0] = bundle
2193 run_variables[0] = bundle
2195 run_variables[1] = tmp_dir
2194 run_variables[1] = tmp_dir
2196 yield
2195 yield
2197 run_variables[0] = None
2196 run_variables[0] = None
2198 run_variables[1] = None
2197 run_variables[1] = None
2199
2198
2200 def runone():
2199 def runone():
2201 bundle = run_variables[0]
2200 bundle = run_variables[0]
2202 tmp_dir = run_variables[1]
2201 tmp_dir = run_variables[1]
2203 # only pass ui when no srcrepo
2202 # only pass ui when no srcrepo
2204 localrepo.createrepository(
2203 localrepo.createrepository(
2205 repo.ui, tmp_dir, requirements=repo.requirements
2204 repo.ui, tmp_dir, requirements=repo.requirements
2206 )
2205 )
2207 target = hg.repository(repo.ui, tmp_dir)
2206 target = hg.repository(repo.ui, tmp_dir)
2208 gen = exchange.readbundle(target.ui, bundle, bundle.name)
2207 gen = exchange.readbundle(target.ui, bundle, bundle.name)
2209 # stream v1
2208 # stream v1
2210 if util.safehasattr(gen, 'apply'):
2209 if util.safehasattr(gen, 'apply'):
2211 gen.apply(target)
2210 gen.apply(target)
2212 else:
2211 else:
2213 with target.transaction(b"perf::stream-consume") as tr:
2212 with target.transaction(b"perf::stream-consume") as tr:
2214 bundle2.applybundle(
2213 bundle2.applybundle(
2215 target,
2214 target,
2216 gen,
2215 gen,
2217 tr,
2216 tr,
2218 source=b'unbundle',
2217 source=b'unbundle',
2219 url=filename,
2218 url=filename,
2220 )
2219 )
2221
2220
2222 timer(runone, context=context, title=b"consume")
2221 timer(runone, context=context, title=b"consume")
2223 fm.end()
2222 fm.end()
2224
2223
2225
2224
2226 @command(b'perf::parents|perfparents', formatteropts)
2225 @command(b'perf::parents|perfparents', formatteropts)
2227 def perfparents(ui, repo, **opts):
2226 def perfparents(ui, repo, **opts):
2228 """benchmark the time necessary to fetch one changeset's parents.
2227 """benchmark the time necessary to fetch one changeset's parents.
2229
2228
2230 The fetch is done using the `node identifier`, traversing all object layers
2229 The fetch is done using the `node identifier`, traversing all object layers
2231 from the repository object. The first N revisions will be used for this
2230 from the repository object. The first N revisions will be used for this
2232 benchmark. N is controlled by the ``perf.parentscount`` config option
2231 benchmark. N is controlled by the ``perf.parentscount`` config option
2233 (default: 1000).
2232 (default: 1000).
2234 """
2233 """
2235 opts = _byteskwargs(opts)
2234 opts = _byteskwargs(opts)
2236 timer, fm = gettimer(ui, opts)
2235 timer, fm = gettimer(ui, opts)
2237 # control the number of commits perfparents iterates over
2236 # control the number of commits perfparents iterates over
2238 # experimental config: perf.parentscount
2237 # experimental config: perf.parentscount
2239 count = getint(ui, b"perf", b"parentscount", 1000)
2238 count = getint(ui, b"perf", b"parentscount", 1000)
2240 if len(repo.changelog) < count:
2239 if len(repo.changelog) < count:
2241 raise error.Abort(b"repo needs %d commits for this test" % count)
2240 raise error.Abort(b"repo needs %d commits for this test" % count)
2242 repo = repo.unfiltered()
2241 repo = repo.unfiltered()
2243 nl = [repo.changelog.node(i) for i in _xrange(count)]
2242 nl = [repo.changelog.node(i) for i in _xrange(count)]
2244
2243
2245 def d():
2244 def d():
2246 for n in nl:
2245 for n in nl:
2247 repo.changelog.parents(n)
2246 repo.changelog.parents(n)
2248
2247
2249 timer(d)
2248 timer(d)
2250 fm.end()
2249 fm.end()
2251
2250
2252
2251
2253 @command(b'perf::ctxfiles|perfctxfiles', formatteropts)
2252 @command(b'perf::ctxfiles|perfctxfiles', formatteropts)
2254 def perfctxfiles(ui, repo, x, **opts):
2253 def perfctxfiles(ui, repo, x, **opts):
2255 opts = _byteskwargs(opts)
2254 opts = _byteskwargs(opts)
2256 x = int(x)
2255 x = int(x)
2257 timer, fm = gettimer(ui, opts)
2256 timer, fm = gettimer(ui, opts)
2258
2257
2259 def d():
2258 def d():
2260 len(repo[x].files())
2259 len(repo[x].files())
2261
2260
2262 timer(d)
2261 timer(d)
2263 fm.end()
2262 fm.end()
2264
2263
2265
2264
2266 @command(b'perf::rawfiles|perfrawfiles', formatteropts)
2265 @command(b'perf::rawfiles|perfrawfiles', formatteropts)
2267 def perfrawfiles(ui, repo, x, **opts):
2266 def perfrawfiles(ui, repo, x, **opts):
2268 opts = _byteskwargs(opts)
2267 opts = _byteskwargs(opts)
2269 x = int(x)
2268 x = int(x)
2270 timer, fm = gettimer(ui, opts)
2269 timer, fm = gettimer(ui, opts)
2271 cl = repo.changelog
2270 cl = repo.changelog
2272
2271
2273 def d():
2272 def d():
2274 len(cl.read(x)[3])
2273 len(cl.read(x)[3])
2275
2274
2276 timer(d)
2275 timer(d)
2277 fm.end()
2276 fm.end()
2278
2277
2279
2278
2280 @command(b'perf::lookup|perflookup', formatteropts)
2279 @command(b'perf::lookup|perflookup', formatteropts)
2281 def perflookup(ui, repo, rev, **opts):
2280 def perflookup(ui, repo, rev, **opts):
2282 opts = _byteskwargs(opts)
2281 opts = _byteskwargs(opts)
2283 timer, fm = gettimer(ui, opts)
2282 timer, fm = gettimer(ui, opts)
2284 timer(lambda: len(repo.lookup(rev)))
2283 timer(lambda: len(repo.lookup(rev)))
2285 fm.end()
2284 fm.end()
2286
2285
2287
2286
2288 @command(
2287 @command(
2289 b'perf::linelogedits|perflinelogedits',
2288 b'perf::linelogedits|perflinelogedits',
2290 [
2289 [
2291 (b'n', b'edits', 10000, b'number of edits'),
2290 (b'n', b'edits', 10000, b'number of edits'),
2292 (b'', b'max-hunk-lines', 10, b'max lines in a hunk'),
2291 (b'', b'max-hunk-lines', 10, b'max lines in a hunk'),
2293 ],
2292 ],
2294 norepo=True,
2293 norepo=True,
2295 )
2294 )
2296 def perflinelogedits(ui, **opts):
2295 def perflinelogedits(ui, **opts):
2297 from mercurial import linelog
2296 from mercurial import linelog
2298
2297
2299 opts = _byteskwargs(opts)
2298 opts = _byteskwargs(opts)
2300
2299
2301 edits = opts[b'edits']
2300 edits = opts[b'edits']
2302 maxhunklines = opts[b'max_hunk_lines']
2301 maxhunklines = opts[b'max_hunk_lines']
2303
2302
2304 maxb1 = 100000
2303 maxb1 = 100000
2305 random.seed(0)
2304 random.seed(0)
2306 randint = random.randint
2305 randint = random.randint
2307 currentlines = 0
2306 currentlines = 0
2308 arglist = []
2307 arglist = []
2309 for rev in _xrange(edits):
2308 for rev in _xrange(edits):
2310 a1 = randint(0, currentlines)
2309 a1 = randint(0, currentlines)
2311 a2 = randint(a1, min(currentlines, a1 + maxhunklines))
2310 a2 = randint(a1, min(currentlines, a1 + maxhunklines))
2312 b1 = randint(0, maxb1)
2311 b1 = randint(0, maxb1)
2313 b2 = randint(b1, b1 + maxhunklines)
2312 b2 = randint(b1, b1 + maxhunklines)
2314 currentlines += (b2 - b1) - (a2 - a1)
2313 currentlines += (b2 - b1) - (a2 - a1)
2315 arglist.append((rev, a1, a2, b1, b2))
2314 arglist.append((rev, a1, a2, b1, b2))
2316
2315
2317 def d():
2316 def d():
2318 ll = linelog.linelog()
2317 ll = linelog.linelog()
2319 for args in arglist:
2318 for args in arglist:
2320 ll.replacelines(*args)
2319 ll.replacelines(*args)
2321
2320
2322 timer, fm = gettimer(ui, opts)
2321 timer, fm = gettimer(ui, opts)
2323 timer(d)
2322 timer(d)
2324 fm.end()
2323 fm.end()
2325
2324
2326
2325
2327 @command(b'perf::revrange|perfrevrange', formatteropts)
2326 @command(b'perf::revrange|perfrevrange', formatteropts)
2328 def perfrevrange(ui, repo, *specs, **opts):
2327 def perfrevrange(ui, repo, *specs, **opts):
2329 opts = _byteskwargs(opts)
2328 opts = _byteskwargs(opts)
2330 timer, fm = gettimer(ui, opts)
2329 timer, fm = gettimer(ui, opts)
2331 revrange = scmutil.revrange
2330 revrange = scmutil.revrange
2332 timer(lambda: len(revrange(repo, specs)))
2331 timer(lambda: len(revrange(repo, specs)))
2333 fm.end()
2332 fm.end()
2334
2333
2335
2334
2336 @command(b'perf::nodelookup|perfnodelookup', formatteropts)
2335 @command(b'perf::nodelookup|perfnodelookup', formatteropts)
2337 def perfnodelookup(ui, repo, rev, **opts):
2336 def perfnodelookup(ui, repo, rev, **opts):
2338 opts = _byteskwargs(opts)
2337 opts = _byteskwargs(opts)
2339 timer, fm = gettimer(ui, opts)
2338 timer, fm = gettimer(ui, opts)
2340 import mercurial.revlog
2339 import mercurial.revlog
2341
2340
2342 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
2341 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
2343 n = scmutil.revsingle(repo, rev).node()
2342 n = scmutil.revsingle(repo, rev).node()
2344
2343
2345 try:
2344 try:
2346 cl = revlog(getsvfs(repo), radix=b"00changelog")
2345 cl = revlog(getsvfs(repo), radix=b"00changelog")
2347 except TypeError:
2346 except TypeError:
2348 cl = revlog(getsvfs(repo), indexfile=b"00changelog.i")
2347 cl = revlog(getsvfs(repo), indexfile=b"00changelog.i")
2349
2348
2350 def d():
2349 def d():
2351 cl.rev(n)
2350 cl.rev(n)
2352 clearcaches(cl)
2351 clearcaches(cl)
2353
2352
2354 timer(d)
2353 timer(d)
2355 fm.end()
2354 fm.end()
2356
2355
2357
2356
2358 @command(
2357 @command(
2359 b'perf::log|perflog',
2358 b'perf::log|perflog',
2360 [(b'', b'rename', False, b'ask log to follow renames')] + formatteropts,
2359 [(b'', b'rename', False, b'ask log to follow renames')] + formatteropts,
2361 )
2360 )
2362 def perflog(ui, repo, rev=None, **opts):
2361 def perflog(ui, repo, rev=None, **opts):
2363 opts = _byteskwargs(opts)
2362 opts = _byteskwargs(opts)
2364 if rev is None:
2363 if rev is None:
2365 rev = []
2364 rev = []
2366 timer, fm = gettimer(ui, opts)
2365 timer, fm = gettimer(ui, opts)
2367 ui.pushbuffer()
2366 ui.pushbuffer()
2368 timer(
2367 timer(
2369 lambda: commands.log(
2368 lambda: commands.log(
2370 ui, repo, rev=rev, date=b'', user=b'', copies=opts.get(b'rename')
2369 ui, repo, rev=rev, date=b'', user=b'', copies=opts.get(b'rename')
2371 )
2370 )
2372 )
2371 )
2373 ui.popbuffer()
2372 ui.popbuffer()
2374 fm.end()
2373 fm.end()
2375
2374
2376
2375
2377 @command(b'perf::moonwalk|perfmoonwalk', formatteropts)
2376 @command(b'perf::moonwalk|perfmoonwalk', formatteropts)
2378 def perfmoonwalk(ui, repo, **opts):
2377 def perfmoonwalk(ui, repo, **opts):
2379 """benchmark walking the changelog backwards
2378 """benchmark walking the changelog backwards
2380
2379
2381 This also loads the changelog data for each revision in the changelog.
2380 This also loads the changelog data for each revision in the changelog.
2382 """
2381 """
2383 opts = _byteskwargs(opts)
2382 opts = _byteskwargs(opts)
2384 timer, fm = gettimer(ui, opts)
2383 timer, fm = gettimer(ui, opts)
2385
2384
2386 def moonwalk():
2385 def moonwalk():
2387 for i in repo.changelog.revs(start=(len(repo) - 1), stop=-1):
2386 for i in repo.changelog.revs(start=(len(repo) - 1), stop=-1):
2388 ctx = repo[i]
2387 ctx = repo[i]
2389 ctx.branch() # read changelog data (in addition to the index)
2388 ctx.branch() # read changelog data (in addition to the index)
2390
2389
2391 timer(moonwalk)
2390 timer(moonwalk)
2392 fm.end()
2391 fm.end()
2393
2392
2394
2393
2395 @command(
2394 @command(
2396 b'perf::templating|perftemplating',
2395 b'perf::templating|perftemplating',
2397 [
2396 [
2398 (b'r', b'rev', [], b'revisions to run the template on'),
2397 (b'r', b'rev', [], b'revisions to run the template on'),
2399 ]
2398 ]
2400 + formatteropts,
2399 + formatteropts,
2401 )
2400 )
2402 def perftemplating(ui, repo, testedtemplate=None, **opts):
2401 def perftemplating(ui, repo, testedtemplate=None, **opts):
2403 """test the rendering time of a given template"""
2402 """test the rendering time of a given template"""
2404 if makelogtemplater is None:
2403 if makelogtemplater is None:
2405 raise error.Abort(
2404 raise error.Abort(
2406 b"perftemplating not available with this Mercurial",
2405 b"perftemplating not available with this Mercurial",
2407 hint=b"use 4.3 or later",
2406 hint=b"use 4.3 or later",
2408 )
2407 )
2409
2408
2410 opts = _byteskwargs(opts)
2409 opts = _byteskwargs(opts)
2411
2410
2412 nullui = ui.copy()
2411 nullui = ui.copy()
2413 nullui.fout = open(os.devnull, 'wb')
2412 nullui.fout = open(os.devnull, 'wb')
2414 nullui.disablepager()
2413 nullui.disablepager()
2415 revs = opts.get(b'rev')
2414 revs = opts.get(b'rev')
2416 if not revs:
2415 if not revs:
2417 revs = [b'all()']
2416 revs = [b'all()']
2418 revs = list(scmutil.revrange(repo, revs))
2417 revs = list(scmutil.revrange(repo, revs))
2419
2418
2420 defaulttemplate = (
2419 defaulttemplate = (
2421 b'{date|shortdate} [{rev}:{node|short}]'
2420 b'{date|shortdate} [{rev}:{node|short}]'
2422 b' {author|person}: {desc|firstline}\n'
2421 b' {author|person}: {desc|firstline}\n'
2423 )
2422 )
2424 if testedtemplate is None:
2423 if testedtemplate is None:
2425 testedtemplate = defaulttemplate
2424 testedtemplate = defaulttemplate
2426 displayer = makelogtemplater(nullui, repo, testedtemplate)
2425 displayer = makelogtemplater(nullui, repo, testedtemplate)
2427
2426
2428 def format():
2427 def format():
2429 for r in revs:
2428 for r in revs:
2430 ctx = repo[r]
2429 ctx = repo[r]
2431 displayer.show(ctx)
2430 displayer.show(ctx)
2432 displayer.flush(ctx)
2431 displayer.flush(ctx)
2433
2432
2434 timer, fm = gettimer(ui, opts)
2433 timer, fm = gettimer(ui, opts)
2435 timer(format)
2434 timer(format)
2436 fm.end()
2435 fm.end()
2437
2436
2438
2437
2439 def _displaystats(ui, opts, entries, data):
2438 def _displaystats(ui, opts, entries, data):
2440 # use a second formatter because the data are quite different, not sure
2439 # use a second formatter because the data are quite different, not sure
2441 # how it flies with the templater.
2440 # how it flies with the templater.
2442 fm = ui.formatter(b'perf-stats', opts)
2441 fm = ui.formatter(b'perf-stats', opts)
2443 for key, title in entries:
2442 for key, title in entries:
2444 values = data[key]
2443 values = data[key]
2445 nbvalues = len(data)
2444 nbvalues = len(data)
2446 values.sort()
2445 values.sort()
2447 stats = {
2446 stats = {
2448 'key': key,
2447 'key': key,
2449 'title': title,
2448 'title': title,
2450 'nbitems': len(values),
2449 'nbitems': len(values),
2451 'min': values[0][0],
2450 'min': values[0][0],
2452 '10%': values[(nbvalues * 10) // 100][0],
2451 '10%': values[(nbvalues * 10) // 100][0],
2453 '25%': values[(nbvalues * 25) // 100][0],
2452 '25%': values[(nbvalues * 25) // 100][0],
2454 '50%': values[(nbvalues * 50) // 100][0],
2453 '50%': values[(nbvalues * 50) // 100][0],
2455 '75%': values[(nbvalues * 75) // 100][0],
2454 '75%': values[(nbvalues * 75) // 100][0],
2456 '80%': values[(nbvalues * 80) // 100][0],
2455 '80%': values[(nbvalues * 80) // 100][0],
2457 '85%': values[(nbvalues * 85) // 100][0],
2456 '85%': values[(nbvalues * 85) // 100][0],
2458 '90%': values[(nbvalues * 90) // 100][0],
2457 '90%': values[(nbvalues * 90) // 100][0],
2459 '95%': values[(nbvalues * 95) // 100][0],
2458 '95%': values[(nbvalues * 95) // 100][0],
2460 '99%': values[(nbvalues * 99) // 100][0],
2459 '99%': values[(nbvalues * 99) // 100][0],
2461 'max': values[-1][0],
2460 'max': values[-1][0],
2462 }
2461 }
2463 fm.startitem()
2462 fm.startitem()
2464 fm.data(**stats)
2463 fm.data(**stats)
2465 # make node pretty for the human output
2464 # make node pretty for the human output
2466 fm.plain('### %s (%d items)\n' % (title, len(values)))
2465 fm.plain('### %s (%d items)\n' % (title, len(values)))
2467 lines = [
2466 lines = [
2468 'min',
2467 'min',
2469 '10%',
2468 '10%',
2470 '25%',
2469 '25%',
2471 '50%',
2470 '50%',
2472 '75%',
2471 '75%',
2473 '80%',
2472 '80%',
2474 '85%',
2473 '85%',
2475 '90%',
2474 '90%',
2476 '95%',
2475 '95%',
2477 '99%',
2476 '99%',
2478 'max',
2477 'max',
2479 ]
2478 ]
2480 for l in lines:
2479 for l in lines:
2481 fm.plain('%s: %s\n' % (l, stats[l]))
2480 fm.plain('%s: %s\n' % (l, stats[l]))
2482 fm.end()
2481 fm.end()
2483
2482
2484
2483
2485 @command(
2484 @command(
2486 b'perf::helper-mergecopies|perfhelper-mergecopies',
2485 b'perf::helper-mergecopies|perfhelper-mergecopies',
2487 formatteropts
2486 formatteropts
2488 + [
2487 + [
2489 (b'r', b'revs', [], b'restrict search to these revisions'),
2488 (b'r', b'revs', [], b'restrict search to these revisions'),
2490 (b'', b'timing', False, b'provides extra data (costly)'),
2489 (b'', b'timing', False, b'provides extra data (costly)'),
2491 (b'', b'stats', False, b'provides statistic about the measured data'),
2490 (b'', b'stats', False, b'provides statistic about the measured data'),
2492 ],
2491 ],
2493 )
2492 )
2494 def perfhelpermergecopies(ui, repo, revs=[], **opts):
2493 def perfhelpermergecopies(ui, repo, revs=[], **opts):
2495 """find statistics about potential parameters for `perfmergecopies`
2494 """find statistics about potential parameters for `perfmergecopies`
2496
2495
2497 This command find (base, p1, p2) triplet relevant for copytracing
2496 This command find (base, p1, p2) triplet relevant for copytracing
2498 benchmarking in the context of a merge. It reports values for some of the
2497 benchmarking in the context of a merge. It reports values for some of the
2499 parameters that impact merge copy tracing time during merge.
2498 parameters that impact merge copy tracing time during merge.
2500
2499
2501 If `--timing` is set, rename detection is run and the associated timing
2500 If `--timing` is set, rename detection is run and the associated timing
2502 will be reported. The extra details come at the cost of slower command
2501 will be reported. The extra details come at the cost of slower command
2503 execution.
2502 execution.
2504
2503
2505 Since rename detection is only run once, other factors might easily
2504 Since rename detection is only run once, other factors might easily
2506 affect the precision of the timing. However it should give a good
2505 affect the precision of the timing. However it should give a good
2507 approximation of which revision triplets are very costly.
2506 approximation of which revision triplets are very costly.
2508 """
2507 """
2509 opts = _byteskwargs(opts)
2508 opts = _byteskwargs(opts)
2510 fm = ui.formatter(b'perf', opts)
2509 fm = ui.formatter(b'perf', opts)
2511 dotiming = opts[b'timing']
2510 dotiming = opts[b'timing']
2512 dostats = opts[b'stats']
2511 dostats = opts[b'stats']
2513
2512
2514 output_template = [
2513 output_template = [
2515 ("base", "%(base)12s"),
2514 ("base", "%(base)12s"),
2516 ("p1", "%(p1.node)12s"),
2515 ("p1", "%(p1.node)12s"),
2517 ("p2", "%(p2.node)12s"),
2516 ("p2", "%(p2.node)12s"),
2518 ("p1.nb-revs", "%(p1.nbrevs)12d"),
2517 ("p1.nb-revs", "%(p1.nbrevs)12d"),
2519 ("p1.nb-files", "%(p1.nbmissingfiles)12d"),
2518 ("p1.nb-files", "%(p1.nbmissingfiles)12d"),
2520 ("p1.renames", "%(p1.renamedfiles)12d"),
2519 ("p1.renames", "%(p1.renamedfiles)12d"),
2521 ("p1.time", "%(p1.time)12.3f"),
2520 ("p1.time", "%(p1.time)12.3f"),
2522 ("p2.nb-revs", "%(p2.nbrevs)12d"),
2521 ("p2.nb-revs", "%(p2.nbrevs)12d"),
2523 ("p2.nb-files", "%(p2.nbmissingfiles)12d"),
2522 ("p2.nb-files", "%(p2.nbmissingfiles)12d"),
2524 ("p2.renames", "%(p2.renamedfiles)12d"),
2523 ("p2.renames", "%(p2.renamedfiles)12d"),
2525 ("p2.time", "%(p2.time)12.3f"),
2524 ("p2.time", "%(p2.time)12.3f"),
2526 ("renames", "%(nbrenamedfiles)12d"),
2525 ("renames", "%(nbrenamedfiles)12d"),
2527 ("total.time", "%(time)12.3f"),
2526 ("total.time", "%(time)12.3f"),
2528 ]
2527 ]
2529 if not dotiming:
2528 if not dotiming:
2530 output_template = [
2529 output_template = [
2531 i
2530 i
2532 for i in output_template
2531 for i in output_template
2533 if not ('time' in i[0] or 'renames' in i[0])
2532 if not ('time' in i[0] or 'renames' in i[0])
2534 ]
2533 ]
2535 header_names = [h for (h, v) in output_template]
2534 header_names = [h for (h, v) in output_template]
2536 output = ' '.join([v for (h, v) in output_template]) + '\n'
2535 output = ' '.join([v for (h, v) in output_template]) + '\n'
2537 header = ' '.join(['%12s'] * len(header_names)) + '\n'
2536 header = ' '.join(['%12s'] * len(header_names)) + '\n'
2538 fm.plain(header % tuple(header_names))
2537 fm.plain(header % tuple(header_names))
2539
2538
2540 if not revs:
2539 if not revs:
2541 revs = ['all()']
2540 revs = ['all()']
2542 revs = scmutil.revrange(repo, revs)
2541 revs = scmutil.revrange(repo, revs)
2543
2542
2544 if dostats:
2543 if dostats:
2545 alldata = {
2544 alldata = {
2546 'nbrevs': [],
2545 'nbrevs': [],
2547 'nbmissingfiles': [],
2546 'nbmissingfiles': [],
2548 }
2547 }
2549 if dotiming:
2548 if dotiming:
2550 alldata['parentnbrenames'] = []
2549 alldata['parentnbrenames'] = []
2551 alldata['totalnbrenames'] = []
2550 alldata['totalnbrenames'] = []
2552 alldata['parenttime'] = []
2551 alldata['parenttime'] = []
2553 alldata['totaltime'] = []
2552 alldata['totaltime'] = []
2554
2553
2555 roi = repo.revs('merge() and %ld', revs)
2554 roi = repo.revs('merge() and %ld', revs)
2556 for r in roi:
2555 for r in roi:
2557 ctx = repo[r]
2556 ctx = repo[r]
2558 p1 = ctx.p1()
2557 p1 = ctx.p1()
2559 p2 = ctx.p2()
2558 p2 = ctx.p2()
2560 bases = repo.changelog._commonancestorsheads(p1.rev(), p2.rev())
2559 bases = repo.changelog._commonancestorsheads(p1.rev(), p2.rev())
2561 for b in bases:
2560 for b in bases:
2562 b = repo[b]
2561 b = repo[b]
2563 p1missing = copies._computeforwardmissing(b, p1)
2562 p1missing = copies._computeforwardmissing(b, p1)
2564 p2missing = copies._computeforwardmissing(b, p2)
2563 p2missing = copies._computeforwardmissing(b, p2)
2565 data = {
2564 data = {
2566 b'base': b.hex(),
2565 b'base': b.hex(),
2567 b'p1.node': p1.hex(),
2566 b'p1.node': p1.hex(),
2568 b'p1.nbrevs': len(repo.revs('only(%d, %d)', p1.rev(), b.rev())),
2567 b'p1.nbrevs': len(repo.revs('only(%d, %d)', p1.rev(), b.rev())),
2569 b'p1.nbmissingfiles': len(p1missing),
2568 b'p1.nbmissingfiles': len(p1missing),
2570 b'p2.node': p2.hex(),
2569 b'p2.node': p2.hex(),
2571 b'p2.nbrevs': len(repo.revs('only(%d, %d)', p2.rev(), b.rev())),
2570 b'p2.nbrevs': len(repo.revs('only(%d, %d)', p2.rev(), b.rev())),
2572 b'p2.nbmissingfiles': len(p2missing),
2571 b'p2.nbmissingfiles': len(p2missing),
2573 }
2572 }
2574 if dostats:
2573 if dostats:
2575 if p1missing:
2574 if p1missing:
2576 alldata['nbrevs'].append(
2575 alldata['nbrevs'].append(
2577 (data['p1.nbrevs'], b.hex(), p1.hex())
2576 (data['p1.nbrevs'], b.hex(), p1.hex())
2578 )
2577 )
2579 alldata['nbmissingfiles'].append(
2578 alldata['nbmissingfiles'].append(
2580 (data['p1.nbmissingfiles'], b.hex(), p1.hex())
2579 (data['p1.nbmissingfiles'], b.hex(), p1.hex())
2581 )
2580 )
2582 if p2missing:
2581 if p2missing:
2583 alldata['nbrevs'].append(
2582 alldata['nbrevs'].append(
2584 (data['p2.nbrevs'], b.hex(), p2.hex())
2583 (data['p2.nbrevs'], b.hex(), p2.hex())
2585 )
2584 )
2586 alldata['nbmissingfiles'].append(
2585 alldata['nbmissingfiles'].append(
2587 (data['p2.nbmissingfiles'], b.hex(), p2.hex())
2586 (data['p2.nbmissingfiles'], b.hex(), p2.hex())
2588 )
2587 )
2589 if dotiming:
2588 if dotiming:
2590 begin = util.timer()
2589 begin = util.timer()
2591 mergedata = copies.mergecopies(repo, p1, p2, b)
2590 mergedata = copies.mergecopies(repo, p1, p2, b)
2592 end = util.timer()
2591 end = util.timer()
2593 # not very stable timing since we did only one run
2592 # not very stable timing since we did only one run
2594 data['time'] = end - begin
2593 data['time'] = end - begin
2595 # mergedata contains five dicts: "copy", "movewithdir",
2594 # mergedata contains five dicts: "copy", "movewithdir",
2596 # "diverge", "renamedelete" and "dirmove".
2595 # "diverge", "renamedelete" and "dirmove".
2597 # The first 4 are about renamed file so lets count that.
2596 # The first 4 are about renamed file so lets count that.
2598 renames = len(mergedata[0])
2597 renames = len(mergedata[0])
2599 renames += len(mergedata[1])
2598 renames += len(mergedata[1])
2600 renames += len(mergedata[2])
2599 renames += len(mergedata[2])
2601 renames += len(mergedata[3])
2600 renames += len(mergedata[3])
2602 data['nbrenamedfiles'] = renames
2601 data['nbrenamedfiles'] = renames
2603 begin = util.timer()
2602 begin = util.timer()
2604 p1renames = copies.pathcopies(b, p1)
2603 p1renames = copies.pathcopies(b, p1)
2605 end = util.timer()
2604 end = util.timer()
2606 data['p1.time'] = end - begin
2605 data['p1.time'] = end - begin
2607 begin = util.timer()
2606 begin = util.timer()
2608 p2renames = copies.pathcopies(b, p2)
2607 p2renames = copies.pathcopies(b, p2)
2609 end = util.timer()
2608 end = util.timer()
2610 data['p2.time'] = end - begin
2609 data['p2.time'] = end - begin
2611 data['p1.renamedfiles'] = len(p1renames)
2610 data['p1.renamedfiles'] = len(p1renames)
2612 data['p2.renamedfiles'] = len(p2renames)
2611 data['p2.renamedfiles'] = len(p2renames)
2613
2612
2614 if dostats:
2613 if dostats:
2615 if p1missing:
2614 if p1missing:
2616 alldata['parentnbrenames'].append(
2615 alldata['parentnbrenames'].append(
2617 (data['p1.renamedfiles'], b.hex(), p1.hex())
2616 (data['p1.renamedfiles'], b.hex(), p1.hex())
2618 )
2617 )
2619 alldata['parenttime'].append(
2618 alldata['parenttime'].append(
2620 (data['p1.time'], b.hex(), p1.hex())
2619 (data['p1.time'], b.hex(), p1.hex())
2621 )
2620 )
2622 if p2missing:
2621 if p2missing:
2623 alldata['parentnbrenames'].append(
2622 alldata['parentnbrenames'].append(
2624 (data['p2.renamedfiles'], b.hex(), p2.hex())
2623 (data['p2.renamedfiles'], b.hex(), p2.hex())
2625 )
2624 )
2626 alldata['parenttime'].append(
2625 alldata['parenttime'].append(
2627 (data['p2.time'], b.hex(), p2.hex())
2626 (data['p2.time'], b.hex(), p2.hex())
2628 )
2627 )
2629 if p1missing or p2missing:
2628 if p1missing or p2missing:
2630 alldata['totalnbrenames'].append(
2629 alldata['totalnbrenames'].append(
2631 (
2630 (
2632 data['nbrenamedfiles'],
2631 data['nbrenamedfiles'],
2633 b.hex(),
2632 b.hex(),
2634 p1.hex(),
2633 p1.hex(),
2635 p2.hex(),
2634 p2.hex(),
2636 )
2635 )
2637 )
2636 )
2638 alldata['totaltime'].append(
2637 alldata['totaltime'].append(
2639 (data['time'], b.hex(), p1.hex(), p2.hex())
2638 (data['time'], b.hex(), p1.hex(), p2.hex())
2640 )
2639 )
2641 fm.startitem()
2640 fm.startitem()
2642 fm.data(**data)
2641 fm.data(**data)
2643 # make node pretty for the human output
2642 # make node pretty for the human output
2644 out = data.copy()
2643 out = data.copy()
2645 out['base'] = fm.hexfunc(b.node())
2644 out['base'] = fm.hexfunc(b.node())
2646 out['p1.node'] = fm.hexfunc(p1.node())
2645 out['p1.node'] = fm.hexfunc(p1.node())
2647 out['p2.node'] = fm.hexfunc(p2.node())
2646 out['p2.node'] = fm.hexfunc(p2.node())
2648 fm.plain(output % out)
2647 fm.plain(output % out)
2649
2648
2650 fm.end()
2649 fm.end()
2651 if dostats:
2650 if dostats:
2652 # use a second formatter because the data are quite different, not sure
2651 # use a second formatter because the data are quite different, not sure
2653 # how it flies with the templater.
2652 # how it flies with the templater.
2654 entries = [
2653 entries = [
2655 ('nbrevs', 'number of revision covered'),
2654 ('nbrevs', 'number of revision covered'),
2656 ('nbmissingfiles', 'number of missing files at head'),
2655 ('nbmissingfiles', 'number of missing files at head'),
2657 ]
2656 ]
2658 if dotiming:
2657 if dotiming:
2659 entries.append(
2658 entries.append(
2660 ('parentnbrenames', 'rename from one parent to base')
2659 ('parentnbrenames', 'rename from one parent to base')
2661 )
2660 )
2662 entries.append(('totalnbrenames', 'total number of renames'))
2661 entries.append(('totalnbrenames', 'total number of renames'))
2663 entries.append(('parenttime', 'time for one parent'))
2662 entries.append(('parenttime', 'time for one parent'))
2664 entries.append(('totaltime', 'time for both parents'))
2663 entries.append(('totaltime', 'time for both parents'))
2665 _displaystats(ui, opts, entries, alldata)
2664 _displaystats(ui, opts, entries, alldata)
2666
2665
2667
2666
2668 @command(
2667 @command(
2669 b'perf::helper-pathcopies|perfhelper-pathcopies',
2668 b'perf::helper-pathcopies|perfhelper-pathcopies',
2670 formatteropts
2669 formatteropts
2671 + [
2670 + [
2672 (b'r', b'revs', [], b'restrict search to these revisions'),
2671 (b'r', b'revs', [], b'restrict search to these revisions'),
2673 (b'', b'timing', False, b'provides extra data (costly)'),
2672 (b'', b'timing', False, b'provides extra data (costly)'),
2674 (b'', b'stats', False, b'provides statistic about the measured data'),
2673 (b'', b'stats', False, b'provides statistic about the measured data'),
2675 ],
2674 ],
2676 )
2675 )
2677 def perfhelperpathcopies(ui, repo, revs=[], **opts):
2676 def perfhelperpathcopies(ui, repo, revs=[], **opts):
2678 """find statistic about potential parameters for the `perftracecopies`
2677 """find statistic about potential parameters for the `perftracecopies`
2679
2678
2680 This command find source-destination pair relevant for copytracing testing.
2679 This command find source-destination pair relevant for copytracing testing.
2681 It report value for some of the parameters that impact copy tracing time.
2680 It report value for some of the parameters that impact copy tracing time.
2682
2681
2683 If `--timing` is set, rename detection is run and the associated timing
2682 If `--timing` is set, rename detection is run and the associated timing
2684 will be reported. The extra details comes at the cost of a slower command
2683 will be reported. The extra details comes at the cost of a slower command
2685 execution.
2684 execution.
2686
2685
2687 Since the rename detection is only run once, other factors might easily
2686 Since the rename detection is only run once, other factors might easily
2688 affect the precision of the timing. However it should give a good
2687 affect the precision of the timing. However it should give a good
2689 approximation of which revision pairs are very costly.
2688 approximation of which revision pairs are very costly.
2690 """
2689 """
2691 opts = _byteskwargs(opts)
2690 opts = _byteskwargs(opts)
2692 fm = ui.formatter(b'perf', opts)
2691 fm = ui.formatter(b'perf', opts)
2693 dotiming = opts[b'timing']
2692 dotiming = opts[b'timing']
2694 dostats = opts[b'stats']
2693 dostats = opts[b'stats']
2695
2694
2696 if dotiming:
2695 if dotiming:
2697 header = '%12s %12s %12s %12s %12s %12s\n'
2696 header = '%12s %12s %12s %12s %12s %12s\n'
2698 output = (
2697 output = (
2699 "%(source)12s %(destination)12s "
2698 "%(source)12s %(destination)12s "
2700 "%(nbrevs)12d %(nbmissingfiles)12d "
2699 "%(nbrevs)12d %(nbmissingfiles)12d "
2701 "%(nbrenamedfiles)12d %(time)18.5f\n"
2700 "%(nbrenamedfiles)12d %(time)18.5f\n"
2702 )
2701 )
2703 header_names = (
2702 header_names = (
2704 "source",
2703 "source",
2705 "destination",
2704 "destination",
2706 "nb-revs",
2705 "nb-revs",
2707 "nb-files",
2706 "nb-files",
2708 "nb-renames",
2707 "nb-renames",
2709 "time",
2708 "time",
2710 )
2709 )
2711 fm.plain(header % header_names)
2710 fm.plain(header % header_names)
2712 else:
2711 else:
2713 header = '%12s %12s %12s %12s\n'
2712 header = '%12s %12s %12s %12s\n'
2714 output = (
2713 output = (
2715 "%(source)12s %(destination)12s "
2714 "%(source)12s %(destination)12s "
2716 "%(nbrevs)12d %(nbmissingfiles)12d\n"
2715 "%(nbrevs)12d %(nbmissingfiles)12d\n"
2717 )
2716 )
2718 fm.plain(header % ("source", "destination", "nb-revs", "nb-files"))
2717 fm.plain(header % ("source", "destination", "nb-revs", "nb-files"))
2719
2718
2720 if not revs:
2719 if not revs:
2721 revs = ['all()']
2720 revs = ['all()']
2722 revs = scmutil.revrange(repo, revs)
2721 revs = scmutil.revrange(repo, revs)
2723
2722
2724 if dostats:
2723 if dostats:
2725 alldata = {
2724 alldata = {
2726 'nbrevs': [],
2725 'nbrevs': [],
2727 'nbmissingfiles': [],
2726 'nbmissingfiles': [],
2728 }
2727 }
2729 if dotiming:
2728 if dotiming:
2730 alldata['nbrenames'] = []
2729 alldata['nbrenames'] = []
2731 alldata['time'] = []
2730 alldata['time'] = []
2732
2731
2733 roi = repo.revs('merge() and %ld', revs)
2732 roi = repo.revs('merge() and %ld', revs)
2734 for r in roi:
2733 for r in roi:
2735 ctx = repo[r]
2734 ctx = repo[r]
2736 p1 = ctx.p1().rev()
2735 p1 = ctx.p1().rev()
2737 p2 = ctx.p2().rev()
2736 p2 = ctx.p2().rev()
2738 bases = repo.changelog._commonancestorsheads(p1, p2)
2737 bases = repo.changelog._commonancestorsheads(p1, p2)
2739 for p in (p1, p2):
2738 for p in (p1, p2):
2740 for b in bases:
2739 for b in bases:
2741 base = repo[b]
2740 base = repo[b]
2742 parent = repo[p]
2741 parent = repo[p]
2743 missing = copies._computeforwardmissing(base, parent)
2742 missing = copies._computeforwardmissing(base, parent)
2744 if not missing:
2743 if not missing:
2745 continue
2744 continue
2746 data = {
2745 data = {
2747 b'source': base.hex(),
2746 b'source': base.hex(),
2748 b'destination': parent.hex(),
2747 b'destination': parent.hex(),
2749 b'nbrevs': len(repo.revs('only(%d, %d)', p, b)),
2748 b'nbrevs': len(repo.revs('only(%d, %d)', p, b)),
2750 b'nbmissingfiles': len(missing),
2749 b'nbmissingfiles': len(missing),
2751 }
2750 }
2752 if dostats:
2751 if dostats:
2753 alldata['nbrevs'].append(
2752 alldata['nbrevs'].append(
2754 (
2753 (
2755 data['nbrevs'],
2754 data['nbrevs'],
2756 base.hex(),
2755 base.hex(),
2757 parent.hex(),
2756 parent.hex(),
2758 )
2757 )
2759 )
2758 )
2760 alldata['nbmissingfiles'].append(
2759 alldata['nbmissingfiles'].append(
2761 (
2760 (
2762 data['nbmissingfiles'],
2761 data['nbmissingfiles'],
2763 base.hex(),
2762 base.hex(),
2764 parent.hex(),
2763 parent.hex(),
2765 )
2764 )
2766 )
2765 )
2767 if dotiming:
2766 if dotiming:
2768 begin = util.timer()
2767 begin = util.timer()
2769 renames = copies.pathcopies(base, parent)
2768 renames = copies.pathcopies(base, parent)
2770 end = util.timer()
2769 end = util.timer()
2771 # not very stable timing since we did only one run
2770 # not very stable timing since we did only one run
2772 data['time'] = end - begin
2771 data['time'] = end - begin
2773 data['nbrenamedfiles'] = len(renames)
2772 data['nbrenamedfiles'] = len(renames)
2774 if dostats:
2773 if dostats:
2775 alldata['time'].append(
2774 alldata['time'].append(
2776 (
2775 (
2777 data['time'],
2776 data['time'],
2778 base.hex(),
2777 base.hex(),
2779 parent.hex(),
2778 parent.hex(),
2780 )
2779 )
2781 )
2780 )
2782 alldata['nbrenames'].append(
2781 alldata['nbrenames'].append(
2783 (
2782 (
2784 data['nbrenamedfiles'],
2783 data['nbrenamedfiles'],
2785 base.hex(),
2784 base.hex(),
2786 parent.hex(),
2785 parent.hex(),
2787 )
2786 )
2788 )
2787 )
2789 fm.startitem()
2788 fm.startitem()
2790 fm.data(**data)
2789 fm.data(**data)
2791 out = data.copy()
2790 out = data.copy()
2792 out['source'] = fm.hexfunc(base.node())
2791 out['source'] = fm.hexfunc(base.node())
2793 out['destination'] = fm.hexfunc(parent.node())
2792 out['destination'] = fm.hexfunc(parent.node())
2794 fm.plain(output % out)
2793 fm.plain(output % out)
2795
2794
2796 fm.end()
2795 fm.end()
2797 if dostats:
2796 if dostats:
2798 entries = [
2797 entries = [
2799 ('nbrevs', 'number of revision covered'),
2798 ('nbrevs', 'number of revision covered'),
2800 ('nbmissingfiles', 'number of missing files at head'),
2799 ('nbmissingfiles', 'number of missing files at head'),
2801 ]
2800 ]
2802 if dotiming:
2801 if dotiming:
2803 entries.append(('nbrenames', 'renamed files'))
2802 entries.append(('nbrenames', 'renamed files'))
2804 entries.append(('time', 'time'))
2803 entries.append(('time', 'time'))
2805 _displaystats(ui, opts, entries, alldata)
2804 _displaystats(ui, opts, entries, alldata)
2806
2805
2807
2806
2808 @command(b'perf::cca|perfcca', formatteropts)
2807 @command(b'perf::cca|perfcca', formatteropts)
2809 def perfcca(ui, repo, **opts):
2808 def perfcca(ui, repo, **opts):
2810 opts = _byteskwargs(opts)
2809 opts = _byteskwargs(opts)
2811 timer, fm = gettimer(ui, opts)
2810 timer, fm = gettimer(ui, opts)
2812 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
2811 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
2813 fm.end()
2812 fm.end()
2814
2813
2815
2814
2816 @command(b'perf::fncacheload|perffncacheload', formatteropts)
2815 @command(b'perf::fncacheload|perffncacheload', formatteropts)
2817 def perffncacheload(ui, repo, **opts):
2816 def perffncacheload(ui, repo, **opts):
2818 opts = _byteskwargs(opts)
2817 opts = _byteskwargs(opts)
2819 timer, fm = gettimer(ui, opts)
2818 timer, fm = gettimer(ui, opts)
2820 s = repo.store
2819 s = repo.store
2821
2820
2822 def d():
2821 def d():
2823 s.fncache._load()
2822 s.fncache._load()
2824
2823
2825 timer(d)
2824 timer(d)
2826 fm.end()
2825 fm.end()
2827
2826
2828
2827
2829 @command(b'perf::fncachewrite|perffncachewrite', formatteropts)
2828 @command(b'perf::fncachewrite|perffncachewrite', formatteropts)
2830 def perffncachewrite(ui, repo, **opts):
2829 def perffncachewrite(ui, repo, **opts):
2831 opts = _byteskwargs(opts)
2830 opts = _byteskwargs(opts)
2832 timer, fm = gettimer(ui, opts)
2831 timer, fm = gettimer(ui, opts)
2833 s = repo.store
2832 s = repo.store
2834 lock = repo.lock()
2833 lock = repo.lock()
2835 s.fncache._load()
2834 s.fncache._load()
2836 tr = repo.transaction(b'perffncachewrite')
2835 tr = repo.transaction(b'perffncachewrite')
2837 tr.addbackup(b'fncache')
2836 tr.addbackup(b'fncache')
2838
2837
2839 def d():
2838 def d():
2840 s.fncache._dirty = True
2839 s.fncache._dirty = True
2841 s.fncache.write(tr)
2840 s.fncache.write(tr)
2842
2841
2843 timer(d)
2842 timer(d)
2844 tr.close()
2843 tr.close()
2845 lock.release()
2844 lock.release()
2846 fm.end()
2845 fm.end()
2847
2846
2848
2847
2849 @command(b'perf::fncacheencode|perffncacheencode', formatteropts)
2848 @command(b'perf::fncacheencode|perffncacheencode', formatteropts)
2850 def perffncacheencode(ui, repo, **opts):
2849 def perffncacheencode(ui, repo, **opts):
2851 opts = _byteskwargs(opts)
2850 opts = _byteskwargs(opts)
2852 timer, fm = gettimer(ui, opts)
2851 timer, fm = gettimer(ui, opts)
2853 s = repo.store
2852 s = repo.store
2854 s.fncache._load()
2853 s.fncache._load()
2855
2854
2856 def d():
2855 def d():
2857 for p in s.fncache.entries:
2856 for p in s.fncache.entries:
2858 s.encode(p)
2857 s.encode(p)
2859
2858
2860 timer(d)
2859 timer(d)
2861 fm.end()
2860 fm.end()
2862
2861
2863
2862
2864 def _bdiffworker(q, blocks, xdiff, ready, done):
2863 def _bdiffworker(q, blocks, xdiff, ready, done):
2865 while not done.is_set():
2864 while not done.is_set():
2866 pair = q.get()
2865 pair = q.get()
2867 while pair is not None:
2866 while pair is not None:
2868 if xdiff:
2867 if xdiff:
2869 mdiff.bdiff.xdiffblocks(*pair)
2868 mdiff.bdiff.xdiffblocks(*pair)
2870 elif blocks:
2869 elif blocks:
2871 mdiff.bdiff.blocks(*pair)
2870 mdiff.bdiff.blocks(*pair)
2872 else:
2871 else:
2873 mdiff.textdiff(*pair)
2872 mdiff.textdiff(*pair)
2874 q.task_done()
2873 q.task_done()
2875 pair = q.get()
2874 pair = q.get()
2876 q.task_done() # for the None one
2875 q.task_done() # for the None one
2877 with ready:
2876 with ready:
2878 ready.wait()
2877 ready.wait()
2879
2878
2880
2879
2881 def _manifestrevision(repo, mnode):
2880 def _manifestrevision(repo, mnode):
2882 ml = repo.manifestlog
2881 ml = repo.manifestlog
2883
2882
2884 if util.safehasattr(ml, b'getstorage'):
2883 if util.safehasattr(ml, b'getstorage'):
2885 store = ml.getstorage(b'')
2884 store = ml.getstorage(b'')
2886 else:
2885 else:
2887 store = ml._revlog
2886 store = ml._revlog
2888
2887
2889 return store.revision(mnode)
2888 return store.revision(mnode)
2890
2889
2891
2890
2892 @command(
2891 @command(
2893 b'perf::bdiff|perfbdiff',
2892 b'perf::bdiff|perfbdiff',
2894 revlogopts
2893 revlogopts
2895 + formatteropts
2894 + formatteropts
2896 + [
2895 + [
2897 (
2896 (
2898 b'',
2897 b'',
2899 b'count',
2898 b'count',
2900 1,
2899 1,
2901 b'number of revisions to test (when using --startrev)',
2900 b'number of revisions to test (when using --startrev)',
2902 ),
2901 ),
2903 (b'', b'alldata', False, b'test bdiffs for all associated revisions'),
2902 (b'', b'alldata', False, b'test bdiffs for all associated revisions'),
2904 (b'', b'threads', 0, b'number of thread to use (disable with 0)'),
2903 (b'', b'threads', 0, b'number of thread to use (disable with 0)'),
2905 (b'', b'blocks', False, b'test computing diffs into blocks'),
2904 (b'', b'blocks', False, b'test computing diffs into blocks'),
2906 (b'', b'xdiff', False, b'use xdiff algorithm'),
2905 (b'', b'xdiff', False, b'use xdiff algorithm'),
2907 ],
2906 ],
2908 b'-c|-m|FILE REV',
2907 b'-c|-m|FILE REV',
2909 )
2908 )
2910 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
2909 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
2911 """benchmark a bdiff between revisions
2910 """benchmark a bdiff between revisions
2912
2911
2913 By default, benchmark a bdiff between its delta parent and itself.
2912 By default, benchmark a bdiff between its delta parent and itself.
2914
2913
2915 With ``--count``, benchmark bdiffs between delta parents and self for N
2914 With ``--count``, benchmark bdiffs between delta parents and self for N
2916 revisions starting at the specified revision.
2915 revisions starting at the specified revision.
2917
2916
2918 With ``--alldata``, assume the requested revision is a changeset and
2917 With ``--alldata``, assume the requested revision is a changeset and
2919 measure bdiffs for all changes related to that changeset (manifest
2918 measure bdiffs for all changes related to that changeset (manifest
2920 and filelogs).
2919 and filelogs).
2921 """
2920 """
2922 opts = _byteskwargs(opts)
2921 opts = _byteskwargs(opts)
2923
2922
2924 if opts[b'xdiff'] and not opts[b'blocks']:
2923 if opts[b'xdiff'] and not opts[b'blocks']:
2925 raise error.CommandError(b'perfbdiff', b'--xdiff requires --blocks')
2924 raise error.CommandError(b'perfbdiff', b'--xdiff requires --blocks')
2926
2925
2927 if opts[b'alldata']:
2926 if opts[b'alldata']:
2928 opts[b'changelog'] = True
2927 opts[b'changelog'] = True
2929
2928
2930 if opts.get(b'changelog') or opts.get(b'manifest'):
2929 if opts.get(b'changelog') or opts.get(b'manifest'):
2931 file_, rev = None, file_
2930 file_, rev = None, file_
2932 elif rev is None:
2931 elif rev is None:
2933 raise error.CommandError(b'perfbdiff', b'invalid arguments')
2932 raise error.CommandError(b'perfbdiff', b'invalid arguments')
2934
2933
2935 blocks = opts[b'blocks']
2934 blocks = opts[b'blocks']
2936 xdiff = opts[b'xdiff']
2935 xdiff = opts[b'xdiff']
2937 textpairs = []
2936 textpairs = []
2938
2937
2939 r = cmdutil.openrevlog(repo, b'perfbdiff', file_, opts)
2938 r = cmdutil.openrevlog(repo, b'perfbdiff', file_, opts)
2940
2939
2941 startrev = r.rev(r.lookup(rev))
2940 startrev = r.rev(r.lookup(rev))
2942 for rev in range(startrev, min(startrev + count, len(r) - 1)):
2941 for rev in range(startrev, min(startrev + count, len(r) - 1)):
2943 if opts[b'alldata']:
2942 if opts[b'alldata']:
2944 # Load revisions associated with changeset.
2943 # Load revisions associated with changeset.
2945 ctx = repo[rev]
2944 ctx = repo[rev]
2946 mtext = _manifestrevision(repo, ctx.manifestnode())
2945 mtext = _manifestrevision(repo, ctx.manifestnode())
2947 for pctx in ctx.parents():
2946 for pctx in ctx.parents():
2948 pman = _manifestrevision(repo, pctx.manifestnode())
2947 pman = _manifestrevision(repo, pctx.manifestnode())
2949 textpairs.append((pman, mtext))
2948 textpairs.append((pman, mtext))
2950
2949
2951 # Load filelog revisions by iterating manifest delta.
2950 # Load filelog revisions by iterating manifest delta.
2952 man = ctx.manifest()
2951 man = ctx.manifest()
2953 pman = ctx.p1().manifest()
2952 pman = ctx.p1().manifest()
2954 for filename, change in pman.diff(man).items():
2953 for filename, change in pman.diff(man).items():
2955 fctx = repo.file(filename)
2954 fctx = repo.file(filename)
2956 f1 = fctx.revision(change[0][0] or -1)
2955 f1 = fctx.revision(change[0][0] or -1)
2957 f2 = fctx.revision(change[1][0] or -1)
2956 f2 = fctx.revision(change[1][0] or -1)
2958 textpairs.append((f1, f2))
2957 textpairs.append((f1, f2))
2959 else:
2958 else:
2960 dp = r.deltaparent(rev)
2959 dp = r.deltaparent(rev)
2961 textpairs.append((r.revision(dp), r.revision(rev)))
2960 textpairs.append((r.revision(dp), r.revision(rev)))
2962
2961
2963 withthreads = threads > 0
2962 withthreads = threads > 0
2964 if not withthreads:
2963 if not withthreads:
2965
2964
2966 def d():
2965 def d():
2967 for pair in textpairs:
2966 for pair in textpairs:
2968 if xdiff:
2967 if xdiff:
2969 mdiff.bdiff.xdiffblocks(*pair)
2968 mdiff.bdiff.xdiffblocks(*pair)
2970 elif blocks:
2969 elif blocks:
2971 mdiff.bdiff.blocks(*pair)
2970 mdiff.bdiff.blocks(*pair)
2972 else:
2971 else:
2973 mdiff.textdiff(*pair)
2972 mdiff.textdiff(*pair)
2974
2973
2975 else:
2974 else:
2976 q = queue()
2975 q = queue()
2977 for i in _xrange(threads):
2976 for i in _xrange(threads):
2978 q.put(None)
2977 q.put(None)
2979 ready = threading.Condition()
2978 ready = threading.Condition()
2980 done = threading.Event()
2979 done = threading.Event()
2981 for i in _xrange(threads):
2980 for i in _xrange(threads):
2982 threading.Thread(
2981 threading.Thread(
2983 target=_bdiffworker, args=(q, blocks, xdiff, ready, done)
2982 target=_bdiffworker, args=(q, blocks, xdiff, ready, done)
2984 ).start()
2983 ).start()
2985 q.join()
2984 q.join()
2986
2985
2987 def d():
2986 def d():
2988 for pair in textpairs:
2987 for pair in textpairs:
2989 q.put(pair)
2988 q.put(pair)
2990 for i in _xrange(threads):
2989 for i in _xrange(threads):
2991 q.put(None)
2990 q.put(None)
2992 with ready:
2991 with ready:
2993 ready.notify_all()
2992 ready.notify_all()
2994 q.join()
2993 q.join()
2995
2994
2996 timer, fm = gettimer(ui, opts)
2995 timer, fm = gettimer(ui, opts)
2997 timer(d)
2996 timer(d)
2998 fm.end()
2997 fm.end()
2999
2998
3000 if withthreads:
2999 if withthreads:
3001 done.set()
3000 done.set()
3002 for i in _xrange(threads):
3001 for i in _xrange(threads):
3003 q.put(None)
3002 q.put(None)
3004 with ready:
3003 with ready:
3005 ready.notify_all()
3004 ready.notify_all()
3006
3005
3007
3006
3008 @command(
3007 @command(
3009 b'perf::unbundle',
3008 b'perf::unbundle',
3010 formatteropts,
3009 formatteropts,
3011 b'BUNDLE_FILE',
3010 b'BUNDLE_FILE',
3012 )
3011 )
3013 def perf_unbundle(ui, repo, fname, **opts):
3012 def perf_unbundle(ui, repo, fname, **opts):
3014 """benchmark application of a bundle in a repository.
3013 """benchmark application of a bundle in a repository.
3015
3014
3016 This does not include the final transaction processing"""
3015 This does not include the final transaction processing"""
3017
3016
3018 from mercurial import exchange
3017 from mercurial import exchange
3019 from mercurial import bundle2
3018 from mercurial import bundle2
3020 from mercurial import transaction
3019 from mercurial import transaction
3021
3020
3022 opts = _byteskwargs(opts)
3021 opts = _byteskwargs(opts)
3023
3022
3024 ### some compatibility hotfix
3023 ### some compatibility hotfix
3025 #
3024 #
3026 # the data attribute is dropped in 63edc384d3b7 a changeset introducing a
3025 # the data attribute is dropped in 63edc384d3b7 a changeset introducing a
3027 # critical regression that break transaction rollback for files that are
3026 # critical regression that break transaction rollback for files that are
3028 # de-inlined.
3027 # de-inlined.
3029 method = transaction.transaction._addentry
3028 method = transaction.transaction._addentry
3030 pre_63edc384d3b7 = "data" in getargspec(method).args
3029 pre_63edc384d3b7 = "data" in getargspec(method).args
3031 # the `detailed_exit_code` attribute is introduced in 33c0c25d0b0f
3030 # the `detailed_exit_code` attribute is introduced in 33c0c25d0b0f
3032 # a changeset that is a close descendant of 18415fc918a1, the changeset
3031 # a changeset that is a close descendant of 18415fc918a1, the changeset
3033 # that conclude the fix run for the bug introduced in 63edc384d3b7.
3032 # that conclude the fix run for the bug introduced in 63edc384d3b7.
3034 args = getargspec(error.Abort.__init__).args
3033 args = getargspec(error.Abort.__init__).args
3035 post_18415fc918a1 = "detailed_exit_code" in args
3034 post_18415fc918a1 = "detailed_exit_code" in args
3036
3035
3037 old_max_inline = None
3036 old_max_inline = None
3038 try:
3037 try:
3039 if not (pre_63edc384d3b7 or post_18415fc918a1):
3038 if not (pre_63edc384d3b7 or post_18415fc918a1):
3040 # disable inlining
3039 # disable inlining
3041 old_max_inline = mercurial.revlog._maxinline
3040 old_max_inline = mercurial.revlog._maxinline
3042 # large enough to never happen
3041 # large enough to never happen
3043 mercurial.revlog._maxinline = 2 ** 50
3042 mercurial.revlog._maxinline = 2 ** 50
3044
3043
3045 with repo.lock():
3044 with repo.lock():
3046 bundle = [None, None]
3045 bundle = [None, None]
3047 orig_quiet = repo.ui.quiet
3046 orig_quiet = repo.ui.quiet
3048 try:
3047 try:
3049 repo.ui.quiet = True
3048 repo.ui.quiet = True
3050 with open(fname, mode="rb") as f:
3049 with open(fname, mode="rb") as f:
3051
3050
3052 def noop_report(*args, **kwargs):
3051 def noop_report(*args, **kwargs):
3053 pass
3052 pass
3054
3053
3055 def setup():
3054 def setup():
3056 gen, tr = bundle
3055 gen, tr = bundle
3057 if tr is not None:
3056 if tr is not None:
3058 tr.abort()
3057 tr.abort()
3059 bundle[:] = [None, None]
3058 bundle[:] = [None, None]
3060 f.seek(0)
3059 f.seek(0)
3061 bundle[0] = exchange.readbundle(ui, f, fname)
3060 bundle[0] = exchange.readbundle(ui, f, fname)
3062 bundle[1] = repo.transaction(b'perf::unbundle')
3061 bundle[1] = repo.transaction(b'perf::unbundle')
3063 # silence the transaction
3062 # silence the transaction
3064 bundle[1]._report = noop_report
3063 bundle[1]._report = noop_report
3065
3064
3066 def apply():
3065 def apply():
3067 gen, tr = bundle
3066 gen, tr = bundle
3068 bundle2.applybundle(
3067 bundle2.applybundle(
3069 repo,
3068 repo,
3070 gen,
3069 gen,
3071 tr,
3070 tr,
3072 source=b'perf::unbundle',
3071 source=b'perf::unbundle',
3073 url=fname,
3072 url=fname,
3074 )
3073 )
3075
3074
3076 timer, fm = gettimer(ui, opts)
3075 timer, fm = gettimer(ui, opts)
3077 timer(apply, setup=setup)
3076 timer(apply, setup=setup)
3078 fm.end()
3077 fm.end()
3079 finally:
3078 finally:
3080 repo.ui.quiet == orig_quiet
3079 repo.ui.quiet == orig_quiet
3081 gen, tr = bundle
3080 gen, tr = bundle
3082 if tr is not None:
3081 if tr is not None:
3083 tr.abort()
3082 tr.abort()
3084 finally:
3083 finally:
3085 if old_max_inline is not None:
3084 if old_max_inline is not None:
3086 mercurial.revlog._maxinline = old_max_inline
3085 mercurial.revlog._maxinline = old_max_inline
3087
3086
3088
3087
3089 @command(
3088 @command(
3090 b'perf::unidiff|perfunidiff',
3089 b'perf::unidiff|perfunidiff',
3091 revlogopts
3090 revlogopts
3092 + formatteropts
3091 + formatteropts
3093 + [
3092 + [
3094 (
3093 (
3095 b'',
3094 b'',
3096 b'count',
3095 b'count',
3097 1,
3096 1,
3098 b'number of revisions to test (when using --startrev)',
3097 b'number of revisions to test (when using --startrev)',
3099 ),
3098 ),
3100 (b'', b'alldata', False, b'test unidiffs for all associated revisions'),
3099 (b'', b'alldata', False, b'test unidiffs for all associated revisions'),
3101 ],
3100 ],
3102 b'-c|-m|FILE REV',
3101 b'-c|-m|FILE REV',
3103 )
3102 )
3104 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
3103 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
3105 """benchmark a unified diff between revisions
3104 """benchmark a unified diff between revisions
3106
3105
3107 This doesn't include any copy tracing - it's just a unified diff
3106 This doesn't include any copy tracing - it's just a unified diff
3108 of the texts.
3107 of the texts.
3109
3108
3110 By default, benchmark a diff between its delta parent and itself.
3109 By default, benchmark a diff between its delta parent and itself.
3111
3110
3112 With ``--count``, benchmark diffs between delta parents and self for N
3111 With ``--count``, benchmark diffs between delta parents and self for N
3113 revisions starting at the specified revision.
3112 revisions starting at the specified revision.
3114
3113
3115 With ``--alldata``, assume the requested revision is a changeset and
3114 With ``--alldata``, assume the requested revision is a changeset and
3116 measure diffs for all changes related to that changeset (manifest
3115 measure diffs for all changes related to that changeset (manifest
3117 and filelogs).
3116 and filelogs).
3118 """
3117 """
3119 opts = _byteskwargs(opts)
3118 opts = _byteskwargs(opts)
3120 if opts[b'alldata']:
3119 if opts[b'alldata']:
3121 opts[b'changelog'] = True
3120 opts[b'changelog'] = True
3122
3121
3123 if opts.get(b'changelog') or opts.get(b'manifest'):
3122 if opts.get(b'changelog') or opts.get(b'manifest'):
3124 file_, rev = None, file_
3123 file_, rev = None, file_
3125 elif rev is None:
3124 elif rev is None:
3126 raise error.CommandError(b'perfunidiff', b'invalid arguments')
3125 raise error.CommandError(b'perfunidiff', b'invalid arguments')
3127
3126
3128 textpairs = []
3127 textpairs = []
3129
3128
3130 r = cmdutil.openrevlog(repo, b'perfunidiff', file_, opts)
3129 r = cmdutil.openrevlog(repo, b'perfunidiff', file_, opts)
3131
3130
3132 startrev = r.rev(r.lookup(rev))
3131 startrev = r.rev(r.lookup(rev))
3133 for rev in range(startrev, min(startrev + count, len(r) - 1)):
3132 for rev in range(startrev, min(startrev + count, len(r) - 1)):
3134 if opts[b'alldata']:
3133 if opts[b'alldata']:
3135 # Load revisions associated with changeset.
3134 # Load revisions associated with changeset.
3136 ctx = repo[rev]
3135 ctx = repo[rev]
3137 mtext = _manifestrevision(repo, ctx.manifestnode())
3136 mtext = _manifestrevision(repo, ctx.manifestnode())
3138 for pctx in ctx.parents():
3137 for pctx in ctx.parents():
3139 pman = _manifestrevision(repo, pctx.manifestnode())
3138 pman = _manifestrevision(repo, pctx.manifestnode())
3140 textpairs.append((pman, mtext))
3139 textpairs.append((pman, mtext))
3141
3140
3142 # Load filelog revisions by iterating manifest delta.
3141 # Load filelog revisions by iterating manifest delta.
3143 man = ctx.manifest()
3142 man = ctx.manifest()
3144 pman = ctx.p1().manifest()
3143 pman = ctx.p1().manifest()
3145 for filename, change in pman.diff(man).items():
3144 for filename, change in pman.diff(man).items():
3146 fctx = repo.file(filename)
3145 fctx = repo.file(filename)
3147 f1 = fctx.revision(change[0][0] or -1)
3146 f1 = fctx.revision(change[0][0] or -1)
3148 f2 = fctx.revision(change[1][0] or -1)
3147 f2 = fctx.revision(change[1][0] or -1)
3149 textpairs.append((f1, f2))
3148 textpairs.append((f1, f2))
3150 else:
3149 else:
3151 dp = r.deltaparent(rev)
3150 dp = r.deltaparent(rev)
3152 textpairs.append((r.revision(dp), r.revision(rev)))
3151 textpairs.append((r.revision(dp), r.revision(rev)))
3153
3152
3154 def d():
3153 def d():
3155 for left, right in textpairs:
3154 for left, right in textpairs:
3156 # The date strings don't matter, so we pass empty strings.
3155 # The date strings don't matter, so we pass empty strings.
3157 headerlines, hunks = mdiff.unidiff(
3156 headerlines, hunks = mdiff.unidiff(
3158 left, b'', right, b'', b'left', b'right', binary=False
3157 left, b'', right, b'', b'left', b'right', binary=False
3159 )
3158 )
3160 # consume iterators in roughly the way patch.py does
3159 # consume iterators in roughly the way patch.py does
3161 b'\n'.join(headerlines)
3160 b'\n'.join(headerlines)
3162 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
3161 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
3163
3162
3164 timer, fm = gettimer(ui, opts)
3163 timer, fm = gettimer(ui, opts)
3165 timer(d)
3164 timer(d)
3166 fm.end()
3165 fm.end()
3167
3166
3168
3167
3169 @command(b'perf::diffwd|perfdiffwd', formatteropts)
3168 @command(b'perf::diffwd|perfdiffwd', formatteropts)
3170 def perfdiffwd(ui, repo, **opts):
3169 def perfdiffwd(ui, repo, **opts):
3171 """Profile diff of working directory changes"""
3170 """Profile diff of working directory changes"""
3172 opts = _byteskwargs(opts)
3171 opts = _byteskwargs(opts)
3173 timer, fm = gettimer(ui, opts)
3172 timer, fm = gettimer(ui, opts)
3174 options = {
3173 options = {
3175 'w': 'ignore_all_space',
3174 'w': 'ignore_all_space',
3176 'b': 'ignore_space_change',
3175 'b': 'ignore_space_change',
3177 'B': 'ignore_blank_lines',
3176 'B': 'ignore_blank_lines',
3178 }
3177 }
3179
3178
3180 for diffopt in ('', 'w', 'b', 'B', 'wB'):
3179 for diffopt in ('', 'w', 'b', 'B', 'wB'):
3181 opts = {options[c]: b'1' for c in diffopt}
3180 opts = {options[c]: b'1' for c in diffopt}
3182
3181
3183 def d():
3182 def d():
3184 ui.pushbuffer()
3183 ui.pushbuffer()
3185 commands.diff(ui, repo, **opts)
3184 commands.diff(ui, repo, **opts)
3186 ui.popbuffer()
3185 ui.popbuffer()
3187
3186
3188 diffopt = diffopt.encode('ascii')
3187 diffopt = diffopt.encode('ascii')
3189 title = b'diffopts: %s' % (diffopt and (b'-' + diffopt) or b'none')
3188 title = b'diffopts: %s' % (diffopt and (b'-' + diffopt) or b'none')
3190 timer(d, title=title)
3189 timer(d, title=title)
3191 fm.end()
3190 fm.end()
3192
3191
3193
3192
3194 @command(
3193 @command(
3195 b'perf::revlogindex|perfrevlogindex',
3194 b'perf::revlogindex|perfrevlogindex',
3196 revlogopts + formatteropts,
3195 revlogopts + formatteropts,
3197 b'-c|-m|FILE',
3196 b'-c|-m|FILE',
3198 )
3197 )
3199 def perfrevlogindex(ui, repo, file_=None, **opts):
3198 def perfrevlogindex(ui, repo, file_=None, **opts):
3200 """Benchmark operations against a revlog index.
3199 """Benchmark operations against a revlog index.
3201
3200
3202 This tests constructing a revlog instance, reading index data,
3201 This tests constructing a revlog instance, reading index data,
3203 parsing index data, and performing various operations related to
3202 parsing index data, and performing various operations related to
3204 index data.
3203 index data.
3205 """
3204 """
3206
3205
3207 opts = _byteskwargs(opts)
3206 opts = _byteskwargs(opts)
3208
3207
3209 rl = cmdutil.openrevlog(repo, b'perfrevlogindex', file_, opts)
3208 rl = cmdutil.openrevlog(repo, b'perfrevlogindex', file_, opts)
3210
3209
3211 opener = getattr(rl, 'opener') # trick linter
3210 opener = getattr(rl, 'opener') # trick linter
3212 # compat with hg <= 5.8
3211 # compat with hg <= 5.8
3213 radix = getattr(rl, 'radix', None)
3212 radix = getattr(rl, 'radix', None)
3214 indexfile = getattr(rl, '_indexfile', None)
3213 indexfile = getattr(rl, '_indexfile', None)
3215 if indexfile is None:
3214 if indexfile is None:
3216 # compatibility with <= hg-5.8
3215 # compatibility with <= hg-5.8
3217 indexfile = getattr(rl, 'indexfile')
3216 indexfile = getattr(rl, 'indexfile')
3218 data = opener.read(indexfile)
3217 data = opener.read(indexfile)
3219
3218
3220 header = struct.unpack(b'>I', data[0:4])[0]
3219 header = struct.unpack(b'>I', data[0:4])[0]
3221 version = header & 0xFFFF
3220 version = header & 0xFFFF
3222 if version == 1:
3221 if version == 1:
3223 inline = header & (1 << 16)
3222 inline = header & (1 << 16)
3224 else:
3223 else:
3225 raise error.Abort(b'unsupported revlog version: %d' % version)
3224 raise error.Abort(b'unsupported revlog version: %d' % version)
3226
3225
3227 parse_index_v1 = getattr(mercurial.revlog, 'parse_index_v1', None)
3226 parse_index_v1 = getattr(mercurial.revlog, 'parse_index_v1', None)
3228 if parse_index_v1 is None:
3227 if parse_index_v1 is None:
3229 parse_index_v1 = mercurial.revlog.revlogio().parseindex
3228 parse_index_v1 = mercurial.revlog.revlogio().parseindex
3230
3229
3231 rllen = len(rl)
3230 rllen = len(rl)
3232
3231
3233 node0 = rl.node(0)
3232 node0 = rl.node(0)
3234 node25 = rl.node(rllen // 4)
3233 node25 = rl.node(rllen // 4)
3235 node50 = rl.node(rllen // 2)
3234 node50 = rl.node(rllen // 2)
3236 node75 = rl.node(rllen // 4 * 3)
3235 node75 = rl.node(rllen // 4 * 3)
3237 node100 = rl.node(rllen - 1)
3236 node100 = rl.node(rllen - 1)
3238
3237
3239 allrevs = range(rllen)
3238 allrevs = range(rllen)
3240 allrevsrev = list(reversed(allrevs))
3239 allrevsrev = list(reversed(allrevs))
3241 allnodes = [rl.node(rev) for rev in range(rllen)]
3240 allnodes = [rl.node(rev) for rev in range(rllen)]
3242 allnodesrev = list(reversed(allnodes))
3241 allnodesrev = list(reversed(allnodes))
3243
3242
3244 def constructor():
3243 def constructor():
3245 if radix is not None:
3244 if radix is not None:
3246 revlog(opener, radix=radix)
3245 revlog(opener, radix=radix)
3247 else:
3246 else:
3248 # hg <= 5.8
3247 # hg <= 5.8
3249 revlog(opener, indexfile=indexfile)
3248 revlog(opener, indexfile=indexfile)
3250
3249
3251 def read():
3250 def read():
3252 with opener(indexfile) as fh:
3251 with opener(indexfile) as fh:
3253 fh.read()
3252 fh.read()
3254
3253
3255 def parseindex():
3254 def parseindex():
3256 parse_index_v1(data, inline)
3255 parse_index_v1(data, inline)
3257
3256
3258 def getentry(revornode):
3257 def getentry(revornode):
3259 index = parse_index_v1(data, inline)[0]
3258 index = parse_index_v1(data, inline)[0]
3260 index[revornode]
3259 index[revornode]
3261
3260
3262 def getentries(revs, count=1):
3261 def getentries(revs, count=1):
3263 index = parse_index_v1(data, inline)[0]
3262 index = parse_index_v1(data, inline)[0]
3264
3263
3265 for i in range(count):
3264 for i in range(count):
3266 for rev in revs:
3265 for rev in revs:
3267 index[rev]
3266 index[rev]
3268
3267
3269 def resolvenode(node):
3268 def resolvenode(node):
3270 index = parse_index_v1(data, inline)[0]
3269 index = parse_index_v1(data, inline)[0]
3271 rev = getattr(index, 'rev', None)
3270 rev = getattr(index, 'rev', None)
3272 if rev is None:
3271 if rev is None:
3273 nodemap = getattr(parse_index_v1(data, inline)[0], 'nodemap', None)
3272 nodemap = getattr(parse_index_v1(data, inline)[0], 'nodemap', None)
3274 # This only works for the C code.
3273 # This only works for the C code.
3275 if nodemap is None:
3274 if nodemap is None:
3276 return
3275 return
3277 rev = nodemap.__getitem__
3276 rev = nodemap.__getitem__
3278
3277
3279 try:
3278 try:
3280 rev(node)
3279 rev(node)
3281 except error.RevlogError:
3280 except error.RevlogError:
3282 pass
3281 pass
3283
3282
3284 def resolvenodes(nodes, count=1):
3283 def resolvenodes(nodes, count=1):
3285 index = parse_index_v1(data, inline)[0]
3284 index = parse_index_v1(data, inline)[0]
3286 rev = getattr(index, 'rev', None)
3285 rev = getattr(index, 'rev', None)
3287 if rev is None:
3286 if rev is None:
3288 nodemap = getattr(parse_index_v1(data, inline)[0], 'nodemap', None)
3287 nodemap = getattr(parse_index_v1(data, inline)[0], 'nodemap', None)
3289 # This only works for the C code.
3288 # This only works for the C code.
3290 if nodemap is None:
3289 if nodemap is None:
3291 return
3290 return
3292 rev = nodemap.__getitem__
3291 rev = nodemap.__getitem__
3293
3292
3294 for i in range(count):
3293 for i in range(count):
3295 for node in nodes:
3294 for node in nodes:
3296 try:
3295 try:
3297 rev(node)
3296 rev(node)
3298 except error.RevlogError:
3297 except error.RevlogError:
3299 pass
3298 pass
3300
3299
3301 benches = [
3300 benches = [
3302 (constructor, b'revlog constructor'),
3301 (constructor, b'revlog constructor'),
3303 (read, b'read'),
3302 (read, b'read'),
3304 (parseindex, b'create index object'),
3303 (parseindex, b'create index object'),
3305 (lambda: getentry(0), b'retrieve index entry for rev 0'),
3304 (lambda: getentry(0), b'retrieve index entry for rev 0'),
3306 (lambda: resolvenode(b'a' * 20), b'look up missing node'),
3305 (lambda: resolvenode(b'a' * 20), b'look up missing node'),
3307 (lambda: resolvenode(node0), b'look up node at rev 0'),
3306 (lambda: resolvenode(node0), b'look up node at rev 0'),
3308 (lambda: resolvenode(node25), b'look up node at 1/4 len'),
3307 (lambda: resolvenode(node25), b'look up node at 1/4 len'),
3309 (lambda: resolvenode(node50), b'look up node at 1/2 len'),
3308 (lambda: resolvenode(node50), b'look up node at 1/2 len'),
3310 (lambda: resolvenode(node75), b'look up node at 3/4 len'),
3309 (lambda: resolvenode(node75), b'look up node at 3/4 len'),
3311 (lambda: resolvenode(node100), b'look up node at tip'),
3310 (lambda: resolvenode(node100), b'look up node at tip'),
3312 # 2x variation is to measure caching impact.
3311 # 2x variation is to measure caching impact.
3313 (lambda: resolvenodes(allnodes), b'look up all nodes (forward)'),
3312 (lambda: resolvenodes(allnodes), b'look up all nodes (forward)'),
3314 (lambda: resolvenodes(allnodes, 2), b'look up all nodes 2x (forward)'),
3313 (lambda: resolvenodes(allnodes, 2), b'look up all nodes 2x (forward)'),
3315 (lambda: resolvenodes(allnodesrev), b'look up all nodes (reverse)'),
3314 (lambda: resolvenodes(allnodesrev), b'look up all nodes (reverse)'),
3316 (
3315 (
3317 lambda: resolvenodes(allnodesrev, 2),
3316 lambda: resolvenodes(allnodesrev, 2),
3318 b'look up all nodes 2x (reverse)',
3317 b'look up all nodes 2x (reverse)',
3319 ),
3318 ),
3320 (lambda: getentries(allrevs), b'retrieve all index entries (forward)'),
3319 (lambda: getentries(allrevs), b'retrieve all index entries (forward)'),
3321 (
3320 (
3322 lambda: getentries(allrevs, 2),
3321 lambda: getentries(allrevs, 2),
3323 b'retrieve all index entries 2x (forward)',
3322 b'retrieve all index entries 2x (forward)',
3324 ),
3323 ),
3325 (
3324 (
3326 lambda: getentries(allrevsrev),
3325 lambda: getentries(allrevsrev),
3327 b'retrieve all index entries (reverse)',
3326 b'retrieve all index entries (reverse)',
3328 ),
3327 ),
3329 (
3328 (
3330 lambda: getentries(allrevsrev, 2),
3329 lambda: getentries(allrevsrev, 2),
3331 b'retrieve all index entries 2x (reverse)',
3330 b'retrieve all index entries 2x (reverse)',
3332 ),
3331 ),
3333 ]
3332 ]
3334
3333
3335 for fn, title in benches:
3334 for fn, title in benches:
3336 timer, fm = gettimer(ui, opts)
3335 timer, fm = gettimer(ui, opts)
3337 timer(fn, title=title)
3336 timer(fn, title=title)
3338 fm.end()
3337 fm.end()
3339
3338
3340
3339
3341 @command(
3340 @command(
3342 b'perf::revlogrevisions|perfrevlogrevisions',
3341 b'perf::revlogrevisions|perfrevlogrevisions',
3343 revlogopts
3342 revlogopts
3344 + formatteropts
3343 + formatteropts
3345 + [
3344 + [
3346 (b'd', b'dist', 100, b'distance between the revisions'),
3345 (b'd', b'dist', 100, b'distance between the revisions'),
3347 (b's', b'startrev', 0, b'revision to start reading at'),
3346 (b's', b'startrev', 0, b'revision to start reading at'),
3348 (b'', b'reverse', False, b'read in reverse'),
3347 (b'', b'reverse', False, b'read in reverse'),
3349 ],
3348 ],
3350 b'-c|-m|FILE',
3349 b'-c|-m|FILE',
3351 )
3350 )
3352 def perfrevlogrevisions(
3351 def perfrevlogrevisions(
3353 ui, repo, file_=None, startrev=0, reverse=False, **opts
3352 ui, repo, file_=None, startrev=0, reverse=False, **opts
3354 ):
3353 ):
3355 """Benchmark reading a series of revisions from a revlog.
3354 """Benchmark reading a series of revisions from a revlog.
3356
3355
3357 By default, we read every ``-d/--dist`` revision from 0 to tip of
3356 By default, we read every ``-d/--dist`` revision from 0 to tip of
3358 the specified revlog.
3357 the specified revlog.
3359
3358
3360 The start revision can be defined via ``-s/--startrev``.
3359 The start revision can be defined via ``-s/--startrev``.
3361 """
3360 """
3362 opts = _byteskwargs(opts)
3361 opts = _byteskwargs(opts)
3363
3362
3364 rl = cmdutil.openrevlog(repo, b'perfrevlogrevisions', file_, opts)
3363 rl = cmdutil.openrevlog(repo, b'perfrevlogrevisions', file_, opts)
3365 rllen = getlen(ui)(rl)
3364 rllen = getlen(ui)(rl)
3366
3365
3367 if startrev < 0:
3366 if startrev < 0:
3368 startrev = rllen + startrev
3367 startrev = rllen + startrev
3369
3368
3370 def d():
3369 def d():
3371 rl.clearcaches()
3370 rl.clearcaches()
3372
3371
3373 beginrev = startrev
3372 beginrev = startrev
3374 endrev = rllen
3373 endrev = rllen
3375 dist = opts[b'dist']
3374 dist = opts[b'dist']
3376
3375
3377 if reverse:
3376 if reverse:
3378 beginrev, endrev = endrev - 1, beginrev - 1
3377 beginrev, endrev = endrev - 1, beginrev - 1
3379 dist = -1 * dist
3378 dist = -1 * dist
3380
3379
3381 for x in _xrange(beginrev, endrev, dist):
3380 for x in _xrange(beginrev, endrev, dist):
3382 # Old revisions don't support passing int.
3381 # Old revisions don't support passing int.
3383 n = rl.node(x)
3382 n = rl.node(x)
3384 rl.revision(n)
3383 rl.revision(n)
3385
3384
3386 timer, fm = gettimer(ui, opts)
3385 timer, fm = gettimer(ui, opts)
3387 timer(d)
3386 timer(d)
3388 fm.end()
3387 fm.end()
3389
3388
3390
3389
3391 @command(
3390 @command(
3392 b'perf::revlogwrite|perfrevlogwrite',
3391 b'perf::revlogwrite|perfrevlogwrite',
3393 revlogopts
3392 revlogopts
3394 + formatteropts
3393 + formatteropts
3395 + [
3394 + [
3396 (b's', b'startrev', 1000, b'revision to start writing at'),
3395 (b's', b'startrev', 1000, b'revision to start writing at'),
3397 (b'', b'stoprev', -1, b'last revision to write'),
3396 (b'', b'stoprev', -1, b'last revision to write'),
3398 (b'', b'count', 3, b'number of passes to perform'),
3397 (b'', b'count', 3, b'number of passes to perform'),
3399 (b'', b'details', False, b'print timing for every revisions tested'),
3398 (b'', b'details', False, b'print timing for every revisions tested'),
3400 (b'', b'source', b'full', b'the kind of data feed in the revlog'),
3399 (b'', b'source', b'full', b'the kind of data feed in the revlog'),
3401 (b'', b'lazydeltabase', True, b'try the provided delta first'),
3400 (b'', b'lazydeltabase', True, b'try the provided delta first'),
3402 (b'', b'clear-caches', True, b'clear revlog cache between calls'),
3401 (b'', b'clear-caches', True, b'clear revlog cache between calls'),
3403 ],
3402 ],
3404 b'-c|-m|FILE',
3403 b'-c|-m|FILE',
3405 )
3404 )
3406 def perfrevlogwrite(ui, repo, file_=None, startrev=1000, stoprev=-1, **opts):
3405 def perfrevlogwrite(ui, repo, file_=None, startrev=1000, stoprev=-1, **opts):
3407 """Benchmark writing a series of revisions to a revlog.
3406 """Benchmark writing a series of revisions to a revlog.
3408
3407
3409 Possible source values are:
3408 Possible source values are:
3410 * `full`: add from a full text (default).
3409 * `full`: add from a full text (default).
3411 * `parent-1`: add from a delta to the first parent
3410 * `parent-1`: add from a delta to the first parent
3412 * `parent-2`: add from a delta to the second parent if it exists
3411 * `parent-2`: add from a delta to the second parent if it exists
3413 (use a delta from the first parent otherwise)
3412 (use a delta from the first parent otherwise)
3414 * `parent-smallest`: add from the smallest delta (either p1 or p2)
3413 * `parent-smallest`: add from the smallest delta (either p1 or p2)
3415 * `storage`: add from the existing precomputed deltas
3414 * `storage`: add from the existing precomputed deltas
3416
3415
3417 Note: This performance command measures performance in a custom way. As a
3416 Note: This performance command measures performance in a custom way. As a
3418 result some of the global configuration of the 'perf' command does not
3417 result some of the global configuration of the 'perf' command does not
3419 apply to it:
3418 apply to it:
3420
3419
3421 * ``pre-run``: disabled
3420 * ``pre-run``: disabled
3422
3421
3423 * ``profile-benchmark``: disabled
3422 * ``profile-benchmark``: disabled
3424
3423
3425 * ``run-limits``: disabled use --count instead
3424 * ``run-limits``: disabled use --count instead
3426 """
3425 """
3427 opts = _byteskwargs(opts)
3426 opts = _byteskwargs(opts)
3428
3427
3429 rl = cmdutil.openrevlog(repo, b'perfrevlogwrite', file_, opts)
3428 rl = cmdutil.openrevlog(repo, b'perfrevlogwrite', file_, opts)
3430 rllen = getlen(ui)(rl)
3429 rllen = getlen(ui)(rl)
3431 if startrev < 0:
3430 if startrev < 0:
3432 startrev = rllen + startrev
3431 startrev = rllen + startrev
3433 if stoprev < 0:
3432 if stoprev < 0:
3434 stoprev = rllen + stoprev
3433 stoprev = rllen + stoprev
3435
3434
3436 lazydeltabase = opts['lazydeltabase']
3435 lazydeltabase = opts['lazydeltabase']
3437 source = opts['source']
3436 source = opts['source']
3438 clearcaches = opts['clear_caches']
3437 clearcaches = opts['clear_caches']
3439 validsource = (
3438 validsource = (
3440 b'full',
3439 b'full',
3441 b'parent-1',
3440 b'parent-1',
3442 b'parent-2',
3441 b'parent-2',
3443 b'parent-smallest',
3442 b'parent-smallest',
3444 b'storage',
3443 b'storage',
3445 )
3444 )
3446 if source not in validsource:
3445 if source not in validsource:
3447 raise error.Abort('invalid source type: %s' % source)
3446 raise error.Abort('invalid source type: %s' % source)
3448
3447
3449 ### actually gather results
3448 ### actually gather results
3450 count = opts['count']
3449 count = opts['count']
3451 if count <= 0:
3450 if count <= 0:
3452 raise error.Abort('invalide run count: %d' % count)
3451 raise error.Abort('invalide run count: %d' % count)
3453 allresults = []
3452 allresults = []
3454 for c in range(count):
3453 for c in range(count):
3455 timing = _timeonewrite(
3454 timing = _timeonewrite(
3456 ui,
3455 ui,
3457 rl,
3456 rl,
3458 source,
3457 source,
3459 startrev,
3458 startrev,
3460 stoprev,
3459 stoprev,
3461 c + 1,
3460 c + 1,
3462 lazydeltabase=lazydeltabase,
3461 lazydeltabase=lazydeltabase,
3463 clearcaches=clearcaches,
3462 clearcaches=clearcaches,
3464 )
3463 )
3465 allresults.append(timing)
3464 allresults.append(timing)
3466
3465
3467 ### consolidate the results in a single list
3466 ### consolidate the results in a single list
3468 results = []
3467 results = []
3469 for idx, (rev, t) in enumerate(allresults[0]):
3468 for idx, (rev, t) in enumerate(allresults[0]):
3470 ts = [t]
3469 ts = [t]
3471 for other in allresults[1:]:
3470 for other in allresults[1:]:
3472 orev, ot = other[idx]
3471 orev, ot = other[idx]
3473 assert orev == rev
3472 assert orev == rev
3474 ts.append(ot)
3473 ts.append(ot)
3475 results.append((rev, ts))
3474 results.append((rev, ts))
3476 resultcount = len(results)
3475 resultcount = len(results)
3477
3476
3478 ### Compute and display relevant statistics
3477 ### Compute and display relevant statistics
3479
3478
3480 # get a formatter
3479 # get a formatter
3481 fm = ui.formatter(b'perf', opts)
3480 fm = ui.formatter(b'perf', opts)
3482 displayall = ui.configbool(b"perf", b"all-timing", True)
3481 displayall = ui.configbool(b"perf", b"all-timing", True)
3483
3482
3484 # print individual details if requested
3483 # print individual details if requested
3485 if opts['details']:
3484 if opts['details']:
3486 for idx, item in enumerate(results, 1):
3485 for idx, item in enumerate(results, 1):
3487 rev, data = item
3486 rev, data = item
3488 title = 'revisions #%d of %d, rev %d' % (idx, resultcount, rev)
3487 title = 'revisions #%d of %d, rev %d' % (idx, resultcount, rev)
3489 formatone(fm, data, title=title, displayall=displayall)
3488 formatone(fm, data, title=title, displayall=displayall)
3490
3489
3491 # sorts results by median time
3490 # sorts results by median time
3492 results.sort(key=lambda x: sorted(x[1])[len(x[1]) // 2])
3491 results.sort(key=lambda x: sorted(x[1])[len(x[1]) // 2])
3493 # list of (name, index) to display)
3492 # list of (name, index) to display)
3494 relevants = [
3493 relevants = [
3495 ("min", 0),
3494 ("min", 0),
3496 ("10%", resultcount * 10 // 100),
3495 ("10%", resultcount * 10 // 100),
3497 ("25%", resultcount * 25 // 100),
3496 ("25%", resultcount * 25 // 100),
3498 ("50%", resultcount * 70 // 100),
3497 ("50%", resultcount * 70 // 100),
3499 ("75%", resultcount * 75 // 100),
3498 ("75%", resultcount * 75 // 100),
3500 ("90%", resultcount * 90 // 100),
3499 ("90%", resultcount * 90 // 100),
3501 ("95%", resultcount * 95 // 100),
3500 ("95%", resultcount * 95 // 100),
3502 ("99%", resultcount * 99 // 100),
3501 ("99%", resultcount * 99 // 100),
3503 ("99.9%", resultcount * 999 // 1000),
3502 ("99.9%", resultcount * 999 // 1000),
3504 ("99.99%", resultcount * 9999 // 10000),
3503 ("99.99%", resultcount * 9999 // 10000),
3505 ("99.999%", resultcount * 99999 // 100000),
3504 ("99.999%", resultcount * 99999 // 100000),
3506 ("max", -1),
3505 ("max", -1),
3507 ]
3506 ]
3508 if not ui.quiet:
3507 if not ui.quiet:
3509 for name, idx in relevants:
3508 for name, idx in relevants:
3510 data = results[idx]
3509 data = results[idx]
3511 title = '%s of %d, rev %d' % (name, resultcount, data[0])
3510 title = '%s of %d, rev %d' % (name, resultcount, data[0])
3512 formatone(fm, data[1], title=title, displayall=displayall)
3511 formatone(fm, data[1], title=title, displayall=displayall)
3513
3512
3514 # XXX summing that many float will not be very precise, we ignore this fact
3513 # XXX summing that many float will not be very precise, we ignore this fact
3515 # for now
3514 # for now
3516 totaltime = []
3515 totaltime = []
3517 for item in allresults:
3516 for item in allresults:
3518 totaltime.append(
3517 totaltime.append(
3519 (
3518 (
3520 sum(x[1][0] for x in item),
3519 sum(x[1][0] for x in item),
3521 sum(x[1][1] for x in item),
3520 sum(x[1][1] for x in item),
3522 sum(x[1][2] for x in item),
3521 sum(x[1][2] for x in item),
3523 )
3522 )
3524 )
3523 )
3525 formatone(
3524 formatone(
3526 fm,
3525 fm,
3527 totaltime,
3526 totaltime,
3528 title="total time (%d revs)" % resultcount,
3527 title="total time (%d revs)" % resultcount,
3529 displayall=displayall,
3528 displayall=displayall,
3530 )
3529 )
3531 fm.end()
3530 fm.end()
3532
3531
3533
3532
3534 class _faketr:
3533 class _faketr:
3535 def add(s, x, y, z=None):
3534 def add(s, x, y, z=None):
3536 return None
3535 return None
3537
3536
3538
3537
3539 def _timeonewrite(
3538 def _timeonewrite(
3540 ui,
3539 ui,
3541 orig,
3540 orig,
3542 source,
3541 source,
3543 startrev,
3542 startrev,
3544 stoprev,
3543 stoprev,
3545 runidx=None,
3544 runidx=None,
3546 lazydeltabase=True,
3545 lazydeltabase=True,
3547 clearcaches=True,
3546 clearcaches=True,
3548 ):
3547 ):
3549 timings = []
3548 timings = []
3550 tr = _faketr()
3549 tr = _faketr()
3551 with _temprevlog(ui, orig, startrev) as dest:
3550 with _temprevlog(ui, orig, startrev) as dest:
3552 if hasattr(dest, "delta_config"):
3551 if hasattr(dest, "delta_config"):
3553 dest.delta_config.lazy_delta_base = lazydeltabase
3552 dest.delta_config.lazy_delta_base = lazydeltabase
3554 else:
3553 else:
3555 dest._lazydeltabase = lazydeltabase
3554 dest._lazydeltabase = lazydeltabase
3556 revs = list(orig.revs(startrev, stoprev))
3555 revs = list(orig.revs(startrev, stoprev))
3557 total = len(revs)
3556 total = len(revs)
3558 topic = 'adding'
3557 topic = 'adding'
3559 if runidx is not None:
3558 if runidx is not None:
3560 topic += ' (run #%d)' % runidx
3559 topic += ' (run #%d)' % runidx
3561 # Support both old and new progress API
3560 # Support both old and new progress API
3562 if util.safehasattr(ui, 'makeprogress'):
3561 if util.safehasattr(ui, 'makeprogress'):
3563 progress = ui.makeprogress(topic, unit='revs', total=total)
3562 progress = ui.makeprogress(topic, unit='revs', total=total)
3564
3563
3565 def updateprogress(pos):
3564 def updateprogress(pos):
3566 progress.update(pos)
3565 progress.update(pos)
3567
3566
3568 def completeprogress():
3567 def completeprogress():
3569 progress.complete()
3568 progress.complete()
3570
3569
3571 else:
3570 else:
3572
3571
3573 def updateprogress(pos):
3572 def updateprogress(pos):
3574 ui.progress(topic, pos, unit='revs', total=total)
3573 ui.progress(topic, pos, unit='revs', total=total)
3575
3574
3576 def completeprogress():
3575 def completeprogress():
3577 ui.progress(topic, None, unit='revs', total=total)
3576 ui.progress(topic, None, unit='revs', total=total)
3578
3577
3579 for idx, rev in enumerate(revs):
3578 for idx, rev in enumerate(revs):
3580 updateprogress(idx)
3579 updateprogress(idx)
3581 addargs, addkwargs = _getrevisionseed(orig, rev, tr, source)
3580 addargs, addkwargs = _getrevisionseed(orig, rev, tr, source)
3582 if clearcaches:
3581 if clearcaches:
3583 dest.index.clearcaches()
3582 dest.index.clearcaches()
3584 dest.clearcaches()
3583 dest.clearcaches()
3585 with timeone() as r:
3584 with timeone() as r:
3586 dest.addrawrevision(*addargs, **addkwargs)
3585 dest.addrawrevision(*addargs, **addkwargs)
3587 timings.append((rev, r[0]))
3586 timings.append((rev, r[0]))
3588 updateprogress(total)
3587 updateprogress(total)
3589 completeprogress()
3588 completeprogress()
3590 return timings
3589 return timings
3591
3590
3592
3591
3593 def _getrevisionseed(orig, rev, tr, source):
3592 def _getrevisionseed(orig, rev, tr, source):
3594 from mercurial.node import nullid
3593 from mercurial.node import nullid
3595
3594
3596 linkrev = orig.linkrev(rev)
3595 linkrev = orig.linkrev(rev)
3597 node = orig.node(rev)
3596 node = orig.node(rev)
3598 p1, p2 = orig.parents(node)
3597 p1, p2 = orig.parents(node)
3599 flags = orig.flags(rev)
3598 flags = orig.flags(rev)
3600 cachedelta = None
3599 cachedelta = None
3601 text = None
3600 text = None
3602
3601
3603 if source == b'full':
3602 if source == b'full':
3604 text = orig.revision(rev)
3603 text = orig.revision(rev)
3605 elif source == b'parent-1':
3604 elif source == b'parent-1':
3606 baserev = orig.rev(p1)
3605 baserev = orig.rev(p1)
3607 cachedelta = (baserev, orig.revdiff(p1, rev))
3606 cachedelta = (baserev, orig.revdiff(p1, rev))
3608 elif source == b'parent-2':
3607 elif source == b'parent-2':
3609 parent = p2
3608 parent = p2
3610 if p2 == nullid:
3609 if p2 == nullid:
3611 parent = p1
3610 parent = p1
3612 baserev = orig.rev(parent)
3611 baserev = orig.rev(parent)
3613 cachedelta = (baserev, orig.revdiff(parent, rev))
3612 cachedelta = (baserev, orig.revdiff(parent, rev))
3614 elif source == b'parent-smallest':
3613 elif source == b'parent-smallest':
3615 p1diff = orig.revdiff(p1, rev)
3614 p1diff = orig.revdiff(p1, rev)
3616 parent = p1
3615 parent = p1
3617 diff = p1diff
3616 diff = p1diff
3618 if p2 != nullid:
3617 if p2 != nullid:
3619 p2diff = orig.revdiff(p2, rev)
3618 p2diff = orig.revdiff(p2, rev)
3620 if len(p1diff) > len(p2diff):
3619 if len(p1diff) > len(p2diff):
3621 parent = p2
3620 parent = p2
3622 diff = p2diff
3621 diff = p2diff
3623 baserev = orig.rev(parent)
3622 baserev = orig.rev(parent)
3624 cachedelta = (baserev, diff)
3623 cachedelta = (baserev, diff)
3625 elif source == b'storage':
3624 elif source == b'storage':
3626 baserev = orig.deltaparent(rev)
3625 baserev = orig.deltaparent(rev)
3627 cachedelta = (baserev, orig.revdiff(orig.node(baserev), rev))
3626 cachedelta = (baserev, orig.revdiff(orig.node(baserev), rev))
3628
3627
3629 return (
3628 return (
3630 (text, tr, linkrev, p1, p2),
3629 (text, tr, linkrev, p1, p2),
3631 {'node': node, 'flags': flags, 'cachedelta': cachedelta},
3630 {'node': node, 'flags': flags, 'cachedelta': cachedelta},
3632 )
3631 )
3633
3632
3634
3633
3635 @contextlib.contextmanager
3634 @contextlib.contextmanager
3636 def _temprevlog(ui, orig, truncaterev):
3635 def _temprevlog(ui, orig, truncaterev):
3637 from mercurial import vfs as vfsmod
3636 from mercurial import vfs as vfsmod
3638
3637
3639 if orig._inline:
3638 if orig._inline:
3640 raise error.Abort('not supporting inline revlog (yet)')
3639 raise error.Abort('not supporting inline revlog (yet)')
3641 revlogkwargs = {}
3640 revlogkwargs = {}
3642 k = 'upperboundcomp'
3641 k = 'upperboundcomp'
3643 if util.safehasattr(orig, k):
3642 if util.safehasattr(orig, k):
3644 revlogkwargs[k] = getattr(orig, k)
3643 revlogkwargs[k] = getattr(orig, k)
3645
3644
3646 indexfile = getattr(orig, '_indexfile', None)
3645 indexfile = getattr(orig, '_indexfile', None)
3647 if indexfile is None:
3646 if indexfile is None:
3648 # compatibility with <= hg-5.8
3647 # compatibility with <= hg-5.8
3649 indexfile = getattr(orig, 'indexfile')
3648 indexfile = getattr(orig, 'indexfile')
3650 origindexpath = orig.opener.join(indexfile)
3649 origindexpath = orig.opener.join(indexfile)
3651
3650
3652 datafile = getattr(orig, '_datafile', getattr(orig, 'datafile'))
3651 datafile = getattr(orig, '_datafile', getattr(orig, 'datafile'))
3653 origdatapath = orig.opener.join(datafile)
3652 origdatapath = orig.opener.join(datafile)
3654 radix = b'revlog'
3653 radix = b'revlog'
3655 indexname = b'revlog.i'
3654 indexname = b'revlog.i'
3656 dataname = b'revlog.d'
3655 dataname = b'revlog.d'
3657
3656
3658 tmpdir = tempfile.mkdtemp(prefix='tmp-hgperf-')
3657 tmpdir = tempfile.mkdtemp(prefix='tmp-hgperf-')
3659 try:
3658 try:
3660 # copy the data file in a temporary directory
3659 # copy the data file in a temporary directory
3661 ui.debug('copying data in %s\n' % tmpdir)
3660 ui.debug('copying data in %s\n' % tmpdir)
3662 destindexpath = os.path.join(tmpdir, 'revlog.i')
3661 destindexpath = os.path.join(tmpdir, 'revlog.i')
3663 destdatapath = os.path.join(tmpdir, 'revlog.d')
3662 destdatapath = os.path.join(tmpdir, 'revlog.d')
3664 shutil.copyfile(origindexpath, destindexpath)
3663 shutil.copyfile(origindexpath, destindexpath)
3665 shutil.copyfile(origdatapath, destdatapath)
3664 shutil.copyfile(origdatapath, destdatapath)
3666
3665
3667 # remove the data we want to add again
3666 # remove the data we want to add again
3668 ui.debug('truncating data to be rewritten\n')
3667 ui.debug('truncating data to be rewritten\n')
3669 with open(destindexpath, 'ab') as index:
3668 with open(destindexpath, 'ab') as index:
3670 index.seek(0)
3669 index.seek(0)
3671 index.truncate(truncaterev * orig._io.size)
3670 index.truncate(truncaterev * orig._io.size)
3672 with open(destdatapath, 'ab') as data:
3671 with open(destdatapath, 'ab') as data:
3673 data.seek(0)
3672 data.seek(0)
3674 data.truncate(orig.start(truncaterev))
3673 data.truncate(orig.start(truncaterev))
3675
3674
3676 # instantiate a new revlog from the temporary copy
3675 # instantiate a new revlog from the temporary copy
3677 ui.debug('truncating adding to be rewritten\n')
3676 ui.debug('truncating adding to be rewritten\n')
3678 vfs = vfsmod.vfs(tmpdir)
3677 vfs = vfsmod.vfs(tmpdir)
3679 vfs.options = getattr(orig.opener, 'options', None)
3678 vfs.options = getattr(orig.opener, 'options', None)
3680
3679
3681 try:
3680 try:
3682 dest = revlog(vfs, radix=radix, **revlogkwargs)
3681 dest = revlog(vfs, radix=radix, **revlogkwargs)
3683 except TypeError:
3682 except TypeError:
3684 dest = revlog(
3683 dest = revlog(
3685 vfs, indexfile=indexname, datafile=dataname, **revlogkwargs
3684 vfs, indexfile=indexname, datafile=dataname, **revlogkwargs
3686 )
3685 )
3687 if dest._inline:
3686 if dest._inline:
3688 raise error.Abort('not supporting inline revlog (yet)')
3687 raise error.Abort('not supporting inline revlog (yet)')
3689 # make sure internals are initialized
3688 # make sure internals are initialized
3690 dest.revision(len(dest) - 1)
3689 dest.revision(len(dest) - 1)
3691 yield dest
3690 yield dest
3692 del dest, vfs
3691 del dest, vfs
3693 finally:
3692 finally:
3694 shutil.rmtree(tmpdir, True)
3693 shutil.rmtree(tmpdir, True)
3695
3694
3696
3695
3697 @command(
3696 @command(
3698 b'perf::revlogchunks|perfrevlogchunks',
3697 b'perf::revlogchunks|perfrevlogchunks',
3699 revlogopts
3698 revlogopts
3700 + formatteropts
3699 + formatteropts
3701 + [
3700 + [
3702 (b'e', b'engines', b'', b'compression engines to use'),
3701 (b'e', b'engines', b'', b'compression engines to use'),
3703 (b's', b'startrev', 0, b'revision to start at'),
3702 (b's', b'startrev', 0, b'revision to start at'),
3704 ],
3703 ],
3705 b'-c|-m|FILE',
3704 b'-c|-m|FILE',
3706 )
3705 )
3707 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
3706 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
3708 """Benchmark operations on revlog chunks.
3707 """Benchmark operations on revlog chunks.
3709
3708
3710 Logically, each revlog is a collection of fulltext revisions. However,
3709 Logically, each revlog is a collection of fulltext revisions. However,
3711 stored within each revlog are "chunks" of possibly compressed data. This
3710 stored within each revlog are "chunks" of possibly compressed data. This
3712 data needs to be read and decompressed or compressed and written.
3711 data needs to be read and decompressed or compressed and written.
3713
3712
3714 This command measures the time it takes to read+decompress and recompress
3713 This command measures the time it takes to read+decompress and recompress
3715 chunks in a revlog. It effectively isolates I/O and compression performance.
3714 chunks in a revlog. It effectively isolates I/O and compression performance.
3716 For measurements of higher-level operations like resolving revisions,
3715 For measurements of higher-level operations like resolving revisions,
3717 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
3716 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
3718 """
3717 """
3719 opts = _byteskwargs(opts)
3718 opts = _byteskwargs(opts)
3720
3719
3721 rl = cmdutil.openrevlog(repo, b'perfrevlogchunks', file_, opts)
3720 rl = cmdutil.openrevlog(repo, b'perfrevlogchunks', file_, opts)
3722
3721
3723 # - _chunkraw was renamed to _getsegmentforrevs
3722 # - _chunkraw was renamed to _getsegmentforrevs
3724 # - _getsegmentforrevs was moved on the inner object
3723 # - _getsegmentforrevs was moved on the inner object
3725 try:
3724 try:
3726 segmentforrevs = rl._inner.get_segment_for_revs
3725 segmentforrevs = rl._inner.get_segment_for_revs
3727 except AttributeError:
3726 except AttributeError:
3728 try:
3727 try:
3729 segmentforrevs = rl._getsegmentforrevs
3728 segmentforrevs = rl._getsegmentforrevs
3730 except AttributeError:
3729 except AttributeError:
3731 segmentforrevs = rl._chunkraw
3730 segmentforrevs = rl._chunkraw
3732
3731
3733 # Verify engines argument.
3732 # Verify engines argument.
3734 if engines:
3733 if engines:
3735 engines = {e.strip() for e in engines.split(b',')}
3734 engines = {e.strip() for e in engines.split(b',')}
3736 for engine in engines:
3735 for engine in engines:
3737 try:
3736 try:
3738 util.compressionengines[engine]
3737 util.compressionengines[engine]
3739 except KeyError:
3738 except KeyError:
3740 raise error.Abort(b'unknown compression engine: %s' % engine)
3739 raise error.Abort(b'unknown compression engine: %s' % engine)
3741 else:
3740 else:
3742 engines = []
3741 engines = []
3743 for e in util.compengines:
3742 for e in util.compengines:
3744 engine = util.compengines[e]
3743 engine = util.compengines[e]
3745 try:
3744 try:
3746 if engine.available():
3745 if engine.available():
3747 engine.revlogcompressor().compress(b'dummy')
3746 engine.revlogcompressor().compress(b'dummy')
3748 engines.append(e)
3747 engines.append(e)
3749 except NotImplementedError:
3748 except NotImplementedError:
3750 pass
3749 pass
3751
3750
3752 revs = list(rl.revs(startrev, len(rl) - 1))
3751 revs = list(rl.revs(startrev, len(rl) - 1))
3753
3752
3754 @contextlib.contextmanager
3753 @contextlib.contextmanager
3755 def reading(rl):
3754 def reading(rl):
3756 if getattr(rl, 'reading', None) is not None:
3755 if getattr(rl, 'reading', None) is not None:
3757 with rl.reading():
3756 with rl.reading():
3758 yield None
3757 yield None
3759 elif rl._inline:
3758 elif rl._inline:
3760 indexfile = getattr(rl, '_indexfile', None)
3759 indexfile = getattr(rl, '_indexfile', None)
3761 if indexfile is None:
3760 if indexfile is None:
3762 # compatibility with <= hg-5.8
3761 # compatibility with <= hg-5.8
3763 indexfile = getattr(rl, 'indexfile')
3762 indexfile = getattr(rl, 'indexfile')
3764 yield getsvfs(repo)(indexfile)
3763 yield getsvfs(repo)(indexfile)
3765 else:
3764 else:
3766 datafile = getattr(rl, 'datafile', getattr(rl, 'datafile'))
3765 datafile = getattr(rl, 'datafile', getattr(rl, 'datafile'))
3767 yield getsvfs(repo)(datafile)
3766 yield getsvfs(repo)(datafile)
3768
3767
3769 if getattr(rl, 'reading', None) is not None:
3768 if getattr(rl, 'reading', None) is not None:
3770
3769
3771 @contextlib.contextmanager
3770 @contextlib.contextmanager
3772 def lazy_reading(rl):
3771 def lazy_reading(rl):
3773 with rl.reading():
3772 with rl.reading():
3774 yield
3773 yield
3775
3774
3776 else:
3775 else:
3777
3776
3778 @contextlib.contextmanager
3777 @contextlib.contextmanager
3779 def lazy_reading(rl):
3778 def lazy_reading(rl):
3780 yield
3779 yield
3781
3780
3782 def doread():
3781 def doread():
3783 rl.clearcaches()
3782 rl.clearcaches()
3784 for rev in revs:
3783 for rev in revs:
3785 with lazy_reading(rl):
3784 with lazy_reading(rl):
3786 segmentforrevs(rev, rev)
3785 segmentforrevs(rev, rev)
3787
3786
3788 def doreadcachedfh():
3787 def doreadcachedfh():
3789 rl.clearcaches()
3788 rl.clearcaches()
3790 with reading(rl) as fh:
3789 with reading(rl) as fh:
3791 if fh is not None:
3790 if fh is not None:
3792 for rev in revs:
3791 for rev in revs:
3793 segmentforrevs(rev, rev, df=fh)
3792 segmentforrevs(rev, rev, df=fh)
3794 else:
3793 else:
3795 for rev in revs:
3794 for rev in revs:
3796 segmentforrevs(rev, rev)
3795 segmentforrevs(rev, rev)
3797
3796
3798 def doreadbatch():
3797 def doreadbatch():
3799 rl.clearcaches()
3798 rl.clearcaches()
3800 with lazy_reading(rl):
3799 with lazy_reading(rl):
3801 segmentforrevs(revs[0], revs[-1])
3800 segmentforrevs(revs[0], revs[-1])
3802
3801
3803 def doreadbatchcachedfh():
3802 def doreadbatchcachedfh():
3804 rl.clearcaches()
3803 rl.clearcaches()
3805 with reading(rl) as fh:
3804 with reading(rl) as fh:
3806 if fh is not None:
3805 if fh is not None:
3807 segmentforrevs(revs[0], revs[-1], df=fh)
3806 segmentforrevs(revs[0], revs[-1], df=fh)
3808 else:
3807 else:
3809 segmentforrevs(revs[0], revs[-1])
3808 segmentforrevs(revs[0], revs[-1])
3810
3809
3811 def dochunk():
3810 def dochunk():
3812 rl.clearcaches()
3811 rl.clearcaches()
3813 # chunk used to be available directly on the revlog
3812 # chunk used to be available directly on the revlog
3814 _chunk = getattr(rl, '_inner', rl)._chunk
3813 _chunk = getattr(rl, '_inner', rl)._chunk
3815 with reading(rl) as fh:
3814 with reading(rl) as fh:
3816 if fh is not None:
3815 if fh is not None:
3817 for rev in revs:
3816 for rev in revs:
3818 _chunk(rev, df=fh)
3817 _chunk(rev, df=fh)
3819 else:
3818 else:
3820 for rev in revs:
3819 for rev in revs:
3821 _chunk(rev)
3820 _chunk(rev)
3822
3821
3823 chunks = [None]
3822 chunks = [None]
3824
3823
3825 def dochunkbatch():
3824 def dochunkbatch():
3826 rl.clearcaches()
3825 rl.clearcaches()
3827 _chunks = getattr(rl, '_inner', rl)._chunks
3826 _chunks = getattr(rl, '_inner', rl)._chunks
3828 with reading(rl) as fh:
3827 with reading(rl) as fh:
3829 if fh is not None:
3828 if fh is not None:
3830 # Save chunks as a side-effect.
3829 # Save chunks as a side-effect.
3831 chunks[0] = _chunks(revs, df=fh)
3830 chunks[0] = _chunks(revs, df=fh)
3832 else:
3831 else:
3833 # Save chunks as a side-effect.
3832 # Save chunks as a side-effect.
3834 chunks[0] = _chunks(revs)
3833 chunks[0] = _chunks(revs)
3835
3834
3836 def docompress(compressor):
3835 def docompress(compressor):
3837 rl.clearcaches()
3836 rl.clearcaches()
3838
3837
3839 compressor_holder = getattr(rl, '_inner', rl)
3838 compressor_holder = getattr(rl, '_inner', rl)
3840
3839
3841 try:
3840 try:
3842 # Swap in the requested compression engine.
3841 # Swap in the requested compression engine.
3843 oldcompressor = compressor_holder._compressor
3842 oldcompressor = compressor_holder._compressor
3844 compressor_holder._compressor = compressor
3843 compressor_holder._compressor = compressor
3845 for chunk in chunks[0]:
3844 for chunk in chunks[0]:
3846 rl.compress(chunk)
3845 rl.compress(chunk)
3847 finally:
3846 finally:
3848 compressor_holder._compressor = oldcompressor
3847 compressor_holder._compressor = oldcompressor
3849
3848
3850 benches = [
3849 benches = [
3851 (lambda: doread(), b'read'),
3850 (lambda: doread(), b'read'),
3852 (lambda: doreadcachedfh(), b'read w/ reused fd'),
3851 (lambda: doreadcachedfh(), b'read w/ reused fd'),
3853 (lambda: doreadbatch(), b'read batch'),
3852 (lambda: doreadbatch(), b'read batch'),
3854 (lambda: doreadbatchcachedfh(), b'read batch w/ reused fd'),
3853 (lambda: doreadbatchcachedfh(), b'read batch w/ reused fd'),
3855 (lambda: dochunk(), b'chunk'),
3854 (lambda: dochunk(), b'chunk'),
3856 (lambda: dochunkbatch(), b'chunk batch'),
3855 (lambda: dochunkbatch(), b'chunk batch'),
3857 ]
3856 ]
3858
3857
3859 for engine in sorted(engines):
3858 for engine in sorted(engines):
3860 compressor = util.compengines[engine].revlogcompressor()
3859 compressor = util.compengines[engine].revlogcompressor()
3861 benches.append(
3860 benches.append(
3862 (
3861 (
3863 functools.partial(docompress, compressor),
3862 functools.partial(docompress, compressor),
3864 b'compress w/ %s' % engine,
3863 b'compress w/ %s' % engine,
3865 )
3864 )
3866 )
3865 )
3867
3866
3868 for fn, title in benches:
3867 for fn, title in benches:
3869 timer, fm = gettimer(ui, opts)
3868 timer, fm = gettimer(ui, opts)
3870 timer(fn, title=title)
3869 timer(fn, title=title)
3871 fm.end()
3870 fm.end()
3872
3871
3873
3872
3874 @command(
3873 @command(
3875 b'perf::revlogrevision|perfrevlogrevision',
3874 b'perf::revlogrevision|perfrevlogrevision',
3876 revlogopts
3875 revlogopts
3877 + formatteropts
3876 + formatteropts
3878 + [(b'', b'cache', False, b'use caches instead of clearing')],
3877 + [(b'', b'cache', False, b'use caches instead of clearing')],
3879 b'-c|-m|FILE REV',
3878 b'-c|-m|FILE REV',
3880 )
3879 )
3881 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
3880 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
3882 """Benchmark obtaining a revlog revision.
3881 """Benchmark obtaining a revlog revision.
3883
3882
3884 Obtaining a revlog revision consists of roughly the following steps:
3883 Obtaining a revlog revision consists of roughly the following steps:
3885
3884
3886 1. Compute the delta chain
3885 1. Compute the delta chain
3887 2. Slice the delta chain if applicable
3886 2. Slice the delta chain if applicable
3888 3. Obtain the raw chunks for that delta chain
3887 3. Obtain the raw chunks for that delta chain
3889 4. Decompress each raw chunk
3888 4. Decompress each raw chunk
3890 5. Apply binary patches to obtain fulltext
3889 5. Apply binary patches to obtain fulltext
3891 6. Verify hash of fulltext
3890 6. Verify hash of fulltext
3892
3891
3893 This command measures the time spent in each of these phases.
3892 This command measures the time spent in each of these phases.
3894 """
3893 """
3895 opts = _byteskwargs(opts)
3894 opts = _byteskwargs(opts)
3896
3895
3897 if opts.get(b'changelog') or opts.get(b'manifest'):
3896 if opts.get(b'changelog') or opts.get(b'manifest'):
3898 file_, rev = None, file_
3897 file_, rev = None, file_
3899 elif rev is None:
3898 elif rev is None:
3900 raise error.CommandError(b'perfrevlogrevision', b'invalid arguments')
3899 raise error.CommandError(b'perfrevlogrevision', b'invalid arguments')
3901
3900
3902 r = cmdutil.openrevlog(repo, b'perfrevlogrevision', file_, opts)
3901 r = cmdutil.openrevlog(repo, b'perfrevlogrevision', file_, opts)
3903
3902
3904 # _chunkraw was renamed to _getsegmentforrevs.
3903 # _chunkraw was renamed to _getsegmentforrevs.
3905 try:
3904 try:
3906 segmentforrevs = r._inner.get_segment_for_revs
3905 segmentforrevs = r._inner.get_segment_for_revs
3907 except AttributeError:
3906 except AttributeError:
3908 try:
3907 try:
3909 segmentforrevs = r._getsegmentforrevs
3908 segmentforrevs = r._getsegmentforrevs
3910 except AttributeError:
3909 except AttributeError:
3911 segmentforrevs = r._chunkraw
3910 segmentforrevs = r._chunkraw
3912
3911
3913 node = r.lookup(rev)
3912 node = r.lookup(rev)
3914 rev = r.rev(node)
3913 rev = r.rev(node)
3915
3914
3916 if getattr(r, 'reading', None) is not None:
3915 if getattr(r, 'reading', None) is not None:
3917
3916
3918 @contextlib.contextmanager
3917 @contextlib.contextmanager
3919 def lazy_reading(r):
3918 def lazy_reading(r):
3920 with r.reading():
3919 with r.reading():
3921 yield
3920 yield
3922
3921
3923 else:
3922 else:
3924
3923
3925 @contextlib.contextmanager
3924 @contextlib.contextmanager
3926 def lazy_reading(r):
3925 def lazy_reading(r):
3927 yield
3926 yield
3928
3927
3929 def getrawchunks(data, chain):
3928 def getrawchunks(data, chain):
3930 start = r.start
3929 start = r.start
3931 length = r.length
3930 length = r.length
3932 inline = r._inline
3931 inline = r._inline
3933 try:
3932 try:
3934 iosize = r.index.entry_size
3933 iosize = r.index.entry_size
3935 except AttributeError:
3934 except AttributeError:
3936 iosize = r._io.size
3935 iosize = r._io.size
3937 buffer = util.buffer
3936 buffer = util.buffer
3938
3937
3939 chunks = []
3938 chunks = []
3940 ladd = chunks.append
3939 ladd = chunks.append
3941 for idx, item in enumerate(chain):
3940 for idx, item in enumerate(chain):
3942 offset = start(item[0])
3941 offset = start(item[0])
3943 bits = data[idx]
3942 bits = data[idx]
3944 for rev in item:
3943 for rev in item:
3945 chunkstart = start(rev)
3944 chunkstart = start(rev)
3946 if inline:
3945 if inline:
3947 chunkstart += (rev + 1) * iosize
3946 chunkstart += (rev + 1) * iosize
3948 chunklength = length(rev)
3947 chunklength = length(rev)
3949 ladd(buffer(bits, chunkstart - offset, chunklength))
3948 ladd(buffer(bits, chunkstart - offset, chunklength))
3950
3949
3951 return chunks
3950 return chunks
3952
3951
3953 def dodeltachain(rev):
3952 def dodeltachain(rev):
3954 if not cache:
3953 if not cache:
3955 r.clearcaches()
3954 r.clearcaches()
3956 r._deltachain(rev)
3955 r._deltachain(rev)
3957
3956
3958 def doread(chain):
3957 def doread(chain):
3959 if not cache:
3958 if not cache:
3960 r.clearcaches()
3959 r.clearcaches()
3961 for item in slicedchain:
3960 for item in slicedchain:
3962 with lazy_reading(r):
3961 with lazy_reading(r):
3963 segmentforrevs(item[0], item[-1])
3962 segmentforrevs(item[0], item[-1])
3964
3963
3965 def doslice(r, chain, size):
3964 def doslice(r, chain, size):
3966 for s in slicechunk(r, chain, targetsize=size):
3965 for s in slicechunk(r, chain, targetsize=size):
3967 pass
3966 pass
3968
3967
3969 def dorawchunks(data, chain):
3968 def dorawchunks(data, chain):
3970 if not cache:
3969 if not cache:
3971 r.clearcaches()
3970 r.clearcaches()
3972 getrawchunks(data, chain)
3971 getrawchunks(data, chain)
3973
3972
3974 def dodecompress(chunks):
3973 def dodecompress(chunks):
3975 decomp = r.decompress
3974 decomp = r.decompress
3976 for chunk in chunks:
3975 for chunk in chunks:
3977 decomp(chunk)
3976 decomp(chunk)
3978
3977
3979 def dopatch(text, bins):
3978 def dopatch(text, bins):
3980 if not cache:
3979 if not cache:
3981 r.clearcaches()
3980 r.clearcaches()
3982 mdiff.patches(text, bins)
3981 mdiff.patches(text, bins)
3983
3982
3984 def dohash(text):
3983 def dohash(text):
3985 if not cache:
3984 if not cache:
3986 r.clearcaches()
3985 r.clearcaches()
3987 r.checkhash(text, node, rev=rev)
3986 r.checkhash(text, node, rev=rev)
3988
3987
3989 def dorevision():
3988 def dorevision():
3990 if not cache:
3989 if not cache:
3991 r.clearcaches()
3990 r.clearcaches()
3992 r.revision(node)
3991 r.revision(node)
3993
3992
3994 try:
3993 try:
3995 from mercurial.revlogutils.deltas import slicechunk
3994 from mercurial.revlogutils.deltas import slicechunk
3996 except ImportError:
3995 except ImportError:
3997 slicechunk = getattr(revlog, '_slicechunk', None)
3996 slicechunk = getattr(revlog, '_slicechunk', None)
3998
3997
3999 size = r.length(rev)
3998 size = r.length(rev)
4000 chain = r._deltachain(rev)[0]
3999 chain = r._deltachain(rev)[0]
4001
4000
4002 with_sparse_read = False
4001 with_sparse_read = False
4003 if hasattr(r, 'data_config'):
4002 if hasattr(r, 'data_config'):
4004 with_sparse_read = r.data_config.with_sparse_read
4003 with_sparse_read = r.data_config.with_sparse_read
4005 elif hasattr(r, '_withsparseread'):
4004 elif hasattr(r, '_withsparseread'):
4006 with_sparse_read = r._withsparseread
4005 with_sparse_read = r._withsparseread
4007 if with_sparse_read:
4006 if with_sparse_read:
4008 slicedchain = (chain,)
4007 slicedchain = (chain,)
4009 else:
4008 else:
4010 slicedchain = tuple(slicechunk(r, chain, targetsize=size))
4009 slicedchain = tuple(slicechunk(r, chain, targetsize=size))
4011 data = [segmentforrevs(seg[0], seg[-1])[1] for seg in slicedchain]
4010 data = [segmentforrevs(seg[0], seg[-1])[1] for seg in slicedchain]
4012 rawchunks = getrawchunks(data, slicedchain)
4011 rawchunks = getrawchunks(data, slicedchain)
4013 bins = r._inner._chunks(chain)
4012 bins = r._inner._chunks(chain)
4014 text = bytes(bins[0])
4013 text = bytes(bins[0])
4015 bins = bins[1:]
4014 bins = bins[1:]
4016 text = mdiff.patches(text, bins)
4015 text = mdiff.patches(text, bins)
4017
4016
4018 benches = [
4017 benches = [
4019 (lambda: dorevision(), b'full'),
4018 (lambda: dorevision(), b'full'),
4020 (lambda: dodeltachain(rev), b'deltachain'),
4019 (lambda: dodeltachain(rev), b'deltachain'),
4021 (lambda: doread(chain), b'read'),
4020 (lambda: doread(chain), b'read'),
4022 ]
4021 ]
4023
4022
4024 if with_sparse_read:
4023 if with_sparse_read:
4025 slicing = (lambda: doslice(r, chain, size), b'slice-sparse-chain')
4024 slicing = (lambda: doslice(r, chain, size), b'slice-sparse-chain')
4026 benches.append(slicing)
4025 benches.append(slicing)
4027
4026
4028 benches.extend(
4027 benches.extend(
4029 [
4028 [
4030 (lambda: dorawchunks(data, slicedchain), b'rawchunks'),
4029 (lambda: dorawchunks(data, slicedchain), b'rawchunks'),
4031 (lambda: dodecompress(rawchunks), b'decompress'),
4030 (lambda: dodecompress(rawchunks), b'decompress'),
4032 (lambda: dopatch(text, bins), b'patch'),
4031 (lambda: dopatch(text, bins), b'patch'),
4033 (lambda: dohash(text), b'hash'),
4032 (lambda: dohash(text), b'hash'),
4034 ]
4033 ]
4035 )
4034 )
4036
4035
4037 timer, fm = gettimer(ui, opts)
4036 timer, fm = gettimer(ui, opts)
4038 for fn, title in benches:
4037 for fn, title in benches:
4039 timer(fn, title=title)
4038 timer(fn, title=title)
4040 fm.end()
4039 fm.end()
4041
4040
4042
4041
4043 @command(
4042 @command(
4044 b'perf::revset|perfrevset',
4043 b'perf::revset|perfrevset',
4045 [
4044 [
4046 (b'C', b'clear', False, b'clear volatile cache between each call.'),
4045 (b'C', b'clear', False, b'clear volatile cache between each call.'),
4047 (b'', b'contexts', False, b'obtain changectx for each revision'),
4046 (b'', b'contexts', False, b'obtain changectx for each revision'),
4048 ]
4047 ]
4049 + formatteropts,
4048 + formatteropts,
4050 b"REVSET",
4049 b"REVSET",
4051 )
4050 )
4052 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
4051 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
4053 """benchmark the execution time of a revset
4052 """benchmark the execution time of a revset
4054
4053
4055 Use the --clean option if need to evaluate the impact of build volatile
4054 Use the --clean option if need to evaluate the impact of build volatile
4056 revisions set cache on the revset execution. Volatile cache hold filtered
4055 revisions set cache on the revset execution. Volatile cache hold filtered
4057 and obsolete related cache."""
4056 and obsolete related cache."""
4058 opts = _byteskwargs(opts)
4057 opts = _byteskwargs(opts)
4059
4058
4060 timer, fm = gettimer(ui, opts)
4059 timer, fm = gettimer(ui, opts)
4061
4060
4062 def d():
4061 def d():
4063 if clear:
4062 if clear:
4064 repo.invalidatevolatilesets()
4063 repo.invalidatevolatilesets()
4065 if contexts:
4064 if contexts:
4066 for ctx in repo.set(expr):
4065 for ctx in repo.set(expr):
4067 pass
4066 pass
4068 else:
4067 else:
4069 for r in repo.revs(expr):
4068 for r in repo.revs(expr):
4070 pass
4069 pass
4071
4070
4072 timer(d)
4071 timer(d)
4073 fm.end()
4072 fm.end()
4074
4073
4075
4074
4076 @command(
4075 @command(
4077 b'perf::volatilesets|perfvolatilesets',
4076 b'perf::volatilesets|perfvolatilesets',
4078 [
4077 [
4079 (b'', b'clear-obsstore', False, b'drop obsstore between each call.'),
4078 (b'', b'clear-obsstore', False, b'drop obsstore between each call.'),
4080 ]
4079 ]
4081 + formatteropts,
4080 + formatteropts,
4082 )
4081 )
4083 def perfvolatilesets(ui, repo, *names, **opts):
4082 def perfvolatilesets(ui, repo, *names, **opts):
4084 """benchmark the computation of various volatile set
4083 """benchmark the computation of various volatile set
4085
4084
4086 Volatile set computes element related to filtering and obsolescence."""
4085 Volatile set computes element related to filtering and obsolescence."""
4087 opts = _byteskwargs(opts)
4086 opts = _byteskwargs(opts)
4088 timer, fm = gettimer(ui, opts)
4087 timer, fm = gettimer(ui, opts)
4089 repo = repo.unfiltered()
4088 repo = repo.unfiltered()
4090
4089
4091 def getobs(name):
4090 def getobs(name):
4092 def d():
4091 def d():
4093 repo.invalidatevolatilesets()
4092 repo.invalidatevolatilesets()
4094 if opts[b'clear_obsstore']:
4093 if opts[b'clear_obsstore']:
4095 clearfilecache(repo, b'obsstore')
4094 clearfilecache(repo, b'obsstore')
4096 obsolete.getrevs(repo, name)
4095 obsolete.getrevs(repo, name)
4097
4096
4098 return d
4097 return d
4099
4098
4100 allobs = sorted(obsolete.cachefuncs)
4099 allobs = sorted(obsolete.cachefuncs)
4101 if names:
4100 if names:
4102 allobs = [n for n in allobs if n in names]
4101 allobs = [n for n in allobs if n in names]
4103
4102
4104 for name in allobs:
4103 for name in allobs:
4105 timer(getobs(name), title=name)
4104 timer(getobs(name), title=name)
4106
4105
4107 def getfiltered(name):
4106 def getfiltered(name):
4108 def d():
4107 def d():
4109 repo.invalidatevolatilesets()
4108 repo.invalidatevolatilesets()
4110 if opts[b'clear_obsstore']:
4109 if opts[b'clear_obsstore']:
4111 clearfilecache(repo, b'obsstore')
4110 clearfilecache(repo, b'obsstore')
4112 repoview.filterrevs(repo, name)
4111 repoview.filterrevs(repo, name)
4113
4112
4114 return d
4113 return d
4115
4114
4116 allfilter = sorted(repoview.filtertable)
4115 allfilter = sorted(repoview.filtertable)
4117 if names:
4116 if names:
4118 allfilter = [n for n in allfilter if n in names]
4117 allfilter = [n for n in allfilter if n in names]
4119
4118
4120 for name in allfilter:
4119 for name in allfilter:
4121 timer(getfiltered(name), title=name)
4120 timer(getfiltered(name), title=name)
4122 fm.end()
4121 fm.end()
4123
4122
4124
4123
4125 @command(
4124 @command(
4126 b'perf::branchmap|perfbranchmap',
4125 b'perf::branchmap|perfbranchmap',
4127 [
4126 [
4128 (b'f', b'full', False, b'Includes build time of subset'),
4127 (b'f', b'full', False, b'Includes build time of subset'),
4129 (
4128 (
4130 b'',
4129 b'',
4131 b'clear-revbranch',
4130 b'clear-revbranch',
4132 False,
4131 False,
4133 b'purge the revbranch cache between computation',
4132 b'purge the revbranch cache between computation',
4134 ),
4133 ),
4135 ]
4134 ]
4136 + formatteropts,
4135 + formatteropts,
4137 )
4136 )
4138 def perfbranchmap(ui, repo, *filternames, **opts):
4137 def perfbranchmap(ui, repo, *filternames, **opts):
4139 """benchmark the update of a branchmap
4138 """benchmark the update of a branchmap
4140
4139
4141 This benchmarks the full repo.branchmap() call with read and write disabled
4140 This benchmarks the full repo.branchmap() call with read and write disabled
4142 """
4141 """
4143 opts = _byteskwargs(opts)
4142 opts = _byteskwargs(opts)
4144 full = opts.get(b"full", False)
4143 full = opts.get(b"full", False)
4145 clear_revbranch = opts.get(b"clear_revbranch", False)
4144 clear_revbranch = opts.get(b"clear_revbranch", False)
4146 timer, fm = gettimer(ui, opts)
4145 timer, fm = gettimer(ui, opts)
4147
4146
4148 def getbranchmap(filtername):
4147 def getbranchmap(filtername):
4149 """generate a benchmark function for the filtername"""
4148 """generate a benchmark function for the filtername"""
4150 if filtername is None:
4149 if filtername is None:
4151 view = repo
4150 view = repo
4152 else:
4151 else:
4153 view = repo.filtered(filtername)
4152 view = repo.filtered(filtername)
4154 if util.safehasattr(view._branchcaches, '_per_filter'):
4153 if util.safehasattr(view._branchcaches, '_per_filter'):
4155 filtered = view._branchcaches._per_filter
4154 filtered = view._branchcaches._per_filter
4156 else:
4155 else:
4157 # older versions
4156 # older versions
4158 filtered = view._branchcaches
4157 filtered = view._branchcaches
4159
4158
4160 def d():
4159 def d():
4161 if clear_revbranch:
4160 if clear_revbranch:
4162 repo.revbranchcache()._clear()
4161 repo.revbranchcache()._clear()
4163 if full:
4162 if full:
4164 view._branchcaches.clear()
4163 view._branchcaches.clear()
4165 else:
4164 else:
4166 filtered.pop(filtername, None)
4165 filtered.pop(filtername, None)
4167 view.branchmap()
4166 view.branchmap()
4168
4167
4169 return d
4168 return d
4170
4169
4171 # add filter in smaller subset to bigger subset
4170 # add filter in smaller subset to bigger subset
4172 possiblefilters = set(repoview.filtertable)
4171 possiblefilters = set(repoview.filtertable)
4173 if filternames:
4172 if filternames:
4174 possiblefilters &= set(filternames)
4173 possiblefilters &= set(filternames)
4175 subsettable = getbranchmapsubsettable()
4174 subsettable = getbranchmapsubsettable()
4176 allfilters = []
4175 allfilters = []
4177 while possiblefilters:
4176 while possiblefilters:
4178 for name in possiblefilters:
4177 for name in possiblefilters:
4179 subset = subsettable.get(name)
4178 subset = subsettable.get(name)
4180 if subset not in possiblefilters:
4179 if subset not in possiblefilters:
4181 break
4180 break
4182 else:
4181 else:
4183 assert False, b'subset cycle %s!' % possiblefilters
4182 assert False, b'subset cycle %s!' % possiblefilters
4184 allfilters.append(name)
4183 allfilters.append(name)
4185 possiblefilters.remove(name)
4184 possiblefilters.remove(name)
4186
4185
4187 # warm the cache
4186 # warm the cache
4188 if not full:
4187 if not full:
4189 for name in allfilters:
4188 for name in allfilters:
4190 repo.filtered(name).branchmap()
4189 repo.filtered(name).branchmap()
4191 if not filternames or b'unfiltered' in filternames:
4190 if not filternames or b'unfiltered' in filternames:
4192 # add unfiltered
4191 # add unfiltered
4193 allfilters.append(None)
4192 allfilters.append(None)
4194
4193
4195 if util.safehasattr(branchmap.branchcache, 'fromfile'):
4194 if util.safehasattr(branchmap.branchcache, 'fromfile'):
4196 branchcacheread = safeattrsetter(branchmap.branchcache, b'fromfile')
4195 branchcacheread = safeattrsetter(branchmap.branchcache, b'fromfile')
4197 branchcacheread.set(classmethod(lambda *args: None))
4196 branchcacheread.set(classmethod(lambda *args: None))
4198 else:
4197 else:
4199 # older versions
4198 # older versions
4200 branchcacheread = safeattrsetter(branchmap, b'read')
4199 branchcacheread = safeattrsetter(branchmap, b'read')
4201 branchcacheread.set(lambda *args: None)
4200 branchcacheread.set(lambda *args: None)
4202 branchcachewrite = safeattrsetter(branchmap.branchcache, b'write')
4201 branchcachewrite = safeattrsetter(branchmap.branchcache, b'write')
4203 branchcachewrite.set(lambda *args: None)
4202 branchcachewrite.set(lambda *args: None)
4204 try:
4203 try:
4205 for name in allfilters:
4204 for name in allfilters:
4206 printname = name
4205 printname = name
4207 if name is None:
4206 if name is None:
4208 printname = b'unfiltered'
4207 printname = b'unfiltered'
4209 timer(getbranchmap(name), title=printname)
4208 timer(getbranchmap(name), title=printname)
4210 finally:
4209 finally:
4211 branchcacheread.restore()
4210 branchcacheread.restore()
4212 branchcachewrite.restore()
4211 branchcachewrite.restore()
4213 fm.end()
4212 fm.end()
4214
4213
4215
4214
4216 @command(
4215 @command(
4217 b'perf::branchmapupdate|perfbranchmapupdate',
4216 b'perf::branchmapupdate|perfbranchmapupdate',
4218 [
4217 [
4219 (b'', b'base', [], b'subset of revision to start from'),
4218 (b'', b'base', [], b'subset of revision to start from'),
4220 (b'', b'target', [], b'subset of revision to end with'),
4219 (b'', b'target', [], b'subset of revision to end with'),
4221 (b'', b'clear-caches', False, b'clear cache between each runs'),
4220 (b'', b'clear-caches', False, b'clear cache between each runs'),
4222 ]
4221 ]
4223 + formatteropts,
4222 + formatteropts,
4224 )
4223 )
4225 def perfbranchmapupdate(ui, repo, base=(), target=(), **opts):
4224 def perfbranchmapupdate(ui, repo, base=(), target=(), **opts):
4226 """benchmark branchmap update from for <base> revs to <target> revs
4225 """benchmark branchmap update from for <base> revs to <target> revs
4227
4226
4228 If `--clear-caches` is passed, the following items will be reset before
4227 If `--clear-caches` is passed, the following items will be reset before
4229 each update:
4228 each update:
4230 * the changelog instance and associated indexes
4229 * the changelog instance and associated indexes
4231 * the rev-branch-cache instance
4230 * the rev-branch-cache instance
4232
4231
4233 Examples:
4232 Examples:
4234
4233
4235 # update for the one last revision
4234 # update for the one last revision
4236 $ hg perfbranchmapupdate --base 'not tip' --target 'tip'
4235 $ hg perfbranchmapupdate --base 'not tip' --target 'tip'
4237
4236
4238 $ update for change coming with a new branch
4237 $ update for change coming with a new branch
4239 $ hg perfbranchmapupdate --base 'stable' --target 'default'
4238 $ hg perfbranchmapupdate --base 'stable' --target 'default'
4240 """
4239 """
4241 from mercurial import branchmap
4240 from mercurial import branchmap
4242 from mercurial import repoview
4241 from mercurial import repoview
4243
4242
4244 opts = _byteskwargs(opts)
4243 opts = _byteskwargs(opts)
4245 timer, fm = gettimer(ui, opts)
4244 timer, fm = gettimer(ui, opts)
4246 clearcaches = opts[b'clear_caches']
4245 clearcaches = opts[b'clear_caches']
4247 unfi = repo.unfiltered()
4246 unfi = repo.unfiltered()
4248 x = [None] # used to pass data between closure
4247 x = [None] # used to pass data between closure
4249
4248
4250 # we use a `list` here to avoid possible side effect from smartset
4249 # we use a `list` here to avoid possible side effect from smartset
4251 baserevs = list(scmutil.revrange(repo, base))
4250 baserevs = list(scmutil.revrange(repo, base))
4252 targetrevs = list(scmutil.revrange(repo, target))
4251 targetrevs = list(scmutil.revrange(repo, target))
4253 if not baserevs:
4252 if not baserevs:
4254 raise error.Abort(b'no revisions selected for --base')
4253 raise error.Abort(b'no revisions selected for --base')
4255 if not targetrevs:
4254 if not targetrevs:
4256 raise error.Abort(b'no revisions selected for --target')
4255 raise error.Abort(b'no revisions selected for --target')
4257
4256
4258 # make sure the target branchmap also contains the one in the base
4257 # make sure the target branchmap also contains the one in the base
4259 targetrevs = list(set(baserevs) | set(targetrevs))
4258 targetrevs = list(set(baserevs) | set(targetrevs))
4260 targetrevs.sort()
4259 targetrevs.sort()
4261
4260
4262 cl = repo.changelog
4261 cl = repo.changelog
4263 allbaserevs = list(cl.ancestors(baserevs, inclusive=True))
4262 allbaserevs = list(cl.ancestors(baserevs, inclusive=True))
4264 allbaserevs.sort()
4263 allbaserevs.sort()
4265 alltargetrevs = frozenset(cl.ancestors(targetrevs, inclusive=True))
4264 alltargetrevs = frozenset(cl.ancestors(targetrevs, inclusive=True))
4266
4265
4267 newrevs = list(alltargetrevs.difference(allbaserevs))
4266 newrevs = list(alltargetrevs.difference(allbaserevs))
4268 newrevs.sort()
4267 newrevs.sort()
4269
4268
4270 allrevs = frozenset(unfi.changelog.revs())
4269 allrevs = frozenset(unfi.changelog.revs())
4271 basefilterrevs = frozenset(allrevs.difference(allbaserevs))
4270 basefilterrevs = frozenset(allrevs.difference(allbaserevs))
4272 targetfilterrevs = frozenset(allrevs.difference(alltargetrevs))
4271 targetfilterrevs = frozenset(allrevs.difference(alltargetrevs))
4273
4272
4274 def basefilter(repo, visibilityexceptions=None):
4273 def basefilter(repo, visibilityexceptions=None):
4275 return basefilterrevs
4274 return basefilterrevs
4276
4275
4277 def targetfilter(repo, visibilityexceptions=None):
4276 def targetfilter(repo, visibilityexceptions=None):
4278 return targetfilterrevs
4277 return targetfilterrevs
4279
4278
4280 msg = b'benchmark of branchmap with %d revisions with %d new ones\n'
4279 msg = b'benchmark of branchmap with %d revisions with %d new ones\n'
4281 ui.status(msg % (len(allbaserevs), len(newrevs)))
4280 ui.status(msg % (len(allbaserevs), len(newrevs)))
4282 if targetfilterrevs:
4281 if targetfilterrevs:
4283 msg = b'(%d revisions still filtered)\n'
4282 msg = b'(%d revisions still filtered)\n'
4284 ui.status(msg % len(targetfilterrevs))
4283 ui.status(msg % len(targetfilterrevs))
4285
4284
4286 try:
4285 try:
4287 repoview.filtertable[b'__perf_branchmap_update_base'] = basefilter
4286 repoview.filtertable[b'__perf_branchmap_update_base'] = basefilter
4288 repoview.filtertable[b'__perf_branchmap_update_target'] = targetfilter
4287 repoview.filtertable[b'__perf_branchmap_update_target'] = targetfilter
4289
4288
4290 baserepo = repo.filtered(b'__perf_branchmap_update_base')
4289 baserepo = repo.filtered(b'__perf_branchmap_update_base')
4291 targetrepo = repo.filtered(b'__perf_branchmap_update_target')
4290 targetrepo = repo.filtered(b'__perf_branchmap_update_target')
4292
4291
4293 # try to find an existing branchmap to reuse
4292 # try to find an existing branchmap to reuse
4294 subsettable = getbranchmapsubsettable()
4293 subsettable = getbranchmapsubsettable()
4295 candidatefilter = subsettable.get(None)
4294 candidatefilter = subsettable.get(None)
4296 while candidatefilter is not None:
4295 while candidatefilter is not None:
4297 candidatebm = repo.filtered(candidatefilter).branchmap()
4296 candidatebm = repo.filtered(candidatefilter).branchmap()
4298 if candidatebm.validfor(baserepo):
4297 if candidatebm.validfor(baserepo):
4299 filtered = repoview.filterrevs(repo, candidatefilter)
4298 filtered = repoview.filterrevs(repo, candidatefilter)
4300 missing = [r for r in allbaserevs if r in filtered]
4299 missing = [r for r in allbaserevs if r in filtered]
4301 base = candidatebm.copy()
4300 base = candidatebm.copy()
4302 base.update(baserepo, missing)
4301 base.update(baserepo, missing)
4303 break
4302 break
4304 candidatefilter = subsettable.get(candidatefilter)
4303 candidatefilter = subsettable.get(candidatefilter)
4305 else:
4304 else:
4306 # no suitable subset where found
4305 # no suitable subset where found
4307 base = branchmap.branchcache()
4306 base = branchmap.branchcache()
4308 base.update(baserepo, allbaserevs)
4307 base.update(baserepo, allbaserevs)
4309
4308
4310 def setup():
4309 def setup():
4311 x[0] = base.copy()
4310 x[0] = base.copy()
4312 if clearcaches:
4311 if clearcaches:
4313 unfi._revbranchcache = None
4312 unfi._revbranchcache = None
4314 clearchangelog(repo)
4313 clearchangelog(repo)
4315
4314
4316 def bench():
4315 def bench():
4317 x[0].update(targetrepo, newrevs)
4316 x[0].update(targetrepo, newrevs)
4318
4317
4319 timer(bench, setup=setup)
4318 timer(bench, setup=setup)
4320 fm.end()
4319 fm.end()
4321 finally:
4320 finally:
4322 repoview.filtertable.pop(b'__perf_branchmap_update_base', None)
4321 repoview.filtertable.pop(b'__perf_branchmap_update_base', None)
4323 repoview.filtertable.pop(b'__perf_branchmap_update_target', None)
4322 repoview.filtertable.pop(b'__perf_branchmap_update_target', None)
4324
4323
4325
4324
4326 @command(
4325 @command(
4327 b'perf::branchmapload|perfbranchmapload',
4326 b'perf::branchmapload|perfbranchmapload',
4328 [
4327 [
4329 (b'f', b'filter', b'', b'Specify repoview filter'),
4328 (b'f', b'filter', b'', b'Specify repoview filter'),
4330 (b'', b'list', False, b'List brachmap filter caches'),
4329 (b'', b'list', False, b'List brachmap filter caches'),
4331 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
4330 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
4332 ]
4331 ]
4333 + formatteropts,
4332 + formatteropts,
4334 )
4333 )
4335 def perfbranchmapload(ui, repo, filter=b'', list=False, **opts):
4334 def perfbranchmapload(ui, repo, filter=b'', list=False, **opts):
4336 """benchmark reading the branchmap"""
4335 """benchmark reading the branchmap"""
4337 opts = _byteskwargs(opts)
4336 opts = _byteskwargs(opts)
4338 clearrevlogs = opts[b'clear_revlogs']
4337 clearrevlogs = opts[b'clear_revlogs']
4339
4338
4340 if list:
4339 if list:
4341 for name, kind, st in repo.cachevfs.readdir(stat=True):
4340 for name, kind, st in repo.cachevfs.readdir(stat=True):
4342 if name.startswith(b'branch2'):
4341 if name.startswith(b'branch2'):
4343 filtername = name.partition(b'-')[2] or b'unfiltered'
4342 filtername = name.partition(b'-')[2] or b'unfiltered'
4344 ui.status(
4343 ui.status(
4345 b'%s - %s\n' % (filtername, util.bytecount(st.st_size))
4344 b'%s - %s\n' % (filtername, util.bytecount(st.st_size))
4346 )
4345 )
4347 return
4346 return
4348 if not filter:
4347 if not filter:
4349 filter = None
4348 filter = None
4350 subsettable = getbranchmapsubsettable()
4349 subsettable = getbranchmapsubsettable()
4351 if filter is None:
4350 if filter is None:
4352 repo = repo.unfiltered()
4351 repo = repo.unfiltered()
4353 else:
4352 else:
4354 repo = repoview.repoview(repo, filter)
4353 repo = repoview.repoview(repo, filter)
4355
4354
4356 repo.branchmap() # make sure we have a relevant, up to date branchmap
4355 repo.branchmap() # make sure we have a relevant, up to date branchmap
4357
4356
4358 try:
4357 try:
4359 fromfile = branchmap.branchcache.fromfile
4358 fromfile = branchmap.branchcache.fromfile
4360 except AttributeError:
4359 except AttributeError:
4361 # older versions
4360 # older versions
4362 fromfile = branchmap.read
4361 fromfile = branchmap.read
4363
4362
4364 currentfilter = filter
4363 currentfilter = filter
4365 # try once without timer, the filter may not be cached
4364 # try once without timer, the filter may not be cached
4366 while fromfile(repo) is None:
4365 while fromfile(repo) is None:
4367 currentfilter = subsettable.get(currentfilter)
4366 currentfilter = subsettable.get(currentfilter)
4368 if currentfilter is None:
4367 if currentfilter is None:
4369 raise error.Abort(
4368 raise error.Abort(
4370 b'No branchmap cached for %s repo' % (filter or b'unfiltered')
4369 b'No branchmap cached for %s repo' % (filter or b'unfiltered')
4371 )
4370 )
4372 repo = repo.filtered(currentfilter)
4371 repo = repo.filtered(currentfilter)
4373 timer, fm = gettimer(ui, opts)
4372 timer, fm = gettimer(ui, opts)
4374
4373
4375 def setup():
4374 def setup():
4376 if clearrevlogs:
4375 if clearrevlogs:
4377 clearchangelog(repo)
4376 clearchangelog(repo)
4378
4377
4379 def bench():
4378 def bench():
4380 fromfile(repo)
4379 fromfile(repo)
4381
4380
4382 timer(bench, setup=setup)
4381 timer(bench, setup=setup)
4383 fm.end()
4382 fm.end()
4384
4383
4385
4384
4386 @command(b'perf::loadmarkers|perfloadmarkers')
4385 @command(b'perf::loadmarkers|perfloadmarkers')
4387 def perfloadmarkers(ui, repo):
4386 def perfloadmarkers(ui, repo):
4388 """benchmark the time to parse the on-disk markers for a repo
4387 """benchmark the time to parse the on-disk markers for a repo
4389
4388
4390 Result is the number of markers in the repo."""
4389 Result is the number of markers in the repo."""
4391 timer, fm = gettimer(ui)
4390 timer, fm = gettimer(ui)
4392 svfs = getsvfs(repo)
4391 svfs = getsvfs(repo)
4393 timer(lambda: len(obsolete.obsstore(repo, svfs)))
4392 timer(lambda: len(obsolete.obsstore(repo, svfs)))
4394 fm.end()
4393 fm.end()
4395
4394
4396
4395
4397 @command(
4396 @command(
4398 b'perf::lrucachedict|perflrucachedict',
4397 b'perf::lrucachedict|perflrucachedict',
4399 formatteropts
4398 formatteropts
4400 + [
4399 + [
4401 (b'', b'costlimit', 0, b'maximum total cost of items in cache'),
4400 (b'', b'costlimit', 0, b'maximum total cost of items in cache'),
4402 (b'', b'mincost', 0, b'smallest cost of items in cache'),
4401 (b'', b'mincost', 0, b'smallest cost of items in cache'),
4403 (b'', b'maxcost', 100, b'maximum cost of items in cache'),
4402 (b'', b'maxcost', 100, b'maximum cost of items in cache'),
4404 (b'', b'size', 4, b'size of cache'),
4403 (b'', b'size', 4, b'size of cache'),
4405 (b'', b'gets', 10000, b'number of key lookups'),
4404 (b'', b'gets', 10000, b'number of key lookups'),
4406 (b'', b'sets', 10000, b'number of key sets'),
4405 (b'', b'sets', 10000, b'number of key sets'),
4407 (b'', b'mixed', 10000, b'number of mixed mode operations'),
4406 (b'', b'mixed', 10000, b'number of mixed mode operations'),
4408 (
4407 (
4409 b'',
4408 b'',
4410 b'mixedgetfreq',
4409 b'mixedgetfreq',
4411 50,
4410 50,
4412 b'frequency of get vs set ops in mixed mode',
4411 b'frequency of get vs set ops in mixed mode',
4413 ),
4412 ),
4414 ],
4413 ],
4415 norepo=True,
4414 norepo=True,
4416 )
4415 )
4417 def perflrucache(
4416 def perflrucache(
4418 ui,
4417 ui,
4419 mincost=0,
4418 mincost=0,
4420 maxcost=100,
4419 maxcost=100,
4421 costlimit=0,
4420 costlimit=0,
4422 size=4,
4421 size=4,
4423 gets=10000,
4422 gets=10000,
4424 sets=10000,
4423 sets=10000,
4425 mixed=10000,
4424 mixed=10000,
4426 mixedgetfreq=50,
4425 mixedgetfreq=50,
4427 **opts
4426 **opts
4428 ):
4427 ):
4429 opts = _byteskwargs(opts)
4428 opts = _byteskwargs(opts)
4430
4429
4431 def doinit():
4430 def doinit():
4432 for i in _xrange(10000):
4431 for i in _xrange(10000):
4433 util.lrucachedict(size)
4432 util.lrucachedict(size)
4434
4433
4435 costrange = list(range(mincost, maxcost + 1))
4434 costrange = list(range(mincost, maxcost + 1))
4436
4435
4437 values = []
4436 values = []
4438 for i in _xrange(size):
4437 for i in _xrange(size):
4439 values.append(random.randint(0, _maxint))
4438 values.append(random.randint(0, _maxint))
4440
4439
4441 # Get mode fills the cache and tests raw lookup performance with no
4440 # Get mode fills the cache and tests raw lookup performance with no
4442 # eviction.
4441 # eviction.
4443 getseq = []
4442 getseq = []
4444 for i in _xrange(gets):
4443 for i in _xrange(gets):
4445 getseq.append(random.choice(values))
4444 getseq.append(random.choice(values))
4446
4445
4447 def dogets():
4446 def dogets():
4448 d = util.lrucachedict(size)
4447 d = util.lrucachedict(size)
4449 for v in values:
4448 for v in values:
4450 d[v] = v
4449 d[v] = v
4451 for key in getseq:
4450 for key in getseq:
4452 value = d[key]
4451 value = d[key]
4453 value # silence pyflakes warning
4452 value # silence pyflakes warning
4454
4453
4455 def dogetscost():
4454 def dogetscost():
4456 d = util.lrucachedict(size, maxcost=costlimit)
4455 d = util.lrucachedict(size, maxcost=costlimit)
4457 for i, v in enumerate(values):
4456 for i, v in enumerate(values):
4458 d.insert(v, v, cost=costs[i])
4457 d.insert(v, v, cost=costs[i])
4459 for key in getseq:
4458 for key in getseq:
4460 try:
4459 try:
4461 value = d[key]
4460 value = d[key]
4462 value # silence pyflakes warning
4461 value # silence pyflakes warning
4463 except KeyError:
4462 except KeyError:
4464 pass
4463 pass
4465
4464
4466 # Set mode tests insertion speed with cache eviction.
4465 # Set mode tests insertion speed with cache eviction.
4467 setseq = []
4466 setseq = []
4468 costs = []
4467 costs = []
4469 for i in _xrange(sets):
4468 for i in _xrange(sets):
4470 setseq.append(random.randint(0, _maxint))
4469 setseq.append(random.randint(0, _maxint))
4471 costs.append(random.choice(costrange))
4470 costs.append(random.choice(costrange))
4472
4471
4473 def doinserts():
4472 def doinserts():
4474 d = util.lrucachedict(size)
4473 d = util.lrucachedict(size)
4475 for v in setseq:
4474 for v in setseq:
4476 d.insert(v, v)
4475 d.insert(v, v)
4477
4476
4478 def doinsertscost():
4477 def doinsertscost():
4479 d = util.lrucachedict(size, maxcost=costlimit)
4478 d = util.lrucachedict(size, maxcost=costlimit)
4480 for i, v in enumerate(setseq):
4479 for i, v in enumerate(setseq):
4481 d.insert(v, v, cost=costs[i])
4480 d.insert(v, v, cost=costs[i])
4482
4481
4483 def dosets():
4482 def dosets():
4484 d = util.lrucachedict(size)
4483 d = util.lrucachedict(size)
4485 for v in setseq:
4484 for v in setseq:
4486 d[v] = v
4485 d[v] = v
4487
4486
4488 # Mixed mode randomly performs gets and sets with eviction.
4487 # Mixed mode randomly performs gets and sets with eviction.
4489 mixedops = []
4488 mixedops = []
4490 for i in _xrange(mixed):
4489 for i in _xrange(mixed):
4491 r = random.randint(0, 100)
4490 r = random.randint(0, 100)
4492 if r < mixedgetfreq:
4491 if r < mixedgetfreq:
4493 op = 0
4492 op = 0
4494 else:
4493 else:
4495 op = 1
4494 op = 1
4496
4495
4497 mixedops.append(
4496 mixedops.append(
4498 (op, random.randint(0, size * 2), random.choice(costrange))
4497 (op, random.randint(0, size * 2), random.choice(costrange))
4499 )
4498 )
4500
4499
4501 def domixed():
4500 def domixed():
4502 d = util.lrucachedict(size)
4501 d = util.lrucachedict(size)
4503
4502
4504 for op, v, cost in mixedops:
4503 for op, v, cost in mixedops:
4505 if op == 0:
4504 if op == 0:
4506 try:
4505 try:
4507 d[v]
4506 d[v]
4508 except KeyError:
4507 except KeyError:
4509 pass
4508 pass
4510 else:
4509 else:
4511 d[v] = v
4510 d[v] = v
4512
4511
4513 def domixedcost():
4512 def domixedcost():
4514 d = util.lrucachedict(size, maxcost=costlimit)
4513 d = util.lrucachedict(size, maxcost=costlimit)
4515
4514
4516 for op, v, cost in mixedops:
4515 for op, v, cost in mixedops:
4517 if op == 0:
4516 if op == 0:
4518 try:
4517 try:
4519 d[v]
4518 d[v]
4520 except KeyError:
4519 except KeyError:
4521 pass
4520 pass
4522 else:
4521 else:
4523 d.insert(v, v, cost=cost)
4522 d.insert(v, v, cost=cost)
4524
4523
4525 benches = [
4524 benches = [
4526 (doinit, b'init'),
4525 (doinit, b'init'),
4527 ]
4526 ]
4528
4527
4529 if costlimit:
4528 if costlimit:
4530 benches.extend(
4529 benches.extend(
4531 [
4530 [
4532 (dogetscost, b'gets w/ cost limit'),
4531 (dogetscost, b'gets w/ cost limit'),
4533 (doinsertscost, b'inserts w/ cost limit'),
4532 (doinsertscost, b'inserts w/ cost limit'),
4534 (domixedcost, b'mixed w/ cost limit'),
4533 (domixedcost, b'mixed w/ cost limit'),
4535 ]
4534 ]
4536 )
4535 )
4537 else:
4536 else:
4538 benches.extend(
4537 benches.extend(
4539 [
4538 [
4540 (dogets, b'gets'),
4539 (dogets, b'gets'),
4541 (doinserts, b'inserts'),
4540 (doinserts, b'inserts'),
4542 (dosets, b'sets'),
4541 (dosets, b'sets'),
4543 (domixed, b'mixed'),
4542 (domixed, b'mixed'),
4544 ]
4543 ]
4545 )
4544 )
4546
4545
4547 for fn, title in benches:
4546 for fn, title in benches:
4548 timer, fm = gettimer(ui, opts)
4547 timer, fm = gettimer(ui, opts)
4549 timer(fn, title=title)
4548 timer(fn, title=title)
4550 fm.end()
4549 fm.end()
4551
4550
4552
4551
4553 @command(
4552 @command(
4554 b'perf::write|perfwrite',
4553 b'perf::write|perfwrite',
4555 formatteropts
4554 formatteropts
4556 + [
4555 + [
4557 (b'', b'write-method', b'write', b'ui write method'),
4556 (b'', b'write-method', b'write', b'ui write method'),
4558 (b'', b'nlines', 100, b'number of lines'),
4557 (b'', b'nlines', 100, b'number of lines'),
4559 (b'', b'nitems', 100, b'number of items (per line)'),
4558 (b'', b'nitems', 100, b'number of items (per line)'),
4560 (b'', b'item', b'x', b'item that is written'),
4559 (b'', b'item', b'x', b'item that is written'),
4561 (b'', b'batch-line', None, b'pass whole line to write method at once'),
4560 (b'', b'batch-line', None, b'pass whole line to write method at once'),
4562 (b'', b'flush-line', None, b'flush after each line'),
4561 (b'', b'flush-line', None, b'flush after each line'),
4563 ],
4562 ],
4564 )
4563 )
4565 def perfwrite(ui, repo, **opts):
4564 def perfwrite(ui, repo, **opts):
4566 """microbenchmark ui.write (and others)"""
4565 """microbenchmark ui.write (and others)"""
4567 opts = _byteskwargs(opts)
4566 opts = _byteskwargs(opts)
4568
4567
4569 write = getattr(ui, _sysstr(opts[b'write_method']))
4568 write = getattr(ui, _sysstr(opts[b'write_method']))
4570 nlines = int(opts[b'nlines'])
4569 nlines = int(opts[b'nlines'])
4571 nitems = int(opts[b'nitems'])
4570 nitems = int(opts[b'nitems'])
4572 item = opts[b'item']
4571 item = opts[b'item']
4573 batch_line = opts.get(b'batch_line')
4572 batch_line = opts.get(b'batch_line')
4574 flush_line = opts.get(b'flush_line')
4573 flush_line = opts.get(b'flush_line')
4575
4574
4576 if batch_line:
4575 if batch_line:
4577 line = item * nitems + b'\n'
4576 line = item * nitems + b'\n'
4578
4577
4579 def benchmark():
4578 def benchmark():
4580 for i in pycompat.xrange(nlines):
4579 for i in pycompat.xrange(nlines):
4581 if batch_line:
4580 if batch_line:
4582 write(line)
4581 write(line)
4583 else:
4582 else:
4584 for i in pycompat.xrange(nitems):
4583 for i in pycompat.xrange(nitems):
4585 write(item)
4584 write(item)
4586 write(b'\n')
4585 write(b'\n')
4587 if flush_line:
4586 if flush_line:
4588 ui.flush()
4587 ui.flush()
4589 ui.flush()
4588 ui.flush()
4590
4589
4591 timer, fm = gettimer(ui, opts)
4590 timer, fm = gettimer(ui, opts)
4592 timer(benchmark)
4591 timer(benchmark)
4593 fm.end()
4592 fm.end()
4594
4593
4595
4594
4596 def uisetup(ui):
4595 def uisetup(ui):
4597 if util.safehasattr(cmdutil, b'openrevlog') and not util.safehasattr(
4596 if util.safehasattr(cmdutil, b'openrevlog') and not util.safehasattr(
4598 commands, b'debugrevlogopts'
4597 commands, b'debugrevlogopts'
4599 ):
4598 ):
4600 # for "historical portability":
4599 # for "historical portability":
4601 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
4600 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
4602 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
4601 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
4603 # openrevlog() should cause failure, because it has been
4602 # openrevlog() should cause failure, because it has been
4604 # available since 3.5 (or 49c583ca48c4).
4603 # available since 3.5 (or 49c583ca48c4).
4605 def openrevlog(orig, repo, cmd, file_, opts):
4604 def openrevlog(orig, repo, cmd, file_, opts):
4606 if opts.get(b'dir') and not util.safehasattr(repo, b'dirlog'):
4605 if opts.get(b'dir') and not util.safehasattr(repo, b'dirlog'):
4607 raise error.Abort(
4606 raise error.Abort(
4608 b"This version doesn't support --dir option",
4607 b"This version doesn't support --dir option",
4609 hint=b"use 3.5 or later",
4608 hint=b"use 3.5 or later",
4610 )
4609 )
4611 return orig(repo, cmd, file_, opts)
4610 return orig(repo, cmd, file_, opts)
4612
4611
4613 name = _sysstr(b'openrevlog')
4612 name = _sysstr(b'openrevlog')
4614 extensions.wrapfunction(cmdutil, name, openrevlog)
4613 extensions.wrapfunction(cmdutil, name, openrevlog)
4615
4614
4616
4615
4617 @command(
4616 @command(
4618 b'perf::progress|perfprogress',
4617 b'perf::progress|perfprogress',
4619 formatteropts
4618 formatteropts
4620 + [
4619 + [
4621 (b'', b'topic', b'topic', b'topic for progress messages'),
4620 (b'', b'topic', b'topic', b'topic for progress messages'),
4622 (b'c', b'total', 1000000, b'total value we are progressing to'),
4621 (b'c', b'total', 1000000, b'total value we are progressing to'),
4623 ],
4622 ],
4624 norepo=True,
4623 norepo=True,
4625 )
4624 )
4626 def perfprogress(ui, topic=None, total=None, **opts):
4625 def perfprogress(ui, topic=None, total=None, **opts):
4627 """printing of progress bars"""
4626 """printing of progress bars"""
4628 opts = _byteskwargs(opts)
4627 opts = _byteskwargs(opts)
4629
4628
4630 timer, fm = gettimer(ui, opts)
4629 timer, fm = gettimer(ui, opts)
4631
4630
4632 def doprogress():
4631 def doprogress():
4633 with ui.makeprogress(topic, total=total) as progress:
4632 with ui.makeprogress(topic, total=total) as progress:
4634 for i in _xrange(total):
4633 for i in _xrange(total):
4635 progress.increment()
4634 progress.increment()
4636
4635
4637 timer(doprogress)
4636 timer(doprogress)
4638 fm.end()
4637 fm.end()
@@ -1,2400 +1,2402 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 A "phabstatus" view for :hg:`show` is also provided; it displays status
14 A "phabstatus" view for :hg:`show` is also provided; it displays status
15 information of Phabricator differentials associated with unfinished
15 information of Phabricator differentials associated with unfinished
16 changesets.
16 changesets.
17
17
18 By default, Phabricator requires ``Test Plan`` which might prevent some
18 By default, Phabricator requires ``Test Plan`` which might prevent some
19 changeset from being sent. The requirement could be disabled by changing
19 changeset from being sent. The requirement could be disabled by changing
20 ``differential.require-test-plan-field`` config server side.
20 ``differential.require-test-plan-field`` config server side.
21
21
22 Config::
22 Config::
23
23
24 [phabricator]
24 [phabricator]
25 # Phabricator URL
25 # Phabricator URL
26 url = https://phab.example.com/
26 url = https://phab.example.com/
27
27
28 # Repo callsign. If a repo has a URL https://$HOST/diffusion/FOO, then its
28 # Repo callsign. If a repo has a URL https://$HOST/diffusion/FOO, then its
29 # callsign is "FOO".
29 # callsign is "FOO".
30 callsign = FOO
30 callsign = FOO
31
31
32 # curl command to use. If not set (default), use builtin HTTP library to
32 # curl command to use. If not set (default), use builtin HTTP library to
33 # communicate. If set, use the specified curl command. This could be useful
33 # communicate. If set, use the specified curl command. This could be useful
34 # if you need to specify advanced options that is not easily supported by
34 # if you need to specify advanced options that is not easily supported by
35 # the internal library.
35 # the internal library.
36 curlcmd = curl --connect-timeout 2 --retry 3 --silent
36 curlcmd = curl --connect-timeout 2 --retry 3 --silent
37
37
38 # retry failed command N time (default 0). Useful when using the extension
38 # retry failed command N time (default 0). Useful when using the extension
39 # over flakly connection.
39 # over flakly connection.
40 #
40 #
41 # We wait `retry.interval` between each retry, in seconds.
41 # We wait `retry.interval` between each retry, in seconds.
42 # (default 1 second).
42 # (default 1 second).
43 retry = 3
43 retry = 3
44 retry.interval = 10
44 retry.interval = 10
45
45
46 # the retry option can combine well with the http.timeout one.
46 # the retry option can combine well with the http.timeout one.
47 #
47 #
48 # For example to give up on http request after 20 seconds:
48 # For example to give up on http request after 20 seconds:
49 [http]
49 [http]
50 timeout=20
50 timeout=20
51
51
52 [auth]
52 [auth]
53 example.schemes = https
53 example.schemes = https
54 example.prefix = phab.example.com
54 example.prefix = phab.example.com
55
55
56 # API token. Get it from https://$HOST/conduit/login/
56 # API token. Get it from https://$HOST/conduit/login/
57 example.phabtoken = cli-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
57 example.phabtoken = cli-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
58 """
58 """
59
59
60
60
61 import base64
61 import base64
62 import contextlib
62 import contextlib
63 import hashlib
63 import hashlib
64 import io
64 import io
65 import itertools
65 import itertools
66 import json
66 import json
67 import mimetypes
67 import mimetypes
68 import operator
68 import operator
69 import re
69 import re
70 import time
70 import time
71
71
72 from mercurial.node import bin, short
72 from mercurial.node import bin, short
73 from mercurial.i18n import _
73 from mercurial.i18n import _
74 from mercurial.thirdparty import attr
74 from mercurial.thirdparty import attr
75 from mercurial import (
75 from mercurial import (
76 cmdutil,
76 cmdutil,
77 context,
77 context,
78 copies,
78 copies,
79 encoding,
79 encoding,
80 error,
80 error,
81 exthelper,
81 exthelper,
82 graphmod,
82 graphmod,
83 httpconnection as httpconnectionmod,
83 httpconnection as httpconnectionmod,
84 localrepo,
84 localrepo,
85 logcmdutil,
85 logcmdutil,
86 match,
86 match,
87 mdiff,
87 mdiff,
88 obsutil,
88 obsutil,
89 parser,
89 parser,
90 patch,
90 patch,
91 phases,
91 phases,
92 pycompat,
92 pycompat,
93 rewriteutil,
93 rewriteutil,
94 scmutil,
94 scmutil,
95 smartset,
95 smartset,
96 tags,
96 tags,
97 templatefilters,
97 templatefilters,
98 templateutil,
98 templateutil,
99 url as urlmod,
99 url as urlmod,
100 util,
100 util,
101 )
101 )
102 from mercurial.utils import (
102 from mercurial.utils import (
103 procutil,
103 procutil,
104 stringutil,
104 stringutil,
105 urlutil,
105 urlutil,
106 )
106 )
107 from . import show
107 from . import show
108
108
109
109
110 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
110 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
111 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
111 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
112 # be specifying the version(s) of Mercurial they are tested with, or
112 # be specifying the version(s) of Mercurial they are tested with, or
113 # leave the attribute unspecified.
113 # leave the attribute unspecified.
114 testedwith = b'ships-with-hg-core'
114 testedwith = b'ships-with-hg-core'
115
115
116 eh = exthelper.exthelper()
116 eh = exthelper.exthelper()
117
117
118 cmdtable = eh.cmdtable
118 cmdtable = eh.cmdtable
119 command = eh.command
119 command = eh.command
120 configtable = eh.configtable
120 configtable = eh.configtable
121 templatekeyword = eh.templatekeyword
121 templatekeyword = eh.templatekeyword
122 uisetup = eh.finaluisetup
122 uisetup = eh.finaluisetup
123
123
124 # developer config: phabricator.batchsize
124 # developer config: phabricator.batchsize
125 eh.configitem(
125 eh.configitem(
126 b'phabricator',
126 b'phabricator',
127 b'batchsize',
127 b'batchsize',
128 default=12,
128 default=12,
129 )
129 )
130 eh.configitem(
130 eh.configitem(
131 b'phabricator',
131 b'phabricator',
132 b'callsign',
132 b'callsign',
133 default=None,
133 default=None,
134 )
134 )
135 eh.configitem(
135 eh.configitem(
136 b'phabricator',
136 b'phabricator',
137 b'curlcmd',
137 b'curlcmd',
138 default=None,
138 default=None,
139 )
139 )
140 # developer config: phabricator.debug
140 # developer config: phabricator.debug
141 eh.configitem(
141 eh.configitem(
142 b'phabricator',
142 b'phabricator',
143 b'debug',
143 b'debug',
144 default=False,
144 default=False,
145 )
145 )
146 # developer config: phabricator.repophid
146 # developer config: phabricator.repophid
147 eh.configitem(
147 eh.configitem(
148 b'phabricator',
148 b'phabricator',
149 b'repophid',
149 b'repophid',
150 default=None,
150 default=None,
151 )
151 )
152 eh.configitem(
152 eh.configitem(
153 b'phabricator',
153 b'phabricator',
154 b'retry',
154 b'retry',
155 default=0,
155 default=0,
156 )
156 )
157 eh.configitem(
157 eh.configitem(
158 b'phabricator',
158 b'phabricator',
159 b'retry.interval',
159 b'retry.interval',
160 default=1,
160 default=1,
161 )
161 )
162 eh.configitem(
162 eh.configitem(
163 b'phabricator',
163 b'phabricator',
164 b'url',
164 b'url',
165 default=None,
165 default=None,
166 )
166 )
167 eh.configitem(
167 eh.configitem(
168 b'phabsend',
168 b'phabsend',
169 b'confirm',
169 b'confirm',
170 default=False,
170 default=False,
171 )
171 )
172 eh.configitem(
172 eh.configitem(
173 b'phabimport',
173 b'phabimport',
174 b'secret',
174 b'secret',
175 default=False,
175 default=False,
176 )
176 )
177 eh.configitem(
177 eh.configitem(
178 b'phabimport',
178 b'phabimport',
179 b'obsolete',
179 b'obsolete',
180 default=False,
180 default=False,
181 )
181 )
182
182
183 colortable = {
183 colortable = {
184 b'phabricator.action.created': b'green',
184 b'phabricator.action.created': b'green',
185 b'phabricator.action.skipped': b'magenta',
185 b'phabricator.action.skipped': b'magenta',
186 b'phabricator.action.updated': b'magenta',
186 b'phabricator.action.updated': b'magenta',
187 b'phabricator.drev': b'bold',
187 b'phabricator.drev': b'bold',
188 b'phabricator.status.abandoned': b'magenta dim',
188 b'phabricator.status.abandoned': b'magenta dim',
189 b'phabricator.status.accepted': b'green bold',
189 b'phabricator.status.accepted': b'green bold',
190 b'phabricator.status.closed': b'green',
190 b'phabricator.status.closed': b'green',
191 b'phabricator.status.needsreview': b'yellow',
191 b'phabricator.status.needsreview': b'yellow',
192 b'phabricator.status.needsrevision': b'red',
192 b'phabricator.status.needsrevision': b'red',
193 b'phabricator.status.changesplanned': b'red',
193 b'phabricator.status.changesplanned': b'red',
194 }
194 }
195
195
196 _VCR_FLAGS = [
196 _VCR_FLAGS = [
197 (
197 (
198 b'',
198 b'',
199 b'test-vcr',
199 b'test-vcr',
200 b'',
200 b'',
201 _(
201 _(
202 b'Path to a vcr file. If nonexistent, will record a new vcr transcript'
202 b'Path to a vcr file. If nonexistent, will record a new vcr transcript'
203 b', otherwise will mock all http requests using the specified vcr file.'
203 b', otherwise will mock all http requests using the specified vcr file.'
204 b' (ADVANCED)'
204 b' (ADVANCED)'
205 ),
205 ),
206 ),
206 ),
207 ]
207 ]
208
208
209
209
210 @eh.wrapfunction(localrepo, "loadhgrc")
210 @eh.wrapfunction(localrepo, "loadhgrc")
211 def _loadhgrc(orig, ui, wdirvfs, hgvfs, requirements, *args, **opts):
211 def _loadhgrc(orig, ui, wdirvfs, hgvfs, requirements, *args, **opts):
212 """Load ``.arcconfig`` content into a ui instance on repository open."""
212 """Load ``.arcconfig`` content into a ui instance on repository open."""
213 result = False
213 result = False
214 arcconfig = {}
214 arcconfig = {}
215
215
216 try:
216 try:
217 # json.loads only accepts bytes from 3.6+
217 # json.loads only accepts bytes from 3.6+
218 rawparams = encoding.unifromlocal(wdirvfs.read(b".arcconfig"))
218 rawparams = encoding.unifromlocal(wdirvfs.read(b".arcconfig"))
219 # json.loads only returns unicode strings
219 # json.loads only returns unicode strings
220 arcconfig = pycompat.rapply(
220 arcconfig = pycompat.rapply(
221 lambda x: encoding.unitolocal(x) if isinstance(x, str) else x,
221 lambda x: encoding.unitolocal(x) if isinstance(x, str) else x,
222 pycompat.json_loads(rawparams),
222 pycompat.json_loads(rawparams),
223 )
223 )
224
224
225 result = True
225 result = True
226 except ValueError:
226 except ValueError:
227 ui.warn(_(b"invalid JSON in %s\n") % wdirvfs.join(b".arcconfig"))
227 ui.warn(_(b"invalid JSON in %s\n") % wdirvfs.join(b".arcconfig"))
228 except IOError:
228 except IOError:
229 pass
229 pass
230
230
231 cfg = util.sortdict()
231 cfg = util.sortdict()
232
232
233 if b"repository.callsign" in arcconfig:
233 if b"repository.callsign" in arcconfig:
234 cfg[(b"phabricator", b"callsign")] = arcconfig[b"repository.callsign"]
234 cfg[(b"phabricator", b"callsign")] = arcconfig[b"repository.callsign"]
235
235
236 if b"phabricator.uri" in arcconfig:
236 if b"phabricator.uri" in arcconfig:
237 cfg[(b"phabricator", b"url")] = arcconfig[b"phabricator.uri"]
237 cfg[(b"phabricator", b"url")] = arcconfig[b"phabricator.uri"]
238
238
239 if cfg:
239 if cfg:
240 ui.applyconfig(cfg, source=wdirvfs.join(b".arcconfig"))
240 ui.applyconfig(cfg, source=wdirvfs.join(b".arcconfig"))
241
241
242 return (
242 return (
243 orig(ui, wdirvfs, hgvfs, requirements, *args, **opts) or result
243 orig(ui, wdirvfs, hgvfs, requirements, *args, **opts) or result
244 ) # Load .hg/hgrc
244 ) # Load .hg/hgrc
245
245
246
246
247 def vcrcommand(name, flags, spec, helpcategory=None, optionalrepo=False):
247 def vcrcommand(name, flags, spec, helpcategory=None, optionalrepo=False):
248 fullflags = flags + _VCR_FLAGS
248 fullflags = flags + _VCR_FLAGS
249
249
250 def hgmatcher(r1, r2):
250 def hgmatcher(r1, r2):
251 if r1.uri != r2.uri or r1.method != r2.method:
251 if r1.uri != r2.uri or r1.method != r2.method:
252 return False
252 return False
253 r1params = util.urlreq.parseqs(r1.body)
253 r1params = util.urlreq.parseqs(r1.body)
254 r2params = util.urlreq.parseqs(r2.body)
254 r2params = util.urlreq.parseqs(r2.body)
255 for key in r1params:
255 for key in r1params:
256 if key not in r2params:
256 if key not in r2params:
257 return False
257 return False
258 value = r1params[key][0]
258 value = r1params[key][0]
259 # we want to compare json payloads without worrying about ordering
259 # we want to compare json payloads without worrying about ordering
260 if value.startswith(b'{') and value.endswith(b'}'):
260 if value.startswith(b'{') and value.endswith(b'}'):
261 r1json = pycompat.json_loads(value)
261 r1json = pycompat.json_loads(value)
262 r2json = pycompat.json_loads(r2params[key][0])
262 r2json = pycompat.json_loads(r2params[key][0])
263 if r1json != r2json:
263 if r1json != r2json:
264 return False
264 return False
265 elif r2params[key][0] != value:
265 elif r2params[key][0] != value:
266 return False
266 return False
267 return True
267 return True
268
268
269 def sanitiserequest(request):
269 def sanitiserequest(request):
270 request.body = re.sub(
270 request.body = re.sub(
271 br'cli-[a-z0-9]+', br'cli-hahayouwish', request.body
271 br'cli-[a-z0-9]+', br'cli-hahayouwish', request.body
272 )
272 )
273 return request
273 return request
274
274
275 def sanitiseresponse(response):
275 def sanitiseresponse(response):
276 if 'set-cookie' in response['headers']:
276 if 'set-cookie' in response['headers']:
277 del response['headers']['set-cookie']
277 del response['headers']['set-cookie']
278 return response
278 return response
279
279
280 def decorate(fn):
280 def decorate(fn):
281 def inner(*args, **kwargs):
281 def inner(*args, **kwargs):
282 vcr = kwargs.pop('test_vcr')
282 vcr = kwargs.pop('test_vcr')
283 if vcr:
283 if vcr:
284 cassette = pycompat.fsdecode(vcr)
284 cassette = pycompat.fsdecode(vcr)
285 import hgdemandimport
285 import hgdemandimport
286
286
287 with hgdemandimport.deactivated():
287 with hgdemandimport.deactivated():
288 # pytype: disable=import-error
288 # pytype: disable=import-error
289 import vcr as vcrmod
289 import vcr as vcrmod
290 import vcr.stubs as stubs
290 import vcr.stubs as stubs
291
291
292 # pytype: enable=import-error
292 # pytype: enable=import-error
293
293
294 vcr = vcrmod.VCR(
294 vcr = vcrmod.VCR(
295 serializer='json',
295 serializer='json',
296 before_record_request=sanitiserequest,
296 before_record_request=sanitiserequest,
297 before_record_response=sanitiseresponse,
297 before_record_response=sanitiseresponse,
298 custom_patches=[
298 custom_patches=[
299 (
299 (
300 urlmod,
300 urlmod,
301 'httpconnection',
301 'httpconnection',
302 stubs.VCRHTTPConnection,
302 stubs.VCRHTTPConnection,
303 ),
303 ),
304 (
304 (
305 urlmod,
305 urlmod,
306 'httpsconnection',
306 'httpsconnection',
307 stubs.VCRHTTPSConnection,
307 stubs.VCRHTTPSConnection,
308 ),
308 ),
309 ],
309 ],
310 )
310 )
311 vcr.register_matcher('hgmatcher', hgmatcher)
311 vcr.register_matcher('hgmatcher', hgmatcher)
312 with vcr.use_cassette(cassette, match_on=['hgmatcher']):
312 with vcr.use_cassette(cassette, match_on=['hgmatcher']):
313 return fn(*args, **kwargs)
313 return fn(*args, **kwargs)
314 return fn(*args, **kwargs)
314 return fn(*args, **kwargs)
315
315
316 cmd = util.checksignature(inner, depth=2)
316 cmd = util.checksignature(inner, depth=2)
317 cmd.__name__ = fn.__name__
317 cmd.__name__ = fn.__name__
318 cmd.__doc__ = fn.__doc__
318 cmd.__doc__ = fn.__doc__
319
319
320 return command(
320 return command(
321 name,
321 name,
322 fullflags,
322 fullflags,
323 spec,
323 spec,
324 helpcategory=helpcategory,
324 helpcategory=helpcategory,
325 optionalrepo=optionalrepo,
325 optionalrepo=optionalrepo,
326 )(cmd)
326 )(cmd)
327
327
328 return decorate
328 return decorate
329
329
330
330
331 def _debug(ui, *msg, **opts):
331 def _debug(ui, *msg, **opts):
332 """write debug output for Phabricator if ``phabricator.debug`` is set
332 """write debug output for Phabricator if ``phabricator.debug`` is set
333
333
334 Specifically, this avoids dumping Conduit and HTTP auth chatter that is
334 Specifically, this avoids dumping Conduit and HTTP auth chatter that is
335 printed with the --debug argument.
335 printed with the --debug argument.
336 """
336 """
337 if ui.configbool(b"phabricator", b"debug"):
337 if ui.configbool(b"phabricator", b"debug"):
338 flag = ui.debugflag
338 flag = ui.debugflag
339 try:
339 try:
340 ui.debugflag = True
340 ui.debugflag = True
341 ui.write(*msg, **opts)
341 ui.write(*msg, **opts)
342 finally:
342 finally:
343 ui.debugflag = flag
343 ui.debugflag = flag
344
344
345
345
346 def urlencodenested(params):
346 def urlencodenested(params):
347 """like urlencode, but works with nested parameters.
347 """like urlencode, but works with nested parameters.
348
348
349 For example, if params is {'a': ['b', 'c'], 'd': {'e': 'f'}}, it will be
349 For example, if params is {'a': ['b', 'c'], 'd': {'e': 'f'}}, it will be
350 flattened to {'a[0]': 'b', 'a[1]': 'c', 'd[e]': 'f'} and then passed to
350 flattened to {'a[0]': 'b', 'a[1]': 'c', 'd[e]': 'f'} and then passed to
351 urlencode. Note: the encoding is consistent with PHP's http_build_query.
351 urlencode. Note: the encoding is consistent with PHP's http_build_query.
352 """
352 """
353 flatparams = util.sortdict()
353 flatparams = util.sortdict()
354
354
355 def process(prefix: bytes, obj):
355 def process(prefix: bytes, obj):
356 if isinstance(obj, bool):
356 if isinstance(obj, bool):
357 obj = {True: b'true', False: b'false'}[obj] # Python -> PHP form
357 obj = {True: b'true', False: b'false'}[obj] # Python -> PHP form
358 lister = lambda l: [(b'%d' % k, v) for k, v in enumerate(l)]
358 lister = lambda l: [(b'%d' % k, v) for k, v in enumerate(l)]
359 # .items() will only be called for a dict type
359 # .items() will only be called for a dict type
360 # pytype: disable=attribute-error
360 # pytype: disable=attribute-error
361 items = {list: lister, dict: lambda x: x.items()}.get(type(obj))
361 items = {list: lister, dict: lambda x: x.items()}.get(type(obj))
362 # pytype: enable=attribute-error
362 # pytype: enable=attribute-error
363 if items is None:
363 if items is None:
364 flatparams[prefix] = obj
364 flatparams[prefix] = obj
365 else:
365 else:
366 for k, v in items(obj):
366 for k, v in items(obj):
367 if prefix:
367 if prefix:
368 process(b'%s[%s]' % (prefix, k), v)
368 process(b'%s[%s]' % (prefix, k), v)
369 else:
369 else:
370 process(k, v)
370 process(k, v)
371
371
372 process(b'', params)
372 process(b'', params)
373 return urlutil.urlreq.urlencode(flatparams)
373 return urlutil.urlreq.urlencode(flatparams)
374
374
375
375
376 def readurltoken(ui):
376 def readurltoken(ui):
377 """return conduit url, token and make sure they exist
377 """return conduit url, token and make sure they exist
378
378
379 Currently read from [auth] config section. In the future, it might
379 Currently read from [auth] config section. In the future, it might
380 make sense to read from .arcconfig and .arcrc as well.
380 make sense to read from .arcconfig and .arcrc as well.
381 """
381 """
382 url = ui.config(b'phabricator', b'url')
382 url = ui.config(b'phabricator', b'url')
383 if not url:
383 if not url:
384 raise error.Abort(
384 raise error.Abort(
385 _(b'config %s.%s is required') % (b'phabricator', b'url')
385 _(b'config %s.%s is required') % (b'phabricator', b'url')
386 )
386 )
387
387
388 res = httpconnectionmod.readauthforuri(ui, url, urlutil.url(url).user)
388 res = httpconnectionmod.readauthforuri(ui, url, urlutil.url(url).user)
389 token = None
389 token = None
390
390
391 if res:
391 if res:
392 group, auth = res
392 group, auth = res
393
393
394 ui.debug(b"using auth.%s.* for authentication\n" % group)
394 ui.debug(b"using auth.%s.* for authentication\n" % group)
395
395
396 token = auth.get(b'phabtoken')
396 token = auth.get(b'phabtoken')
397
397
398 if not token:
398 if not token:
399 raise error.Abort(
399 raise error.Abort(
400 _(b'Can\'t find conduit token associated to %s') % (url,)
400 _(b'Can\'t find conduit token associated to %s') % (url,)
401 )
401 )
402
402
403 return url, token
403 return url, token
404
404
405
405
406 def callconduit(ui, name, params):
406 def callconduit(ui, name, params):
407 """call Conduit API, params is a dict. return json.loads result, or None"""
407 """call Conduit API, params is a dict. return json.loads result, or None"""
408 host, token = readurltoken(ui)
408 host, token = readurltoken(ui)
409 url, authinfo = urlutil.url(b'/'.join([host, b'api', name])).authinfo()
409 url, authinfo = urlutil.url(b'/'.join([host, b'api', name])).authinfo()
410 ui.debug(b'Conduit Call: %s %s\n' % (url, pycompat.byterepr(params)))
410 ui.debug(b'Conduit Call: %s %s\n' % (url, pycompat.byterepr(params)))
411 params = params.copy()
411 params = params.copy()
412 params[b'__conduit__'] = {
412 params[b'__conduit__'] = {
413 b'token': token,
413 b'token': token,
414 }
414 }
415 rawdata = {
415 rawdata = {
416 b'params': templatefilters.json(params),
416 b'params': templatefilters.json(params),
417 b'output': b'json',
417 b'output': b'json',
418 b'__conduit__': 1,
418 b'__conduit__': 1,
419 }
419 }
420 data = urlencodenested(rawdata)
420 data = urlencodenested(rawdata)
421 curlcmd = ui.config(b'phabricator', b'curlcmd')
421 curlcmd = ui.config(b'phabricator', b'curlcmd')
422 if curlcmd:
422 if curlcmd:
423 sin, sout = procutil.popen2(
423 sin, sout = procutil.popen2(
424 b'%s -d @- %s' % (curlcmd, procutil.shellquote(url))
424 b'%s -d @- %s' % (curlcmd, procutil.shellquote(url))
425 )
425 )
426 sin.write(data)
426 sin.write(data)
427 sin.close()
427 sin.close()
428 body = sout.read()
428 body = sout.read()
429 else:
429 else:
430 urlopener = urlmod.opener(ui, authinfo)
430 urlopener = urlmod.opener(ui, authinfo)
431 request = util.urlreq.request(pycompat.strurl(url), data=data)
431 request = util.urlreq.request(pycompat.strurl(url), data=data)
432 max_try = ui.configint(b'phabricator', b'retry') + 1
432 max_try = ui.configint(b'phabricator', b'retry') + 1
433 timeout = ui.configwith(float, b'http', b'timeout')
433 timeout = ui.configwith(float, b'http', b'timeout')
434 for try_count in range(max_try):
434 for try_count in range(max_try):
435 try:
435 try:
436 with contextlib.closing(
436 with contextlib.closing(
437 urlopener.open(request, timeout=timeout)
437 urlopener.open(request, timeout=timeout)
438 ) as rsp:
438 ) as rsp:
439 body = rsp.read()
439 body = rsp.read()
440 break
440 break
441 except util.urlerr.urlerror as err:
441 except util.urlerr.urlerror as err:
442 if try_count == max_try - 1:
442 if try_count == max_try - 1:
443 raise
443 raise
444 ui.debug(
444 ui.debug(
445 b'Conduit Request failed (try %d/%d): %r\n'
445 b'Conduit Request failed (try %d/%d): %r\n'
446 % (try_count + 1, max_try, err)
446 % (try_count + 1, max_try, err)
447 )
447 )
448 # failing request might come from overloaded server
448 # failing request might come from overloaded server
449 retry_interval = ui.configint(b'phabricator', b'retry.interval')
449 retry_interval = ui.configint(b'phabricator', b'retry.interval')
450 time.sleep(retry_interval)
450 time.sleep(retry_interval)
451 ui.debug(b'Conduit Response: %s\n' % body)
451 ui.debug(b'Conduit Response: %s\n' % body)
452 parsed = pycompat.rapply(
452 parsed = pycompat.rapply(
453 lambda x: encoding.unitolocal(x) if isinstance(x, str) else x,
453 lambda x: encoding.unitolocal(x) if isinstance(x, str) else x,
454 # json.loads only accepts bytes from py3.6+
454 # json.loads only accepts bytes from py3.6+
455 pycompat.json_loads(encoding.unifromlocal(body)),
455 pycompat.json_loads(encoding.unifromlocal(body)),
456 )
456 )
457 if parsed.get(b'error_code'):
457 if parsed.get(b'error_code'):
458 msg = _(b'Conduit Error (%s): %s') % (
458 msg = _(b'Conduit Error (%s): %s') % (
459 parsed[b'error_code'],
459 parsed[b'error_code'],
460 parsed[b'error_info'],
460 parsed[b'error_info'],
461 )
461 )
462 raise error.Abort(msg)
462 raise error.Abort(msg)
463 return parsed[b'result']
463 return parsed[b'result']
464
464
465
465
466 @vcrcommand(b'debugcallconduit', [], _(b'METHOD'), optionalrepo=True)
466 @vcrcommand(b'debugcallconduit', [], _(b'METHOD'), optionalrepo=True)
467 def debugcallconduit(ui, repo, name):
467 def debugcallconduit(ui, repo, name):
468 """call Conduit API
468 """call Conduit API
469
469
470 Call parameters are read from stdin as a JSON blob. Result will be written
470 Call parameters are read from stdin as a JSON blob. Result will be written
471 to stdout as a JSON blob.
471 to stdout as a JSON blob.
472 """
472 """
473 # json.loads only accepts bytes from 3.6+
473 # json.loads only accepts bytes from 3.6+
474 rawparams = encoding.unifromlocal(ui.fin.read())
474 rawparams = encoding.unifromlocal(ui.fin.read())
475 # json.loads only returns unicode strings
475 # json.loads only returns unicode strings
476 params = pycompat.rapply(
476 params = pycompat.rapply(
477 lambda x: encoding.unitolocal(x) if isinstance(x, str) else x,
477 lambda x: encoding.unitolocal(x) if isinstance(x, str) else x,
478 pycompat.json_loads(rawparams),
478 pycompat.json_loads(rawparams),
479 )
479 )
480 # json.dumps only accepts unicode strings
480 # json.dumps only accepts unicode strings
481 result = pycompat.rapply(
481 result = pycompat.rapply(
482 lambda x: encoding.unifromlocal(x) if isinstance(x, bytes) else x,
482 lambda x: encoding.unifromlocal(x) if isinstance(x, bytes) else x,
483 callconduit(ui, name, params),
483 callconduit(ui, name, params),
484 )
484 )
485 s = json.dumps(result, sort_keys=True, indent=2, separators=(u',', u': '))
485 s = json.dumps(result, sort_keys=True, indent=2, separators=(u',', u': '))
486 ui.write(b'%s\n' % encoding.unitolocal(s))
486 ui.write(b'%s\n' % encoding.unitolocal(s))
487
487
488
488
489 def getrepophid(repo):
489 def getrepophid(repo):
490 """given callsign, return repository PHID or None"""
490 """given callsign, return repository PHID or None"""
491 # developer config: phabricator.repophid
491 # developer config: phabricator.repophid
492 repophid = repo.ui.config(b'phabricator', b'repophid')
492 repophid = repo.ui.config(b'phabricator', b'repophid')
493 if repophid:
493 if repophid:
494 return repophid
494 return repophid
495 callsign = repo.ui.config(b'phabricator', b'callsign')
495 callsign = repo.ui.config(b'phabricator', b'callsign')
496 if not callsign:
496 if not callsign:
497 return None
497 return None
498 query = callconduit(
498 query = callconduit(
499 repo.ui,
499 repo.ui,
500 b'diffusion.repository.search',
500 b'diffusion.repository.search',
501 {b'constraints': {b'callsigns': [callsign]}},
501 {b'constraints': {b'callsigns': [callsign]}},
502 )
502 )
503 if len(query[b'data']) == 0:
503 if len(query[b'data']) == 0:
504 return None
504 return None
505 repophid = query[b'data'][0][b'phid']
505 repophid = query[b'data'][0][b'phid']
506 repo.ui.setconfig(b'phabricator', b'repophid', repophid)
506 repo.ui.setconfig(b'phabricator', b'repophid', repophid)
507 return repophid
507 return repophid
508
508
509
509
510 _differentialrevisiontagre = re.compile(br'\AD([1-9][0-9]*)\Z')
510 _differentialrevisiontagre = re.compile(br'\AD([1-9][0-9]*)\Z')
511 _differentialrevisiondescre = re.compile(
511 _differentialrevisiondescre = re.compile(
512 br'^Differential Revision:\s*(?P<url>(?:.*)D(?P<id>[1-9][0-9]*))$', re.M
512 br'^Differential Revision:\s*(?P<url>(?:.*)D(?P<id>[1-9][0-9]*))$', re.M
513 )
513 )
514
514
515
515
516 def getoldnodedrevmap(repo, nodelist):
516 def getoldnodedrevmap(repo, nodelist):
517 """find previous nodes that has been sent to Phabricator
517 """find previous nodes that has been sent to Phabricator
518
518
519 return {node: (oldnode, Differential diff, Differential Revision ID)}
519 return {node: (oldnode, Differential diff, Differential Revision ID)}
520 for node in nodelist with known previous sent versions, or associated
520 for node in nodelist with known previous sent versions, or associated
521 Differential Revision IDs. ``oldnode`` and ``Differential diff`` could
521 Differential Revision IDs. ``oldnode`` and ``Differential diff`` could
522 be ``None``.
522 be ``None``.
523
523
524 Examines commit messages like "Differential Revision:" to get the
524 Examines commit messages like "Differential Revision:" to get the
525 association information.
525 association information.
526
526
527 If such commit message line is not found, examines all precursors and their
527 If such commit message line is not found, examines all precursors and their
528 tags. Tags with format like "D1234" are considered a match and the node
528 tags. Tags with format like "D1234" are considered a match and the node
529 with that tag, and the number after "D" (ex. 1234) will be returned.
529 with that tag, and the number after "D" (ex. 1234) will be returned.
530
530
531 The ``old node``, if not None, is guaranteed to be the last diff of
531 The ``old node``, if not None, is guaranteed to be the last diff of
532 corresponding Differential Revision, and exist in the repo.
532 corresponding Differential Revision, and exist in the repo.
533 """
533 """
534 unfi = repo.unfiltered()
534 unfi = repo.unfiltered()
535 has_node = unfi.changelog.index.has_node
535 has_node = unfi.changelog.index.has_node
536
536
537 result = {} # {node: (oldnode?, lastdiff?, drev)}
537 result = {} # {node: (oldnode?, lastdiff?, drev)}
538 # ordered for test stability when printing new -> old mapping below
538 # ordered for test stability when printing new -> old mapping below
539 toconfirm = util.sortdict() # {node: (force, {precnode}, drev)}
539 toconfirm = util.sortdict() # {node: (force, {precnode}, drev)}
540 for node in nodelist:
540 for node in nodelist:
541 ctx = unfi[node]
541 ctx = unfi[node]
542 # For tags like "D123", put them into "toconfirm" to verify later
542 # For tags like "D123", put them into "toconfirm" to verify later
543 precnodes = list(obsutil.allpredecessors(unfi.obsstore, [node]))
543 precnodes = list(obsutil.allpredecessors(unfi.obsstore, [node]))
544 for n in precnodes:
544 for n in precnodes:
545 if has_node(n):
545 if has_node(n):
546 for tag in unfi.nodetags(n):
546 for tag in unfi.nodetags(n):
547 m = _differentialrevisiontagre.match(tag)
547 m = _differentialrevisiontagre.match(tag)
548 if m:
548 if m:
549 toconfirm[node] = (0, set(precnodes), int(m.group(1)))
549 toconfirm[node] = (0, set(precnodes), int(m.group(1)))
550 break
550 break
551 else:
551 else:
552 continue # move to next predecessor
552 continue # move to next predecessor
553 break # found a tag, stop
553 break # found a tag, stop
554 else:
554 else:
555 # Check commit message
555 # Check commit message
556 m = _differentialrevisiondescre.search(ctx.description())
556 m = _differentialrevisiondescre.search(ctx.description())
557 if m:
557 if m:
558 toconfirm[node] = (1, set(precnodes), int(m.group('id')))
558 toconfirm[node] = (1, set(precnodes), int(m.group('id')))
559
559
560 # Double check if tags are genuine by collecting all old nodes from
560 # Double check if tags are genuine by collecting all old nodes from
561 # Phabricator, and expect precursors overlap with it.
561 # Phabricator, and expect precursors overlap with it.
562 if toconfirm:
562 if toconfirm:
563 drevs = [drev for force, precs, drev in toconfirm.values()]
563 drevs = [drev for force, precs, drev in toconfirm.values()]
564 alldiffs = callconduit(
564 alldiffs = callconduit(
565 unfi.ui, b'differential.querydiffs', {b'revisionIDs': drevs}
565 unfi.ui, b'differential.querydiffs', {b'revisionIDs': drevs}
566 )
566 )
567
567
568 def getnodes(d, precset):
568 def getnodes(d, precset):
569 # Ignore other nodes that were combined into the Differential
569 # Ignore other nodes that were combined into the Differential
570 # that aren't predecessors of the current local node.
570 # that aren't predecessors of the current local node.
571 return [n for n in getlocalcommits(d) if n in precset]
571 return [n for n in getlocalcommits(d) if n in precset]
572
572
573 for newnode, (force, precset, drev) in toconfirm.items():
573 for newnode, (force, precset, drev) in toconfirm.items():
574 diffs = [
574 diffs = [
575 d for d in alldiffs.values() if int(d[b'revisionID']) == drev
575 d for d in alldiffs.values() if int(d[b'revisionID']) == drev
576 ]
576 ]
577
577
578 # local predecessors known by Phabricator
578 # local predecessors known by Phabricator
579 phprecset = {n for d in diffs for n in getnodes(d, precset)}
579 phprecset = {n for d in diffs for n in getnodes(d, precset)}
580
580
581 # Ignore if precursors (Phabricator and local repo) do not overlap,
581 # Ignore if precursors (Phabricator and local repo) do not overlap,
582 # and force is not set (when commit message says nothing)
582 # and force is not set (when commit message says nothing)
583 if not force and not phprecset:
583 if not force and not phprecset:
584 tagname = b'D%d' % drev
584 tagname = b'D%d' % drev
585 tags.tag(
585 tags.tag(
586 repo,
586 repo,
587 tagname,
587 tagname,
588 repo.nullid,
588 repo.nullid,
589 message=None,
589 message=None,
590 user=None,
590 user=None,
591 date=None,
591 date=None,
592 local=True,
592 local=True,
593 )
593 )
594 unfi.ui.warn(
594 unfi.ui.warn(
595 _(
595 _(
596 b'D%d: local tag removed - does not match '
596 b'D%d: local tag removed - does not match '
597 b'Differential history\n'
597 b'Differential history\n'
598 )
598 )
599 % drev
599 % drev
600 )
600 )
601 continue
601 continue
602
602
603 # Find the last node using Phabricator metadata, and make sure it
603 # Find the last node using Phabricator metadata, and make sure it
604 # exists in the repo
604 # exists in the repo
605 oldnode = lastdiff = None
605 oldnode = lastdiff = None
606 if diffs:
606 if diffs:
607 lastdiff = max(diffs, key=lambda d: int(d[b'id']))
607 lastdiff = max(diffs, key=lambda d: int(d[b'id']))
608 oldnodes = getnodes(lastdiff, precset)
608 oldnodes = getnodes(lastdiff, precset)
609
609
610 _debug(
610 _debug(
611 unfi.ui,
611 unfi.ui,
612 b"%s mapped to old nodes %s\n"
612 b"%s mapped to old nodes %s\n"
613 % (
613 % (
614 short(newnode),
614 short(newnode),
615 stringutil.pprint([short(n) for n in sorted(oldnodes)]),
615 stringutil.pprint([short(n) for n in sorted(oldnodes)]),
616 ),
616 ),
617 )
617 )
618
618
619 # If this commit was the result of `hg fold` after submission,
619 # If this commit was the result of `hg fold` after submission,
620 # and now resubmitted with --fold, the easiest thing to do is
620 # and now resubmitted with --fold, the easiest thing to do is
621 # to leave the node clear. This only results in creating a new
621 # to leave the node clear. This only results in creating a new
622 # diff for the _same_ Differential Revision if this commit is
622 # diff for the _same_ Differential Revision if this commit is
623 # the first or last in the selected range. If we picked a node
623 # the first or last in the selected range. If we picked a node
624 # from the list instead, it would have to be the lowest if at
624 # from the list instead, it would have to be the lowest if at
625 # the beginning of the --fold range, or the highest at the end.
625 # the beginning of the --fold range, or the highest at the end.
626 # Otherwise, one or more of the nodes wouldn't be considered in
626 # Otherwise, one or more of the nodes wouldn't be considered in
627 # the diff, and the Differential wouldn't be properly updated.
627 # the diff, and the Differential wouldn't be properly updated.
628 # If this commit is the result of `hg split` in the same
628 # If this commit is the result of `hg split` in the same
629 # scenario, there is a single oldnode here (and multiple
629 # scenario, there is a single oldnode here (and multiple
630 # newnodes mapped to it). That makes it the same as the normal
630 # newnodes mapped to it). That makes it the same as the normal
631 # case, as the edges of the newnode range cleanly maps to one
631 # case, as the edges of the newnode range cleanly maps to one
632 # oldnode each.
632 # oldnode each.
633 if len(oldnodes) == 1:
633 if len(oldnodes) == 1:
634 oldnode = oldnodes[0]
634 oldnode = oldnodes[0]
635 if oldnode and not has_node(oldnode):
635 if oldnode and not has_node(oldnode):
636 oldnode = None
636 oldnode = None
637
637
638 result[newnode] = (oldnode, lastdiff, drev)
638 result[newnode] = (oldnode, lastdiff, drev)
639
639
640 return result
640 return result
641
641
642
642
643 def getdrevmap(repo, revs):
643 def getdrevmap(repo, revs):
644 """Return a dict mapping each rev in `revs` to their Differential Revision
644 """Return a dict mapping each rev in `revs` to their Differential Revision
645 ID or None.
645 ID or None.
646 """
646 """
647 result = {}
647 result = {}
648 for rev in revs:
648 for rev in revs:
649 result[rev] = None
649 result[rev] = None
650 ctx = repo[rev]
650 ctx = repo[rev]
651 # Check commit message
651 # Check commit message
652 m = _differentialrevisiondescre.search(ctx.description())
652 m = _differentialrevisiondescre.search(ctx.description())
653 if m:
653 if m:
654 result[rev] = int(m.group('id'))
654 result[rev] = int(m.group('id'))
655 continue
655 continue
656 # Check tags
656 # Check tags
657 for tag in repo.nodetags(ctx.node()):
657 for tag in repo.nodetags(ctx.node()):
658 m = _differentialrevisiontagre.match(tag)
658 m = _differentialrevisiontagre.match(tag)
659 if m:
659 if m:
660 result[rev] = int(m.group(1))
660 result[rev] = int(m.group(1))
661 break
661 break
662
662
663 return result
663 return result
664
664
665
665
666 def getdiff(basectx, ctx, diffopts):
666 def getdiff(basectx, ctx, diffopts):
667 """plain-text diff without header (user, commit message, etc)"""
667 """plain-text diff without header (user, commit message, etc)"""
668 output = util.stringio()
668 output = util.stringio()
669 for chunk, _label in patch.diffui(
669 for chunk, _label in patch.diffui(
670 ctx.repo(), basectx.p1().node(), ctx.node(), None, opts=diffopts
670 ctx.repo(), basectx.p1().node(), ctx.node(), None, opts=diffopts
671 ):
671 ):
672 output.write(chunk)
672 output.write(chunk)
673 return output.getvalue()
673 return output.getvalue()
674
674
675
675
676 class DiffChangeType:
676 class DiffChangeType:
677 ADD = 1
677 ADD = 1
678 CHANGE = 2
678 CHANGE = 2
679 DELETE = 3
679 DELETE = 3
680 MOVE_AWAY = 4
680 MOVE_AWAY = 4
681 COPY_AWAY = 5
681 COPY_AWAY = 5
682 MOVE_HERE = 6
682 MOVE_HERE = 6
683 COPY_HERE = 7
683 COPY_HERE = 7
684 MULTICOPY = 8
684 MULTICOPY = 8
685
685
686
686
687 class DiffFileType:
687 class DiffFileType:
688 TEXT = 1
688 TEXT = 1
689 IMAGE = 2
689 IMAGE = 2
690 BINARY = 3
690 BINARY = 3
691
691
692
692
693 @attr.s
693 @attr.s
694 class phabhunk(dict):
694 class phabhunk(dict):
695 """Represents a Differential hunk, which is owned by a Differential change"""
695 """Represents a Differential hunk, which is owned by a Differential change"""
696
696
697 oldOffset = attr.ib(default=0) # camelcase-required
697 oldOffset = attr.ib(default=0) # camelcase-required
698 oldLength = attr.ib(default=0) # camelcase-required
698 oldLength = attr.ib(default=0) # camelcase-required
699 newOffset = attr.ib(default=0) # camelcase-required
699 newOffset = attr.ib(default=0) # camelcase-required
700 newLength = attr.ib(default=0) # camelcase-required
700 newLength = attr.ib(default=0) # camelcase-required
701 corpus = attr.ib(default='')
701 corpus = attr.ib(default='')
702 # These get added to the phabchange's equivalents
702 # These get added to the phabchange's equivalents
703 addLines = attr.ib(default=0) # camelcase-required
703 addLines = attr.ib(default=0) # camelcase-required
704 delLines = attr.ib(default=0) # camelcase-required
704 delLines = attr.ib(default=0) # camelcase-required
705
705
706
706
707 @attr.s
707 @attr.s
708 class phabchange:
708 class phabchange:
709 """Represents a Differential change, owns Differential hunks and owned by a
709 """Represents a Differential change, owns Differential hunks and owned by a
710 Differential diff. Each one represents one file in a diff.
710 Differential diff. Each one represents one file in a diff.
711 """
711 """
712
712
713 currentPath = attr.ib(default=None) # camelcase-required
713 currentPath = attr.ib(default=None) # camelcase-required
714 oldPath = attr.ib(default=None) # camelcase-required
714 oldPath = attr.ib(default=None) # camelcase-required
715 awayPaths = attr.ib(default=attr.Factory(list)) # camelcase-required
715 awayPaths = attr.ib(default=attr.Factory(list)) # camelcase-required
716 metadata = attr.ib(default=attr.Factory(dict))
716 metadata = attr.ib(default=attr.Factory(dict))
717 oldProperties = attr.ib(default=attr.Factory(dict)) # camelcase-required
717 oldProperties = attr.ib(default=attr.Factory(dict)) # camelcase-required
718 newProperties = attr.ib(default=attr.Factory(dict)) # camelcase-required
718 newProperties = attr.ib(default=attr.Factory(dict)) # camelcase-required
719 type = attr.ib(default=DiffChangeType.CHANGE)
719 type = attr.ib(default=DiffChangeType.CHANGE)
720 fileType = attr.ib(default=DiffFileType.TEXT) # camelcase-required
720 fileType = attr.ib(default=DiffFileType.TEXT) # camelcase-required
721 commitHash = attr.ib(default=None) # camelcase-required
721 commitHash = attr.ib(default=None) # camelcase-required
722 addLines = attr.ib(default=0) # camelcase-required
722 addLines = attr.ib(default=0) # camelcase-required
723 delLines = attr.ib(default=0) # camelcase-required
723 delLines = attr.ib(default=0) # camelcase-required
724 hunks = attr.ib(default=attr.Factory(list))
724 hunks = attr.ib(default=attr.Factory(list))
725
725
726 def copynewmetadatatoold(self):
726 def copynewmetadatatoold(self):
727 for key in list(self.metadata.keys()):
727 for key in list(self.metadata.keys()):
728 newkey = key.replace(b'new:', b'old:')
728 newkey = key.replace(b'new:', b'old:')
729 self.metadata[newkey] = self.metadata[key]
729 self.metadata[newkey] = self.metadata[key]
730
730
731 def addoldmode(self, value):
731 def addoldmode(self, value):
732 self.oldProperties[b'unix:filemode'] = value
732 self.oldProperties[b'unix:filemode'] = value
733
733
734 def addnewmode(self, value):
734 def addnewmode(self, value):
735 self.newProperties[b'unix:filemode'] = value
735 self.newProperties[b'unix:filemode'] = value
736
736
737 def addhunk(self, hunk):
737 def addhunk(self, hunk):
738 if not isinstance(hunk, phabhunk):
738 if not isinstance(hunk, phabhunk):
739 raise error.Abort(b'phabchange.addhunk only takes phabhunks')
739 raise error.Abort(b'phabchange.addhunk only takes phabhunks')
740 self.hunks.append(pycompat.byteskwargs(attr.asdict(hunk)))
740 self.hunks.append(pycompat.byteskwargs(attr.asdict(hunk)))
741 # It's useful to include these stats since the Phab web UI shows them,
741 # It's useful to include these stats since the Phab web UI shows them,
742 # and uses them to estimate how large a change a Revision is. Also used
742 # and uses them to estimate how large a change a Revision is. Also used
743 # in email subjects for the [+++--] bit.
743 # in email subjects for the [+++--] bit.
744 self.addLines += hunk.addLines
744 self.addLines += hunk.addLines
745 self.delLines += hunk.delLines
745 self.delLines += hunk.delLines
746
746
747
747
748 @attr.s
748 @attr.s
749 class phabdiff:
749 class phabdiff:
750 """Represents a Differential diff, owns Differential changes. Corresponds
750 """Represents a Differential diff, owns Differential changes. Corresponds
751 to a commit.
751 to a commit.
752 """
752 """
753
753
754 # Doesn't seem to be any reason to send this (output of uname -n)
754 # Doesn't seem to be any reason to send this (output of uname -n)
755 sourceMachine = attr.ib(default=b'') # camelcase-required
755 sourceMachine = attr.ib(default=b'') # camelcase-required
756 sourcePath = attr.ib(default=b'/') # camelcase-required
756 sourcePath = attr.ib(default=b'/') # camelcase-required
757 sourceControlBaseRevision = attr.ib(default=b'0' * 40) # camelcase-required
757 sourceControlBaseRevision = attr.ib(default=b'0' * 40) # camelcase-required
758 sourceControlPath = attr.ib(default=b'/') # camelcase-required
758 sourceControlPath = attr.ib(default=b'/') # camelcase-required
759 sourceControlSystem = attr.ib(default=b'hg') # camelcase-required
759 sourceControlSystem = attr.ib(default=b'hg') # camelcase-required
760 branch = attr.ib(default=b'default')
760 branch = attr.ib(default=b'default')
761 bookmark = attr.ib(default=None)
761 bookmark = attr.ib(default=None)
762 creationMethod = attr.ib(default=b'phabsend') # camelcase-required
762 creationMethod = attr.ib(default=b'phabsend') # camelcase-required
763 lintStatus = attr.ib(default=b'none') # camelcase-required
763 lintStatus = attr.ib(default=b'none') # camelcase-required
764 unitStatus = attr.ib(default=b'none') # camelcase-required
764 unitStatus = attr.ib(default=b'none') # camelcase-required
765 changes = attr.ib(default=attr.Factory(dict))
765 changes = attr.ib(default=attr.Factory(dict))
766 repositoryPHID = attr.ib(default=None) # camelcase-required
766 repositoryPHID = attr.ib(default=None) # camelcase-required
767
767
768 def addchange(self, change):
768 def addchange(self, change):
769 if not isinstance(change, phabchange):
769 if not isinstance(change, phabchange):
770 raise error.Abort(b'phabdiff.addchange only takes phabchanges')
770 raise error.Abort(b'phabdiff.addchange only takes phabchanges')
771 self.changes[change.currentPath] = pycompat.byteskwargs(
771 self.changes[change.currentPath] = pycompat.byteskwargs(
772 attr.asdict(change)
772 attr.asdict(change)
773 )
773 )
774
774
775
775
776 def maketext(pchange, basectx, ctx, fname):
776 def maketext(pchange, basectx, ctx, fname):
777 """populate the phabchange for a text file"""
777 """populate the phabchange for a text file"""
778 repo = ctx.repo()
778 repo = ctx.repo()
779 fmatcher = match.exact([fname])
779 fmatcher = match.exact([fname])
780 diffopts = mdiff.diffopts(git=True, context=32767)
780 diffopts = mdiff.diffopts(git=True, context=32767)
781 _pfctx, _fctx, header, fhunks = next(
781 _pfctx, _fctx, header, fhunks = next(
782 patch.diffhunks(repo, basectx.p1(), ctx, fmatcher, opts=diffopts)
782 patch.diffhunks(repo, basectx.p1(), ctx, fmatcher, opts=diffopts)
783 )
783 )
784
784
785 for fhunk in fhunks:
785 for fhunk in fhunks:
786 (oldOffset, oldLength, newOffset, newLength), lines = fhunk
786 (oldOffset, oldLength, newOffset, newLength), lines = fhunk
787 corpus = b''.join(lines[1:])
787 corpus = b''.join(lines[1:])
788 shunk = list(header)
788 shunk = list(header)
789 shunk.extend(lines)
789 shunk.extend(lines)
790 _mf, _mt, addLines, delLines, _hb = patch.diffstatsum(
790 _mf, _mt, addLines, delLines, _hb = patch.diffstatsum(
791 patch.diffstatdata(util.iterlines(shunk))
791 patch.diffstatdata(util.iterlines(shunk))
792 )
792 )
793 pchange.addhunk(
793 pchange.addhunk(
794 phabhunk(
794 phabhunk(
795 oldOffset,
795 oldOffset,
796 oldLength,
796 oldLength,
797 newOffset,
797 newOffset,
798 newLength,
798 newLength,
799 corpus,
799 corpus,
800 addLines,
800 addLines,
801 delLines,
801 delLines,
802 )
802 )
803 )
803 )
804
804
805
805
806 def uploadchunks(fctx, fphid):
806 def uploadchunks(fctx, fphid):
807 """upload large binary files as separate chunks.
807 """upload large binary files as separate chunks.
808 Phab requests chunking over 8MiB, and splits into 4MiB chunks
808 Phab requests chunking over 8MiB, and splits into 4MiB chunks
809 """
809 """
810 ui = fctx.repo().ui
810 ui = fctx.repo().ui
811 chunks = callconduit(ui, b'file.querychunks', {b'filePHID': fphid})
811 chunks = callconduit(ui, b'file.querychunks', {b'filePHID': fphid})
812 with ui.makeprogress(
812 with ui.makeprogress(
813 _(b'uploading file chunks'), unit=_(b'chunks'), total=len(chunks)
813 _(b'uploading file chunks'), unit=_(b'chunks'), total=len(chunks)
814 ) as progress:
814 ) as progress:
815 for chunk in chunks:
815 for chunk in chunks:
816 progress.increment()
816 progress.increment()
817 if chunk[b'complete']:
817 if chunk[b'complete']:
818 continue
818 continue
819 bstart = int(chunk[b'byteStart'])
819 bstart = int(chunk[b'byteStart'])
820 bend = int(chunk[b'byteEnd'])
820 bend = int(chunk[b'byteEnd'])
821 callconduit(
821 callconduit(
822 ui,
822 ui,
823 b'file.uploadchunk',
823 b'file.uploadchunk',
824 {
824 {
825 b'filePHID': fphid,
825 b'filePHID': fphid,
826 b'byteStart': bstart,
826 b'byteStart': bstart,
827 b'data': base64.b64encode(fctx.data()[bstart:bend]),
827 b'data': base64.b64encode(fctx.data()[bstart:bend]),
828 b'dataEncoding': b'base64',
828 b'dataEncoding': b'base64',
829 },
829 },
830 )
830 )
831
831
832
832
833 def uploadfile(fctx):
833 def uploadfile(fctx):
834 """upload binary files to Phabricator"""
834 """upload binary files to Phabricator"""
835 repo = fctx.repo()
835 repo = fctx.repo()
836 ui = repo.ui
836 ui = repo.ui
837 fname = fctx.path()
837 fname = fctx.path()
838 size = fctx.size()
838 size = fctx.size()
839 fhash = pycompat.bytestr(hashlib.sha256(fctx.data()).hexdigest())
839 fhash = pycompat.bytestr(hashlib.sha256(fctx.data()).hexdigest())
840
840
841 # an allocate call is required first to see if an upload is even required
841 # an allocate call is required first to see if an upload is even required
842 # (Phab might already have it) and to determine if chunking is needed
842 # (Phab might already have it) and to determine if chunking is needed
843 allocateparams = {
843 allocateparams = {
844 b'name': fname,
844 b'name': fname,
845 b'contentLength': size,
845 b'contentLength': size,
846 b'contentHash': fhash,
846 b'contentHash': fhash,
847 }
847 }
848 filealloc = callconduit(ui, b'file.allocate', allocateparams)
848 filealloc = callconduit(ui, b'file.allocate', allocateparams)
849 fphid = filealloc[b'filePHID']
849 fphid = filealloc[b'filePHID']
850
850
851 if filealloc[b'upload']:
851 if filealloc[b'upload']:
852 ui.write(_(b'uploading %s\n') % bytes(fctx))
852 ui.write(_(b'uploading %s\n') % bytes(fctx))
853 if not fphid:
853 if not fphid:
854 uploadparams = {
854 uploadparams = {
855 b'name': fname,
855 b'name': fname,
856 b'data_base64': base64.b64encode(fctx.data()),
856 b'data_base64': base64.b64encode(fctx.data()),
857 }
857 }
858 fphid = callconduit(ui, b'file.upload', uploadparams)
858 fphid = callconduit(ui, b'file.upload', uploadparams)
859 else:
859 else:
860 uploadchunks(fctx, fphid)
860 uploadchunks(fctx, fphid)
861 else:
861 else:
862 ui.debug(b'server already has %s\n' % bytes(fctx))
862 ui.debug(b'server already has %s\n' % bytes(fctx))
863
863
864 if not fphid:
864 if not fphid:
865 raise error.Abort(b'Upload of %s failed.' % bytes(fctx))
865 raise error.Abort(b'Upload of %s failed.' % bytes(fctx))
866
866
867 return fphid
867 return fphid
868
868
869
869
870 def addoldbinary(pchange, oldfctx, fctx):
870 def addoldbinary(pchange, oldfctx, fctx):
871 """add the metadata for the previous version of a binary file to the
871 """add the metadata for the previous version of a binary file to the
872 phabchange for the new version
872 phabchange for the new version
873
873
874 ``oldfctx`` is the previous version of the file; ``fctx`` is the new
874 ``oldfctx`` is the previous version of the file; ``fctx`` is the new
875 version of the file, or None if the file is being removed.
875 version of the file, or None if the file is being removed.
876 """
876 """
877 if not fctx or fctx.cmp(oldfctx):
877 if not fctx or fctx.cmp(oldfctx):
878 # Files differ, add the old one
878 # Files differ, add the old one
879 pchange.metadata[b'old:file:size'] = oldfctx.size()
879 pchange.metadata[b'old:file:size'] = oldfctx.size()
880 mimeguess, _enc = mimetypes.guess_type(
880 mimeguess, _enc = mimetypes.guess_type(
881 encoding.unifromlocal(oldfctx.path())
881 encoding.unifromlocal(oldfctx.path())
882 )
882 )
883 if mimeguess:
883 if mimeguess:
884 pchange.metadata[b'old:file:mime-type'] = pycompat.bytestr(
884 pchange.metadata[b'old:file:mime-type'] = pycompat.bytestr(
885 mimeguess
885 mimeguess
886 )
886 )
887 fphid = uploadfile(oldfctx)
887 fphid = uploadfile(oldfctx)
888 pchange.metadata[b'old:binary-phid'] = fphid
888 pchange.metadata[b'old:binary-phid'] = fphid
889 else:
889 else:
890 # If it's left as IMAGE/BINARY web UI might try to display it
890 # If it's left as IMAGE/BINARY web UI might try to display it
891 pchange.fileType = DiffFileType.TEXT
891 pchange.fileType = DiffFileType.TEXT
892 pchange.copynewmetadatatoold()
892 pchange.copynewmetadatatoold()
893
893
894
894
895 def makebinary(pchange, fctx):
895 def makebinary(pchange, fctx):
896 """populate the phabchange for a binary file"""
896 """populate the phabchange for a binary file"""
897 pchange.fileType = DiffFileType.BINARY
897 pchange.fileType = DiffFileType.BINARY
898 fphid = uploadfile(fctx)
898 fphid = uploadfile(fctx)
899 pchange.metadata[b'new:binary-phid'] = fphid
899 pchange.metadata[b'new:binary-phid'] = fphid
900 pchange.metadata[b'new:file:size'] = fctx.size()
900 pchange.metadata[b'new:file:size'] = fctx.size()
901 mimeguess, _enc = mimetypes.guess_type(encoding.unifromlocal(fctx.path()))
901 mimeguess, _enc = mimetypes.guess_type(encoding.unifromlocal(fctx.path()))
902 if mimeguess:
902 if mimeguess:
903 mimeguess = pycompat.bytestr(mimeguess)
903 mimeguess = pycompat.bytestr(mimeguess)
904 pchange.metadata[b'new:file:mime-type'] = mimeguess
904 pchange.metadata[b'new:file:mime-type'] = mimeguess
905 if mimeguess.startswith(b'image/'):
905 if mimeguess.startswith(b'image/'):
906 pchange.fileType = DiffFileType.IMAGE
906 pchange.fileType = DiffFileType.IMAGE
907
907
908
908
909 # Copied from mercurial/patch.py
909 # Copied from mercurial/patch.py
910 gitmode = {b'l': b'120000', b'x': b'100755', b'': b'100644'}
910 gitmode = {b'l': b'120000', b'x': b'100755', b'': b'100644'}
911
911
912
912
913 def notutf8(fctx):
913 def notutf8(fctx):
914 """detect non-UTF-8 text files since Phabricator requires them to be marked
914 """detect non-UTF-8 text files since Phabricator requires them to be marked
915 as binary
915 as binary
916 """
916 """
917 try:
917 try:
918 fctx.data().decode('utf-8')
918 fctx.data().decode('utf-8')
919 return False
919 return False
920 except UnicodeDecodeError:
920 except UnicodeDecodeError:
921 fctx.repo().ui.write(
921 fctx.repo().ui.write(
922 _(b'file %s detected as non-UTF-8, marked as binary\n')
922 _(b'file %s detected as non-UTF-8, marked as binary\n')
923 % fctx.path()
923 % fctx.path()
924 )
924 )
925 return True
925 return True
926
926
927
927
928 def addremoved(pdiff, basectx, ctx, removed):
928 def addremoved(pdiff, basectx, ctx, removed):
929 """add removed files to the phabdiff. Shouldn't include moves"""
929 """add removed files to the phabdiff. Shouldn't include moves"""
930 for fname in removed:
930 for fname in removed:
931 pchange = phabchange(
931 pchange = phabchange(
932 currentPath=fname, oldPath=fname, type=DiffChangeType.DELETE
932 currentPath=fname, oldPath=fname, type=DiffChangeType.DELETE
933 )
933 )
934 oldfctx = basectx.p1()[fname]
934 oldfctx = basectx.p1()[fname]
935 pchange.addoldmode(gitmode[oldfctx.flags()])
935 pchange.addoldmode(gitmode[oldfctx.flags()])
936 if not (oldfctx.isbinary() or notutf8(oldfctx)):
936 if not (oldfctx.isbinary() or notutf8(oldfctx)):
937 maketext(pchange, basectx, ctx, fname)
937 maketext(pchange, basectx, ctx, fname)
938
938
939 pdiff.addchange(pchange)
939 pdiff.addchange(pchange)
940
940
941
941
942 def addmodified(pdiff, basectx, ctx, modified):
942 def addmodified(pdiff, basectx, ctx, modified):
943 """add modified files to the phabdiff"""
943 """add modified files to the phabdiff"""
944 for fname in modified:
944 for fname in modified:
945 fctx = ctx[fname]
945 fctx = ctx[fname]
946 oldfctx = basectx.p1()[fname]
946 oldfctx = basectx.p1()[fname]
947 pchange = phabchange(currentPath=fname, oldPath=fname)
947 pchange = phabchange(currentPath=fname, oldPath=fname)
948 filemode = gitmode[fctx.flags()]
948 filemode = gitmode[fctx.flags()]
949 originalmode = gitmode[oldfctx.flags()]
949 originalmode = gitmode[oldfctx.flags()]
950 if filemode != originalmode:
950 if filemode != originalmode:
951 pchange.addoldmode(originalmode)
951 pchange.addoldmode(originalmode)
952 pchange.addnewmode(filemode)
952 pchange.addnewmode(filemode)
953
953
954 if (
954 if (
955 fctx.isbinary()
955 fctx.isbinary()
956 or notutf8(fctx)
956 or notutf8(fctx)
957 or oldfctx.isbinary()
957 or oldfctx.isbinary()
958 or notutf8(oldfctx)
958 or notutf8(oldfctx)
959 ):
959 ):
960 makebinary(pchange, fctx)
960 makebinary(pchange, fctx)
961 addoldbinary(pchange, oldfctx, fctx)
961 addoldbinary(pchange, oldfctx, fctx)
962 else:
962 else:
963 maketext(pchange, basectx, ctx, fname)
963 maketext(pchange, basectx, ctx, fname)
964
964
965 pdiff.addchange(pchange)
965 pdiff.addchange(pchange)
966
966
967
967
968 def addadded(pdiff, basectx, ctx, added, removed):
968 def addadded(pdiff, basectx, ctx, added, removed):
969 """add file adds to the phabdiff, both new files and copies/moves"""
969 """add file adds to the phabdiff, both new files and copies/moves"""
970 # Keep track of files that've been recorded as moved/copied, so if there are
970 # Keep track of files that've been recorded as moved/copied, so if there are
971 # additional copies we can mark them (moves get removed from removed)
971 # additional copies we can mark them (moves get removed from removed)
972 copiedchanges = {}
972 copiedchanges = {}
973 movedchanges = {}
973 movedchanges = {}
974
974
975 copy = {}
975 copy = {}
976 if basectx != ctx:
976 if basectx != ctx:
977 copy = copies.pathcopies(basectx.p1(), ctx)
977 copy = copies.pathcopies(basectx.p1(), ctx)
978
978
979 for fname in added:
979 for fname in added:
980 fctx = ctx[fname]
980 fctx = ctx[fname]
981 oldfctx = None
981 oldfctx = None
982 pchange = phabchange(currentPath=fname)
982 pchange = phabchange(currentPath=fname)
983
983
984 filemode = gitmode[fctx.flags()]
984 filemode = gitmode[fctx.flags()]
985
985
986 if copy:
986 if copy:
987 originalfname = copy.get(fname, fname)
987 originalfname = copy.get(fname, fname)
988 else:
988 else:
989 originalfname = fname
989 originalfname = fname
990 if fctx.renamed():
990 if fctx.renamed():
991 originalfname = fctx.renamed()[0]
991 originalfname = fctx.renamed()[0]
992
992
993 renamed = fname != originalfname
993 renamed = fname != originalfname
994
994
995 if renamed:
995 if renamed:
996 oldfctx = basectx.p1()[originalfname]
996 oldfctx = basectx.p1()[originalfname]
997 originalmode = gitmode[oldfctx.flags()]
997 originalmode = gitmode[oldfctx.flags()]
998 pchange.oldPath = originalfname
998 pchange.oldPath = originalfname
999
999
1000 if originalfname in removed:
1000 if originalfname in removed:
1001 origpchange = phabchange(
1001 origpchange = phabchange(
1002 currentPath=originalfname,
1002 currentPath=originalfname,
1003 oldPath=originalfname,
1003 oldPath=originalfname,
1004 type=DiffChangeType.MOVE_AWAY,
1004 type=DiffChangeType.MOVE_AWAY,
1005 awayPaths=[fname],
1005 awayPaths=[fname],
1006 )
1006 )
1007 movedchanges[originalfname] = origpchange
1007 movedchanges[originalfname] = origpchange
1008 removed.remove(originalfname)
1008 removed.remove(originalfname)
1009 pchange.type = DiffChangeType.MOVE_HERE
1009 pchange.type = DiffChangeType.MOVE_HERE
1010 elif originalfname in movedchanges:
1010 elif originalfname in movedchanges:
1011 movedchanges[originalfname].type = DiffChangeType.MULTICOPY
1011 movedchanges[originalfname].type = DiffChangeType.MULTICOPY
1012 movedchanges[originalfname].awayPaths.append(fname)
1012 movedchanges[originalfname].awayPaths.append(fname)
1013 pchange.type = DiffChangeType.COPY_HERE
1013 pchange.type = DiffChangeType.COPY_HERE
1014 else: # pure copy
1014 else: # pure copy
1015 if originalfname not in copiedchanges:
1015 if originalfname not in copiedchanges:
1016 origpchange = phabchange(
1016 origpchange = phabchange(
1017 currentPath=originalfname, type=DiffChangeType.COPY_AWAY
1017 currentPath=originalfname, type=DiffChangeType.COPY_AWAY
1018 )
1018 )
1019 copiedchanges[originalfname] = origpchange
1019 copiedchanges[originalfname] = origpchange
1020 else:
1020 else:
1021 origpchange = copiedchanges[originalfname]
1021 origpchange = copiedchanges[originalfname]
1022 origpchange.awayPaths.append(fname)
1022 origpchange.awayPaths.append(fname)
1023 pchange.type = DiffChangeType.COPY_HERE
1023 pchange.type = DiffChangeType.COPY_HERE
1024
1024
1025 if filemode != originalmode:
1025 if filemode != originalmode:
1026 pchange.addoldmode(originalmode)
1026 pchange.addoldmode(originalmode)
1027 pchange.addnewmode(filemode)
1027 pchange.addnewmode(filemode)
1028 else: # Brand-new file
1028 else: # Brand-new file
1029 pchange.addnewmode(gitmode[fctx.flags()])
1029 pchange.addnewmode(gitmode[fctx.flags()])
1030 pchange.type = DiffChangeType.ADD
1030 pchange.type = DiffChangeType.ADD
1031
1031
1032 if (
1032 if (
1033 fctx.isbinary()
1033 fctx.isbinary()
1034 or notutf8(fctx)
1034 or notutf8(fctx)
1035 or (oldfctx and (oldfctx.isbinary() or notutf8(oldfctx)))
1035 or (oldfctx and (oldfctx.isbinary() or notutf8(oldfctx)))
1036 ):
1036 ):
1037 makebinary(pchange, fctx)
1037 makebinary(pchange, fctx)
1038 if renamed:
1038 if renamed:
1039 addoldbinary(pchange, oldfctx, fctx)
1039 addoldbinary(pchange, oldfctx, fctx)
1040 else:
1040 else:
1041 maketext(pchange, basectx, ctx, fname)
1041 maketext(pchange, basectx, ctx, fname)
1042
1042
1043 pdiff.addchange(pchange)
1043 pdiff.addchange(pchange)
1044
1044
1045 for _path, copiedchange in copiedchanges.items():
1045 for _path, copiedchange in copiedchanges.items():
1046 pdiff.addchange(copiedchange)
1046 pdiff.addchange(copiedchange)
1047 for _path, movedchange in movedchanges.items():
1047 for _path, movedchange in movedchanges.items():
1048 pdiff.addchange(movedchange)
1048 pdiff.addchange(movedchange)
1049
1049
1050
1050
1051 def creatediff(basectx, ctx):
1051 def creatediff(basectx, ctx):
1052 """create a Differential Diff"""
1052 """create a Differential Diff"""
1053 repo = ctx.repo()
1053 repo = ctx.repo()
1054 repophid = getrepophid(repo)
1054 repophid = getrepophid(repo)
1055 # Create a "Differential Diff" via "differential.creatediff" API
1055 # Create a "Differential Diff" via "differential.creatediff" API
1056 pdiff = phabdiff(
1056 pdiff = phabdiff(
1057 sourceControlBaseRevision=b'%s' % basectx.p1().hex(),
1057 sourceControlBaseRevision=b'%s' % basectx.p1().hex(),
1058 branch=b'%s' % ctx.branch(),
1058 branch=b'%s' % ctx.branch(),
1059 )
1059 )
1060 modified, added, removed, _d, _u, _i, _c = basectx.p1().status(ctx)
1060 modified, added, removed, _d, _u, _i, _c = basectx.p1().status(ctx)
1061 # addadded will remove moved files from removed, so addremoved won't get
1061 # addadded will remove moved files from removed, so addremoved won't get
1062 # them
1062 # them
1063 addadded(pdiff, basectx, ctx, added, removed)
1063 addadded(pdiff, basectx, ctx, added, removed)
1064 addmodified(pdiff, basectx, ctx, modified)
1064 addmodified(pdiff, basectx, ctx, modified)
1065 addremoved(pdiff, basectx, ctx, removed)
1065 addremoved(pdiff, basectx, ctx, removed)
1066 if repophid:
1066 if repophid:
1067 pdiff.repositoryPHID = repophid
1067 pdiff.repositoryPHID = repophid
1068 diff = callconduit(
1068 diff = callconduit(
1069 repo.ui,
1069 repo.ui,
1070 b'differential.creatediff',
1070 b'differential.creatediff',
1071 pycompat.byteskwargs(attr.asdict(pdiff)),
1071 pycompat.byteskwargs(attr.asdict(pdiff)),
1072 )
1072 )
1073 if not diff:
1073 if not diff:
1074 if basectx != ctx:
1074 if basectx != ctx:
1075 msg = _(b'cannot create diff for %s::%s') % (basectx, ctx)
1075 msg = _(b'cannot create diff for %s::%s') % (basectx, ctx)
1076 else:
1076 else:
1077 msg = _(b'cannot create diff for %s') % ctx
1077 msg = _(b'cannot create diff for %s') % ctx
1078 raise error.Abort(msg)
1078 raise error.Abort(msg)
1079 return diff
1079 return diff
1080
1080
1081
1081
1082 def writediffproperties(ctxs, diff):
1082 def writediffproperties(ctxs, diff):
1083 """write metadata to diff so patches could be applied losslessly
1083 """write metadata to diff so patches could be applied losslessly
1084
1084
1085 ``ctxs`` is the list of commits that created the diff, in ascending order.
1085 ``ctxs`` is the list of commits that created the diff, in ascending order.
1086 The list is generally a single commit, but may be several when using
1086 The list is generally a single commit, but may be several when using
1087 ``phabsend --fold``.
1087 ``phabsend --fold``.
1088 """
1088 """
1089 # creatediff returns with a diffid but query returns with an id
1089 # creatediff returns with a diffid but query returns with an id
1090 diffid = diff.get(b'diffid', diff.get(b'id'))
1090 diffid = diff.get(b'diffid', diff.get(b'id'))
1091 basectx = ctxs[0]
1091 basectx = ctxs[0]
1092 tipctx = ctxs[-1]
1092 tipctx = ctxs[-1]
1093
1093
1094 params = {
1094 params = {
1095 b'diff_id': diffid,
1095 b'diff_id': diffid,
1096 b'name': b'hg:meta',
1096 b'name': b'hg:meta',
1097 b'data': templatefilters.json(
1097 b'data': templatefilters.json(
1098 {
1098 {
1099 b'user': tipctx.user(),
1099 b'user': tipctx.user(),
1100 b'date': b'%d %d' % tipctx.date(),
1100 b'date': b'%d %d' % tipctx.date(),
1101 b'branch': tipctx.branch(),
1101 b'branch': tipctx.branch(),
1102 b'node': tipctx.hex(),
1102 b'node': tipctx.hex(),
1103 b'parent': basectx.p1().hex(),
1103 b'parent': basectx.p1().hex(),
1104 }
1104 }
1105 ),
1105 ),
1106 }
1106 }
1107 callconduit(basectx.repo().ui, b'differential.setdiffproperty', params)
1107 callconduit(basectx.repo().ui, b'differential.setdiffproperty', params)
1108
1108
1109 commits = {}
1109 commits = {}
1110 for ctx in ctxs:
1110 for ctx in ctxs:
1111 commits[ctx.hex()] = {
1111 commits[ctx.hex()] = {
1112 b'author': stringutil.person(ctx.user()),
1112 b'author': stringutil.person(ctx.user()),
1113 b'authorEmail': stringutil.email(ctx.user()),
1113 b'authorEmail': stringutil.email(ctx.user()),
1114 b'time': int(ctx.date()[0]),
1114 b'time': int(ctx.date()[0]),
1115 b'commit': ctx.hex(),
1115 b'commit': ctx.hex(),
1116 b'parents': [ctx.p1().hex()],
1116 b'parents': [ctx.p1().hex()],
1117 b'branch': ctx.branch(),
1117 b'branch': ctx.branch(),
1118 }
1118 }
1119 params = {
1119 params = {
1120 b'diff_id': diffid,
1120 b'diff_id': diffid,
1121 b'name': b'local:commits',
1121 b'name': b'local:commits',
1122 b'data': templatefilters.json(commits),
1122 b'data': templatefilters.json(commits),
1123 }
1123 }
1124 callconduit(basectx.repo().ui, b'differential.setdiffproperty', params)
1124 callconduit(basectx.repo().ui, b'differential.setdiffproperty', params)
1125
1125
1126
1126
1127 def createdifferentialrevision(
1127 def createdifferentialrevision(
1128 ctxs,
1128 ctxs,
1129 revid=None,
1129 revid=None,
1130 parentrevphid=None,
1130 parentrevphid=None,
1131 oldbasenode=None,
1131 oldbasenode=None,
1132 oldnode=None,
1132 oldnode=None,
1133 olddiff=None,
1133 olddiff=None,
1134 actions=None,
1134 actions=None,
1135 comment=None,
1135 comment=None,
1136 ):
1136 ):
1137 """create or update a Differential Revision
1137 """create or update a Differential Revision
1138
1138
1139 If revid is None, create a new Differential Revision, otherwise update
1139 If revid is None, create a new Differential Revision, otherwise update
1140 revid. If parentrevphid is not None, set it as a dependency.
1140 revid. If parentrevphid is not None, set it as a dependency.
1141
1141
1142 If there is a single commit for the new Differential Revision, ``ctxs`` will
1142 If there is a single commit for the new Differential Revision, ``ctxs`` will
1143 be a list of that single context. Otherwise, it is a list that covers the
1143 be a list of that single context. Otherwise, it is a list that covers the
1144 range of changes for the differential, where ``ctxs[0]`` is the first change
1144 range of changes for the differential, where ``ctxs[0]`` is the first change
1145 to include and ``ctxs[-1]`` is the last.
1145 to include and ``ctxs[-1]`` is the last.
1146
1146
1147 If oldnode is not None, check if the patch content (without commit message
1147 If oldnode is not None, check if the patch content (without commit message
1148 and metadata) has changed before creating another diff. For a Revision with
1148 and metadata) has changed before creating another diff. For a Revision with
1149 a single commit, ``oldbasenode`` and ``oldnode`` have the same value. For a
1149 a single commit, ``oldbasenode`` and ``oldnode`` have the same value. For a
1150 Revision covering multiple commits, ``oldbasenode`` corresponds to
1150 Revision covering multiple commits, ``oldbasenode`` corresponds to
1151 ``ctxs[0]`` the previous time this Revision was posted, and ``oldnode``
1151 ``ctxs[0]`` the previous time this Revision was posted, and ``oldnode``
1152 corresponds to ``ctxs[-1]``.
1152 corresponds to ``ctxs[-1]``.
1153
1153
1154 If actions is not None, they will be appended to the transaction.
1154 If actions is not None, they will be appended to the transaction.
1155 """
1155 """
1156 ctx = ctxs[-1]
1156 ctx = ctxs[-1]
1157 basectx = ctxs[0]
1157 basectx = ctxs[0]
1158
1158
1159 repo = ctx.repo()
1159 repo = ctx.repo()
1160 if oldnode:
1160 if oldnode:
1161 diffopts = mdiff.diffopts(git=True, context=32767)
1161 diffopts = mdiff.diffopts(git=True, context=32767)
1162 unfi = repo.unfiltered()
1162 unfi = repo.unfiltered()
1163 oldctx = unfi[oldnode]
1163 oldctx = unfi[oldnode]
1164 oldbasectx = unfi[oldbasenode]
1164 oldbasectx = unfi[oldbasenode]
1165 neednewdiff = getdiff(basectx, ctx, diffopts) != getdiff(
1165 neednewdiff = getdiff(basectx, ctx, diffopts) != getdiff(
1166 oldbasectx, oldctx, diffopts
1166 oldbasectx, oldctx, diffopts
1167 )
1167 )
1168 else:
1168 else:
1169 neednewdiff = True
1169 neednewdiff = True
1170
1170
1171 transactions = []
1171 transactions = []
1172 if neednewdiff:
1172 if neednewdiff:
1173 diff = creatediff(basectx, ctx)
1173 diff = creatediff(basectx, ctx)
1174 transactions.append({b'type': b'update', b'value': diff[b'phid']})
1174 transactions.append({b'type': b'update', b'value': diff[b'phid']})
1175 if comment:
1175 if comment:
1176 transactions.append({b'type': b'comment', b'value': comment})
1176 transactions.append({b'type': b'comment', b'value': comment})
1177 else:
1177 else:
1178 # Even if we don't need to upload a new diff because the patch content
1178 # Even if we don't need to upload a new diff because the patch content
1179 # does not change. We might still need to update its metadata so
1179 # does not change. We might still need to update its metadata so
1180 # pushers could know the correct node metadata.
1180 # pushers could know the correct node metadata.
1181 assert olddiff
1181 assert olddiff
1182 diff = olddiff
1182 diff = olddiff
1183 writediffproperties(ctxs, diff)
1183 writediffproperties(ctxs, diff)
1184
1184
1185 # Set the parent Revision every time, so commit re-ordering is picked-up
1185 # Set the parent Revision every time, so commit re-ordering is picked-up
1186 if parentrevphid:
1186 if parentrevphid:
1187 transactions.append(
1187 transactions.append(
1188 {b'type': b'parents.set', b'value': [parentrevphid]}
1188 {b'type': b'parents.set', b'value': [parentrevphid]}
1189 )
1189 )
1190
1190
1191 if actions:
1191 if actions:
1192 transactions += actions
1192 transactions += actions
1193
1193
1194 # When folding multiple local commits into a single review, arcanist will
1194 # When folding multiple local commits into a single review, arcanist will
1195 # take the summary line of the first commit as the title, and then
1195 # take the summary line of the first commit as the title, and then
1196 # concatenate the rest of the remaining messages (including each of their
1196 # concatenate the rest of the remaining messages (including each of their
1197 # first lines) to the rest of the first commit message (each separated by
1197 # first lines) to the rest of the first commit message (each separated by
1198 # an empty line), and use that as the summary field. Do the same here.
1198 # an empty line), and use that as the summary field. Do the same here.
1199 # For commits with only a one line message, there is no summary field, as
1199 # For commits with only a one line message, there is no summary field, as
1200 # this gets assigned to the title.
1200 # this gets assigned to the title.
1201 fields = util.sortdict() # sorted for stable wire protocol in tests
1201 fields = util.sortdict() # sorted for stable wire protocol in tests
1202
1202
1203 for i, _ctx in enumerate(ctxs):
1203 for i, _ctx in enumerate(ctxs):
1204 # Parse commit message and update related fields.
1204 # Parse commit message and update related fields.
1205 desc = _ctx.description()
1205 desc = _ctx.description()
1206 info = callconduit(
1206 info = callconduit(
1207 repo.ui, b'differential.parsecommitmessage', {b'corpus': desc}
1207 repo.ui, b'differential.parsecommitmessage', {b'corpus': desc}
1208 )
1208 )
1209
1209
1210 for k in [b'title', b'summary', b'testPlan']:
1210 for k in [b'title', b'summary', b'testPlan']:
1211 v = info[b'fields'].get(k)
1211 v = info[b'fields'].get(k)
1212 if not v:
1212 if not v:
1213 continue
1213 continue
1214
1214
1215 if i == 0:
1215 if i == 0:
1216 # Title, summary and test plan (if present) are taken verbatim
1216 # Title, summary and test plan (if present) are taken verbatim
1217 # for the first commit.
1217 # for the first commit.
1218 fields[k] = v.rstrip()
1218 fields[k] = v.rstrip()
1219 continue
1219 continue
1220 elif k == b'title':
1220 elif k == b'title':
1221 # Add subsequent titles (i.e. the first line of the commit
1221 # Add subsequent titles (i.e. the first line of the commit
1222 # message) back to the summary.
1222 # message) back to the summary.
1223 k = b'summary'
1223 k = b'summary'
1224
1224
1225 # Append any current field to the existing composite field
1225 # Append any current field to the existing composite field
1226 fields[k] = b'\n\n'.join(filter(None, [fields.get(k), v.rstrip()]))
1226 fields[k] = b'\n\n'.join(filter(None, [fields.get(k), v.rstrip()]))
1227
1227
1228 for k, v in fields.items():
1228 for k, v in fields.items():
1229 transactions.append({b'type': k, b'value': v})
1229 transactions.append({b'type': k, b'value': v})
1230
1230
1231 params = {b'transactions': transactions}
1231 params = {b'transactions': transactions}
1232 if revid is not None:
1232 if revid is not None:
1233 # Update an existing Differential Revision
1233 # Update an existing Differential Revision
1234 params[b'objectIdentifier'] = revid
1234 params[b'objectIdentifier'] = revid
1235
1235
1236 revision = callconduit(repo.ui, b'differential.revision.edit', params)
1236 revision = callconduit(repo.ui, b'differential.revision.edit', params)
1237 if not revision:
1237 if not revision:
1238 if len(ctxs) == 1:
1238 if len(ctxs) == 1:
1239 msg = _(b'cannot create revision for %s') % ctx
1239 msg = _(b'cannot create revision for %s') % ctx
1240 else:
1240 else:
1241 msg = _(b'cannot create revision for %s::%s') % (basectx, ctx)
1241 msg = _(b'cannot create revision for %s::%s') % (basectx, ctx)
1242 raise error.Abort(msg)
1242 raise error.Abort(msg)
1243
1243
1244 return revision, diff
1244 return revision, diff
1245
1245
1246
1246
1247 def userphids(ui, names):
1247 def userphids(ui, names):
1248 """convert user names to PHIDs"""
1248 """convert user names to PHIDs"""
1249 names = [name.lower() for name in names]
1249 names = [name.lower() for name in names]
1250 query = {b'constraints': {b'usernames': names}}
1250 query = {b'constraints': {b'usernames': names}}
1251 result = callconduit(ui, b'user.search', query)
1251 result = callconduit(ui, b'user.search', query)
1252 # username not found is not an error of the API. So check if we have missed
1252 # username not found is not an error of the API. So check if we have missed
1253 # some names here.
1253 # some names here.
1254 data = result[b'data']
1254 data = result[b'data']
1255 resolved = {entry[b'fields'][b'username'].lower() for entry in data}
1255 resolved = {entry[b'fields'][b'username'].lower() for entry in data}
1256 unresolved = set(names) - resolved
1256 unresolved = set(names) - resolved
1257 if unresolved:
1257 if unresolved:
1258 raise error.Abort(
1258 raise error.Abort(
1259 _(b'unknown username: %s') % b' '.join(sorted(unresolved))
1259 _(b'unknown username: %s') % b' '.join(sorted(unresolved))
1260 )
1260 )
1261 return [entry[b'phid'] for entry in data]
1261 return [entry[b'phid'] for entry in data]
1262
1262
1263
1263
1264 def _print_phabsend_action(ui, ctx, newrevid, action):
1264 def _print_phabsend_action(ui, ctx, newrevid, action):
1265 """print the ``action`` that occurred when posting ``ctx`` for review
1265 """print the ``action`` that occurred when posting ``ctx`` for review
1266
1266
1267 This is a utility function for the sending phase of ``phabsend``, which
1267 This is a utility function for the sending phase of ``phabsend``, which
1268 makes it easier to show a status for all local commits with `--fold``.
1268 makes it easier to show a status for all local commits with `--fold``.
1269 """
1269 """
1270 actiondesc = ui.label(
1270 actiondesc = ui.label(
1271 {
1271 {
1272 b'created': _(b'created'),
1272 b'created': _(b'created'),
1273 b'skipped': _(b'skipped'),
1273 b'skipped': _(b'skipped'),
1274 b'updated': _(b'updated'),
1274 b'updated': _(b'updated'),
1275 }[action],
1275 }[action],
1276 b'phabricator.action.%s' % action,
1276 b'phabricator.action.%s' % action,
1277 )
1277 )
1278 drevdesc = ui.label(b'D%d' % newrevid, b'phabricator.drev')
1278 drevdesc = ui.label(b'D%d' % newrevid, b'phabricator.drev')
1279 summary = cmdutil.format_changeset_summary(ui, ctx, b'phabsend')
1279 summary = cmdutil.format_changeset_summary(ui, ctx, b'phabsend')
1280 ui.write(_(b'%s - %s - %s\n') % (drevdesc, actiondesc, summary))
1280 ui.write(_(b'%s - %s - %s\n') % (drevdesc, actiondesc, summary))
1281
1281
1282
1282
1283 def _amend_diff_properties(unfi, drevid, newnodes, diff):
1283 def _amend_diff_properties(unfi, drevid, newnodes, diff):
1284 """update the local commit list for the ``diff`` associated with ``drevid``
1284 """update the local commit list for the ``diff`` associated with ``drevid``
1285
1285
1286 This is a utility function for the amend phase of ``phabsend``, which
1286 This is a utility function for the amend phase of ``phabsend``, which
1287 converts failures to warning messages.
1287 converts failures to warning messages.
1288 """
1288 """
1289 _debug(
1289 _debug(
1290 unfi.ui,
1290 unfi.ui,
1291 b"new commits: %s\n" % stringutil.pprint([short(n) for n in newnodes]),
1291 b"new commits: %s\n" % stringutil.pprint([short(n) for n in newnodes]),
1292 )
1292 )
1293
1293
1294 try:
1294 try:
1295 writediffproperties([unfi[newnode] for newnode in newnodes], diff)
1295 writediffproperties([unfi[newnode] for newnode in newnodes], diff)
1296 except util.urlerr.urlerror:
1296 except util.urlerr.urlerror:
1297 # If it fails just warn and keep going, otherwise the DREV
1297 # If it fails just warn and keep going, otherwise the DREV
1298 # associations will be lost
1298 # associations will be lost
1299 unfi.ui.warnnoi18n(b'Failed to update metadata for D%d\n' % drevid)
1299 unfi.ui.warnnoi18n(b'Failed to update metadata for D%d\n' % drevid)
1300
1300
1301
1301
1302 @vcrcommand(
1302 @vcrcommand(
1303 b'phabsend',
1303 b'phabsend',
1304 [
1304 [
1305 (b'r', b'rev', [], _(b'revisions to send'), _(b'REV')),
1305 (b'r', b'rev', [], _(b'revisions to send'), _(b'REV')),
1306 (b'', b'amend', True, _(b'update commit messages')),
1306 (b'', b'amend', True, _(b'update commit messages')),
1307 (b'', b'reviewer', [], _(b'specify reviewers')),
1307 (b'', b'reviewer', [], _(b'specify reviewers')),
1308 (b'', b'blocker', [], _(b'specify blocking reviewers')),
1308 (b'', b'blocker', [], _(b'specify blocking reviewers')),
1309 (
1309 (
1310 b'm',
1310 b'm',
1311 b'comment',
1311 b'comment',
1312 b'',
1312 b'',
1313 _(b'add a comment to Revisions with new/updated Diffs'),
1313 _(b'add a comment to Revisions with new/updated Diffs'),
1314 ),
1314 ),
1315 (b'', b'confirm', None, _(b'ask for confirmation before sending')),
1315 (b'', b'confirm', None, _(b'ask for confirmation before sending')),
1316 (b'', b'fold', False, _(b'combine the revisions into one review')),
1316 (b'', b'fold', False, _(b'combine the revisions into one review')),
1317 ],
1317 ],
1318 _(b'REV [OPTIONS]'),
1318 _(b'REV [OPTIONS]'),
1319 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1319 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1320 )
1320 )
1321 def phabsend(ui, repo, *revs, **opts):
1321 def phabsend(ui, repo, *revs, **opts):
1322 """upload changesets to Phabricator
1322 """upload changesets to Phabricator
1323
1323
1324 If there are multiple revisions specified, they will be send as a stack
1324 If there are multiple revisions specified, they will be send as a stack
1325 with a linear dependencies relationship using the order specified by the
1325 with a linear dependencies relationship using the order specified by the
1326 revset.
1326 revset.
1327
1327
1328 For the first time uploading changesets, local tags will be created to
1328 For the first time uploading changesets, local tags will be created to
1329 maintain the association. After the first time, phabsend will check
1329 maintain the association. After the first time, phabsend will check
1330 obsstore and tags information so it can figure out whether to update an
1330 obsstore and tags information so it can figure out whether to update an
1331 existing Differential Revision, or create a new one.
1331 existing Differential Revision, or create a new one.
1332
1332
1333 If --amend is set, update commit messages so they have the
1333 If --amend is set, update commit messages so they have the
1334 ``Differential Revision`` URL, remove related tags. This is similar to what
1334 ``Differential Revision`` URL, remove related tags. This is similar to what
1335 arcanist will do, and is more desired in author-push workflows. Otherwise,
1335 arcanist will do, and is more desired in author-push workflows. Otherwise,
1336 use local tags to record the ``Differential Revision`` association.
1336 use local tags to record the ``Differential Revision`` association.
1337
1337
1338 The --confirm option lets you confirm changesets before sending them. You
1338 The --confirm option lets you confirm changesets before sending them. You
1339 can also add following to your configuration file to make it default
1339 can also add following to your configuration file to make it default
1340 behaviour::
1340 behaviour::
1341
1341
1342 [phabsend]
1342 [phabsend]
1343 confirm = true
1343 confirm = true
1344
1344
1345 By default, a separate review will be created for each commit that is
1345 By default, a separate review will be created for each commit that is
1346 selected, and will have the same parent/child relationship in Phabricator.
1346 selected, and will have the same parent/child relationship in Phabricator.
1347 If ``--fold`` is set, multiple commits are rolled up into a single review
1347 If ``--fold`` is set, multiple commits are rolled up into a single review
1348 as if diffed from the parent of the first revision to the last. The commit
1348 as if diffed from the parent of the first revision to the last. The commit
1349 messages are concatenated in the summary field on Phabricator.
1349 messages are concatenated in the summary field on Phabricator.
1350
1350
1351 phabsend will check obsstore and the above association to decide whether to
1351 phabsend will check obsstore and the above association to decide whether to
1352 update an existing Differential Revision, or create a new one.
1352 update an existing Differential Revision, or create a new one.
1353 """
1353 """
1354 opts = pycompat.byteskwargs(opts)
1354 opts = pycompat.byteskwargs(opts)
1355 revs = list(revs) + opts.get(b'rev', [])
1355 revs = list(revs) + opts.get(b'rev', [])
1356 revs = logcmdutil.revrange(repo, revs)
1356 revs = logcmdutil.revrange(repo, revs)
1357 revs.sort() # ascending order to preserve topological parent/child in phab
1357 revs.sort() # ascending order to preserve topological parent/child in phab
1358
1358
1359 if not revs:
1359 if not revs:
1360 raise error.Abort(_(b'phabsend requires at least one changeset'))
1360 raise error.Abort(_(b'phabsend requires at least one changeset'))
1361 if opts.get(b'amend'):
1361 if opts.get(b'amend'):
1362 cmdutil.checkunfinished(repo)
1362 cmdutil.checkunfinished(repo)
1363
1363
1364 ctxs = [repo[rev] for rev in revs]
1364 ctxs = [repo[rev] for rev in revs]
1365
1365
1366 if any(c for c in ctxs if c.obsolete()):
1366 if any(c for c in ctxs if c.obsolete()):
1367 raise error.Abort(_(b"obsolete commits cannot be posted for review"))
1367 raise error.Abort(_(b"obsolete commits cannot be posted for review"))
1368
1368
1369 # Ensure the local commits are an unbroken range. The semantics of the
1369 # Ensure the local commits are an unbroken range. The semantics of the
1370 # --fold option implies this, and the auto restacking of orphans requires
1370 # --fold option implies this, and the auto restacking of orphans requires
1371 # it. Otherwise A+C in A->B->C will cause B to be orphaned, and C' to
1371 # it. Otherwise A+C in A->B->C will cause B to be orphaned, and C' to
1372 # get A' as a parent.
1372 # get A' as a parent.
1373 def _fail_nonlinear_revs(revs, revtype):
1373 def _fail_nonlinear_revs(revs, revtype):
1374 badnodes = [repo[r].node() for r in revs]
1374 badnodes = [repo[r].node() for r in revs]
1375 raise error.Abort(
1375 raise error.Abort(
1376 _(b"cannot phabsend multiple %s revisions: %s")
1376 _(b"cannot phabsend multiple %s revisions: %s")
1377 % (revtype, scmutil.nodesummaries(repo, badnodes)),
1377 % (revtype, scmutil.nodesummaries(repo, badnodes)),
1378 hint=_(b"the revisions must form a linear chain"),
1378 hint=_(b"the revisions must form a linear chain"),
1379 )
1379 )
1380
1380
1381 heads = repo.revs(b'heads(%ld)', revs)
1381 heads = repo.revs(b'heads(%ld)', revs)
1382 if len(heads) > 1:
1382 if len(heads) > 1:
1383 _fail_nonlinear_revs(heads, b"head")
1383 _fail_nonlinear_revs(heads, b"head")
1384
1384
1385 roots = repo.revs(b'roots(%ld)', revs)
1385 roots = repo.revs(b'roots(%ld)', revs)
1386 if len(roots) > 1:
1386 if len(roots) > 1:
1387 _fail_nonlinear_revs(roots, b"root")
1387 _fail_nonlinear_revs(roots, b"root")
1388
1388
1389 fold = opts.get(b'fold')
1389 fold = opts.get(b'fold')
1390 if fold:
1390 if fold:
1391 if len(revs) == 1:
1391 if len(revs) == 1:
1392 # TODO: just switch to --no-fold instead?
1392 # TODO: just switch to --no-fold instead?
1393 raise error.Abort(_(b"cannot fold a single revision"))
1393 raise error.Abort(_(b"cannot fold a single revision"))
1394
1394
1395 # There's no clear way to manage multiple commits with a Dxxx tag, so
1395 # There's no clear way to manage multiple commits with a Dxxx tag, so
1396 # require the amend option. (We could append "_nnn", but then it
1396 # require the amend option. (We could append "_nnn", but then it
1397 # becomes jumbled if earlier commits are added to an update.) It should
1397 # becomes jumbled if earlier commits are added to an update.) It should
1398 # lock the repo and ensure that the range is editable, but that would
1398 # lock the repo and ensure that the range is editable, but that would
1399 # make the code pretty convoluted. The default behavior of `arc` is to
1399 # make the code pretty convoluted. The default behavior of `arc` is to
1400 # create a new review anyway.
1400 # create a new review anyway.
1401 if not opts.get(b"amend"):
1401 if not opts.get(b"amend"):
1402 raise error.Abort(_(b"cannot fold with --no-amend"))
1402 raise error.Abort(_(b"cannot fold with --no-amend"))
1403
1403
1404 # It might be possible to bucketize the revisions by the DREV value, and
1404 # It might be possible to bucketize the revisions by the DREV value, and
1405 # iterate over those groups when posting, and then again when amending.
1405 # iterate over those groups when posting, and then again when amending.
1406 # But for simplicity, require all selected revisions to be for the same
1406 # But for simplicity, require all selected revisions to be for the same
1407 # DREV (if present). Adding local revisions to an existing DREV is
1407 # DREV (if present). Adding local revisions to an existing DREV is
1408 # acceptable.
1408 # acceptable.
1409 drevmatchers = [
1409 drevmatchers = [
1410 _differentialrevisiondescre.search(ctx.description())
1410 _differentialrevisiondescre.search(ctx.description())
1411 for ctx in ctxs
1411 for ctx in ctxs
1412 ]
1412 ]
1413 if len({m.group('url') for m in drevmatchers if m}) > 1:
1413 if len({m.group('url') for m in drevmatchers if m}) > 1:
1414 raise error.Abort(
1414 raise error.Abort(
1415 _(b"cannot fold revisions with different DREV values")
1415 _(b"cannot fold revisions with different DREV values")
1416 )
1416 )
1417
1417
1418 # {newnode: (oldnode, olddiff, olddrev}
1418 # {newnode: (oldnode, olddiff, olddrev}
1419 oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
1419 oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
1420
1420
1421 confirm = ui.configbool(b'phabsend', b'confirm')
1421 confirm = ui.configbool(b'phabsend', b'confirm')
1422 confirm |= bool(opts.get(b'confirm'))
1422 confirm |= bool(opts.get(b'confirm'))
1423 if confirm:
1423 if confirm:
1424 confirmed = _confirmbeforesend(repo, revs, oldmap)
1424 confirmed = _confirmbeforesend(repo, revs, oldmap)
1425 if not confirmed:
1425 if not confirmed:
1426 raise error.Abort(_(b'phabsend cancelled'))
1426 raise error.Abort(_(b'phabsend cancelled'))
1427
1427
1428 actions = []
1428 actions = []
1429 reviewers = opts.get(b'reviewer', [])
1429 reviewers = opts.get(b'reviewer', [])
1430 blockers = opts.get(b'blocker', [])
1430 blockers = opts.get(b'blocker', [])
1431 phids = []
1431 phids = []
1432 if reviewers:
1432 if reviewers:
1433 phids.extend(userphids(repo.ui, reviewers))
1433 phids.extend(userphids(repo.ui, reviewers))
1434 if blockers:
1434 if blockers:
1435 phids.extend(
1435 phids.extend(
1436 map(
1436 map(
1437 lambda phid: b'blocking(%s)' % phid,
1437 lambda phid: b'blocking(%s)' % phid,
1438 userphids(repo.ui, blockers),
1438 userphids(repo.ui, blockers),
1439 )
1439 )
1440 )
1440 )
1441 if phids:
1441 if phids:
1442 actions.append({b'type': b'reviewers.add', b'value': phids})
1442 actions.append({b'type': b'reviewers.add', b'value': phids})
1443
1443
1444 drevids = [] # [int]
1444 drevids = [] # [int]
1445 diffmap = {} # {newnode: diff}
1445 diffmap = {} # {newnode: diff}
1446
1446
1447 # Send patches one by one so we know their Differential Revision PHIDs and
1447 # Send patches one by one so we know their Differential Revision PHIDs and
1448 # can provide dependency relationship
1448 # can provide dependency relationship
1449 lastrevphid = None
1449 lastrevphid = None
1450 for ctx in ctxs:
1450 for ctx in ctxs:
1451 if fold:
1451 if fold:
1452 ui.debug(b'sending rev %d::%d\n' % (ctx.rev(), ctxs[-1].rev()))
1452 ui.debug(b'sending rev %d::%d\n' % (ctx.rev(), ctxs[-1].rev()))
1453 else:
1453 else:
1454 ui.debug(b'sending rev %d\n' % ctx.rev())
1454 ui.debug(b'sending rev %d\n' % ctx.rev())
1455
1455
1456 # Get Differential Revision ID
1456 # Get Differential Revision ID
1457 oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None))
1457 oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None))
1458 oldbasenode, oldbasediff, oldbaserevid = oldnode, olddiff, revid
1458 oldbasenode, oldbasediff, oldbaserevid = oldnode, olddiff, revid
1459
1459
1460 if fold:
1460 if fold:
1461 oldbasenode, oldbasediff, oldbaserevid = oldmap.get(
1461 oldbasenode, oldbasediff, oldbaserevid = oldmap.get(
1462 ctxs[-1].node(), (None, None, None)
1462 ctxs[-1].node(), (None, None, None)
1463 )
1463 )
1464
1464
1465 if oldnode != ctx.node() or opts.get(b'amend'):
1465 if oldnode != ctx.node() or opts.get(b'amend'):
1466 # Create or update Differential Revision
1466 # Create or update Differential Revision
1467 revision, diff = createdifferentialrevision(
1467 revision, diff = createdifferentialrevision(
1468 ctxs if fold else [ctx],
1468 ctxs if fold else [ctx],
1469 revid,
1469 revid,
1470 lastrevphid,
1470 lastrevphid,
1471 oldbasenode,
1471 oldbasenode,
1472 oldnode,
1472 oldnode,
1473 olddiff,
1473 olddiff,
1474 actions,
1474 actions,
1475 opts.get(b'comment'),
1475 opts.get(b'comment'),
1476 )
1476 )
1477
1477
1478 if fold:
1478 if fold:
1479 for ctx in ctxs:
1479 for ctx in ctxs:
1480 diffmap[ctx.node()] = diff
1480 diffmap[ctx.node()] = diff
1481 else:
1481 else:
1482 diffmap[ctx.node()] = diff
1482 diffmap[ctx.node()] = diff
1483
1483
1484 newrevid = int(revision[b'object'][b'id'])
1484 newrevid = int(revision[b'object'][b'id'])
1485 newrevphid = revision[b'object'][b'phid']
1485 newrevphid = revision[b'object'][b'phid']
1486 if revid:
1486 if revid:
1487 action = b'updated'
1487 action = b'updated'
1488 else:
1488 else:
1489 action = b'created'
1489 action = b'created'
1490
1490
1491 # Create a local tag to note the association, if commit message
1491 # Create a local tag to note the association, if commit message
1492 # does not have it already
1492 # does not have it already
1493 if not fold:
1493 if not fold:
1494 m = _differentialrevisiondescre.search(ctx.description())
1494 m = _differentialrevisiondescre.search(ctx.description())
1495 if not m or int(m.group('id')) != newrevid:
1495 if not m or int(m.group('id')) != newrevid:
1496 tagname = b'D%d' % newrevid
1496 tagname = b'D%d' % newrevid
1497 tags.tag(
1497 tags.tag(
1498 repo,
1498 repo,
1499 tagname,
1499 tagname,
1500 ctx.node(),
1500 ctx.node(),
1501 message=None,
1501 message=None,
1502 user=None,
1502 user=None,
1503 date=None,
1503 date=None,
1504 local=True,
1504 local=True,
1505 )
1505 )
1506 else:
1506 else:
1507 # Nothing changed. But still set "newrevphid" so the next revision
1507 # Nothing changed. But still set "newrevphid" so the next revision
1508 # could depend on this one and "newrevid" for the summary line.
1508 # could depend on this one and "newrevid" for the summary line.
1509 newrevphid = querydrev(repo.ui, b'%d' % revid)[0][b'phid']
1509 newrevphid = querydrev(repo.ui, b'%d' % revid)[0][b'phid']
1510 newrevid = revid
1510 newrevid = revid
1511 action = b'skipped'
1511 action = b'skipped'
1512
1512
1513 drevids.append(newrevid)
1513 drevids.append(newrevid)
1514 lastrevphid = newrevphid
1514 lastrevphid = newrevphid
1515
1515
1516 if fold:
1516 if fold:
1517 for c in ctxs:
1517 for c in ctxs:
1518 if oldmap.get(c.node(), (None, None, None))[2]:
1518 if oldmap.get(c.node(), (None, None, None))[2]:
1519 action = b'updated'
1519 action = b'updated'
1520 else:
1520 else:
1521 action = b'created'
1521 action = b'created'
1522 _print_phabsend_action(ui, c, newrevid, action)
1522 _print_phabsend_action(ui, c, newrevid, action)
1523 break
1523 break
1524
1524
1525 _print_phabsend_action(ui, ctx, newrevid, action)
1525 _print_phabsend_action(ui, ctx, newrevid, action)
1526
1526
1527 # Update commit messages and remove tags
1527 # Update commit messages and remove tags
1528 if opts.get(b'amend'):
1528 if opts.get(b'amend'):
1529 unfi = repo.unfiltered()
1529 unfi = repo.unfiltered()
1530 drevs = callconduit(ui, b'differential.query', {b'ids': drevids})
1530 drevs = callconduit(ui, b'differential.query', {b'ids': drevids})
1531 with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'):
1531 with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'):
1532 # Eagerly evaluate commits to restabilize before creating new
1532 # Eagerly evaluate commits to restabilize before creating new
1533 # commits. The selected revisions are excluded because they are
1533 # commits. The selected revisions are excluded because they are
1534 # automatically restacked as part of the submission process.
1534 # automatically restacked as part of the submission process.
1535 restack = [
1535 restack = [
1536 c
1536 c
1537 for c in repo.set(
1537 for c in repo.set(
1538 b"(%ld::) - (%ld) - unstable() - obsolete() - public()",
1538 b"(%ld::) - (%ld) - unstable() - obsolete() - public()",
1539 revs,
1539 revs,
1540 revs,
1540 revs,
1541 )
1541 )
1542 ]
1542 ]
1543 wnode = unfi[b'.'].node()
1543 wnode = unfi[b'.'].node()
1544 mapping = {} # {oldnode: [newnode]}
1544 mapping = {} # {oldnode: [newnode]}
1545 newnodes = []
1545 newnodes = []
1546
1546
1547 drevid = drevids[0]
1547 drevid = drevids[0]
1548
1548
1549 for i, rev in enumerate(revs):
1549 for i, rev in enumerate(revs):
1550 old = unfi[rev]
1550 old = unfi[rev]
1551 if not fold:
1551 if not fold:
1552 drevid = drevids[i]
1552 drevid = drevids[i]
1553 drev = [d for d in drevs if int(d[b'id']) == drevid][0]
1553 drev = [d for d in drevs if int(d[b'id']) == drevid][0]
1554
1554
1555 newdesc = get_amended_desc(drev, old, fold)
1555 newdesc = get_amended_desc(drev, old, fold)
1556 # Make sure commit message contain "Differential Revision"
1556 # Make sure commit message contain "Differential Revision"
1557 if (
1557 if (
1558 old.description() != newdesc
1558 old.description() != newdesc
1559 or old.p1().node() in mapping
1559 or old.p1().node() in mapping
1560 or old.p2().node() in mapping
1560 or old.p2().node() in mapping
1561 ):
1561 ):
1562 if old.phase() == phases.public:
1562 if old.phase() == phases.public:
1563 ui.warn(
1563 ui.warn(
1564 _(b"warning: not updating public commit %s\n")
1564 _(b"warning: not updating public commit %s\n")
1565 % scmutil.formatchangeid(old)
1565 % scmutil.formatchangeid(old)
1566 )
1566 )
1567 continue
1567 continue
1568 parents = [
1568 parents = [
1569 mapping.get(old.p1().node(), (old.p1(),))[0],
1569 mapping.get(old.p1().node(), (old.p1(),))[0],
1570 mapping.get(old.p2().node(), (old.p2(),))[0],
1570 mapping.get(old.p2().node(), (old.p2(),))[0],
1571 ]
1571 ]
1572 newdesc = rewriteutil.update_hash_refs(
1572 newdesc = rewriteutil.update_hash_refs(
1573 repo,
1573 repo,
1574 newdesc,
1574 newdesc,
1575 mapping,
1575 mapping,
1576 )
1576 )
1577 new = context.metadataonlyctx(
1577 new = context.metadataonlyctx(
1578 repo,
1578 repo,
1579 old,
1579 old,
1580 parents=parents,
1580 parents=parents,
1581 text=newdesc,
1581 text=newdesc,
1582 user=old.user(),
1582 user=old.user(),
1583 date=old.date(),
1583 date=old.date(),
1584 extra=old.extra(),
1584 extra=old.extra(),
1585 )
1585 )
1586
1586
1587 newnode = new.commit()
1587 newnode = new.commit()
1588
1588
1589 mapping[old.node()] = [newnode]
1589 mapping[old.node()] = [newnode]
1590
1590
1591 if fold:
1591 if fold:
1592 # Defer updating the (single) Diff until all nodes are
1592 # Defer updating the (single) Diff until all nodes are
1593 # collected. No tags were created, so none need to be
1593 # collected. No tags were created, so none need to be
1594 # removed.
1594 # removed.
1595 newnodes.append(newnode)
1595 newnodes.append(newnode)
1596 continue
1596 continue
1597
1597
1598 _amend_diff_properties(
1598 _amend_diff_properties(
1599 unfi, drevid, [newnode], diffmap[old.node()]
1599 unfi, drevid, [newnode], diffmap[old.node()]
1600 )
1600 )
1601
1601
1602 # Remove local tags since it's no longer necessary
1602 # Remove local tags since it's no longer necessary
1603 tagname = b'D%d' % drevid
1603 tagname = b'D%d' % drevid
1604 if tagname in repo.tags():
1604 if tagname in repo.tags():
1605 tags.tag(
1605 tags.tag(
1606 repo,
1606 repo,
1607 tagname,
1607 tagname,
1608 repo.nullid,
1608 repo.nullid,
1609 message=None,
1609 message=None,
1610 user=None,
1610 user=None,
1611 date=None,
1611 date=None,
1612 local=True,
1612 local=True,
1613 )
1613 )
1614 elif fold:
1614 elif fold:
1615 # When folding multiple commits into one review with
1615 # When folding multiple commits into one review with
1616 # --fold, track even the commits that weren't amended, so
1616 # --fold, track even the commits that weren't amended, so
1617 # that their association isn't lost if the properties are
1617 # that their association isn't lost if the properties are
1618 # rewritten below.
1618 # rewritten below.
1619 newnodes.append(old.node())
1619 newnodes.append(old.node())
1620
1620
1621 # If the submitted commits are public, no amend takes place so
1621 # If the submitted commits are public, no amend takes place so
1622 # there are no newnodes and therefore no diff update to do.
1622 # there are no newnodes and therefore no diff update to do.
1623 if fold and newnodes:
1623 if fold and newnodes:
1624 diff = diffmap[old.node()]
1624 diff = diffmap[old.node()]
1625
1625
1626 # The diff object in diffmap doesn't have the local commits
1626 # The diff object in diffmap doesn't have the local commits
1627 # because that could be returned from differential.creatediff,
1627 # because that could be returned from differential.creatediff,
1628 # not differential.querydiffs. So use the queried diff (if
1628 # not differential.querydiffs. So use the queried diff (if
1629 # present), or force the amend (a new revision is being posted.)
1629 # present), or force the amend (a new revision is being posted.)
1630 if not olddiff or set(newnodes) != getlocalcommits(olddiff):
1630 if not olddiff or set(newnodes) != getlocalcommits(olddiff):
1631 _debug(ui, b"updating local commit list for D%d\n" % drevid)
1631 _debug(ui, b"updating local commit list for D%d\n" % drevid)
1632 _amend_diff_properties(unfi, drevid, newnodes, diff)
1632 _amend_diff_properties(unfi, drevid, newnodes, diff)
1633 else:
1633 else:
1634 _debug(
1634 _debug(
1635 ui,
1635 ui,
1636 b"local commit list for D%d is already up-to-date\n"
1636 b"local commit list for D%d is already up-to-date\n"
1637 % drevid,
1637 % drevid,
1638 )
1638 )
1639 elif fold:
1639 elif fold:
1640 _debug(ui, b"no newnodes to update\n")
1640 _debug(ui, b"no newnodes to update\n")
1641
1641
1642 # Restack any children of first-time submissions that were orphaned
1642 # Restack any children of first-time submissions that were orphaned
1643 # in the process. The ctx won't report that it is an orphan until
1643 # in the process. The ctx won't report that it is an orphan until
1644 # the cleanup takes place below.
1644 # the cleanup takes place below.
1645 for old in restack:
1645 for old in restack:
1646 parents = [
1646 parents = [
1647 mapping.get(old.p1().node(), (old.p1(),))[0],
1647 mapping.get(old.p1().node(), (old.p1(),))[0],
1648 mapping.get(old.p2().node(), (old.p2(),))[0],
1648 mapping.get(old.p2().node(), (old.p2(),))[0],
1649 ]
1649 ]
1650 new = context.metadataonlyctx(
1650 new = context.metadataonlyctx(
1651 repo,
1651 repo,
1652 old,
1652 old,
1653 parents=parents,
1653 parents=parents,
1654 text=rewriteutil.update_hash_refs(
1654 text=rewriteutil.update_hash_refs(
1655 repo, old.description(), mapping
1655 repo, old.description(), mapping
1656 ),
1656 ),
1657 user=old.user(),
1657 user=old.user(),
1658 date=old.date(),
1658 date=old.date(),
1659 extra=old.extra(),
1659 extra=old.extra(),
1660 )
1660 )
1661
1661
1662 newnode = new.commit()
1662 newnode = new.commit()
1663
1663
1664 # Don't obsolete unselected descendants of nodes that have not
1664 # Don't obsolete unselected descendants of nodes that have not
1665 # been changed in this transaction- that results in an error.
1665 # been changed in this transaction- that results in an error.
1666 if newnode != old.node():
1666 if newnode != old.node():
1667 mapping[old.node()] = [newnode]
1667 mapping[old.node()] = [newnode]
1668 _debug(
1668 _debug(
1669 ui,
1669 ui,
1670 b"restabilizing %s as %s\n"
1670 b"restabilizing %s as %s\n"
1671 % (short(old.node()), short(newnode)),
1671 % (short(old.node()), short(newnode)),
1672 )
1672 )
1673 else:
1673 else:
1674 _debug(
1674 _debug(
1675 ui,
1675 ui,
1676 b"not restabilizing unchanged %s\n" % short(old.node()),
1676 b"not restabilizing unchanged %s\n" % short(old.node()),
1677 )
1677 )
1678
1678
1679 scmutil.cleanupnodes(repo, mapping, b'phabsend', fixphase=True)
1679 scmutil.cleanupnodes(repo, mapping, b'phabsend', fixphase=True)
1680 if wnode in mapping:
1680 if wnode in mapping:
1681 unfi.setparents(mapping[wnode][0])
1681 unfi.setparents(mapping[wnode][0])
1682
1682
1683
1683
1684 # Map from "hg:meta" keys to header understood by "hg import". The order is
1684 # Map from "hg:meta" keys to header understood by "hg import". The order is
1685 # consistent with "hg export" output.
1685 # consistent with "hg export" output.
1686 _metanamemap = util.sortdict(
1686 _metanamemap = util.sortdict(
1687 [
1687 [
1688 (b'user', b'User'),
1688 (b'user', b'User'),
1689 (b'date', b'Date'),
1689 (b'date', b'Date'),
1690 (b'branch', b'Branch'),
1690 (b'branch', b'Branch'),
1691 (b'node', b'Node ID'),
1691 (b'node', b'Node ID'),
1692 (b'parent', b'Parent '),
1692 (b'parent', b'Parent '),
1693 ]
1693 ]
1694 )
1694 )
1695
1695
1696
1696
1697 def _confirmbeforesend(repo, revs, oldmap):
1697 def _confirmbeforesend(repo, revs, oldmap):
1698 url, token = readurltoken(repo.ui)
1698 url, token = readurltoken(repo.ui)
1699 ui = repo.ui
1699 ui = repo.ui
1700 for rev in revs:
1700 for rev in revs:
1701 ctx = repo[rev]
1701 ctx = repo[rev]
1702 oldnode, olddiff, drevid = oldmap.get(ctx.node(), (None, None, None))
1702 oldnode, olddiff, drevid = oldmap.get(ctx.node(), (None, None, None))
1703 if drevid:
1703 if drevid:
1704 drevdesc = ui.label(b'D%d' % drevid, b'phabricator.drev')
1704 drevdesc = ui.label(b'D%d' % drevid, b'phabricator.drev')
1705 else:
1705 else:
1706 drevdesc = ui.label(_(b'NEW'), b'phabricator.drev')
1706 drevdesc = ui.label(_(b'NEW'), b'phabricator.drev')
1707
1707
1708 ui.write(
1708 ui.write(
1709 _(b'%s - %s\n')
1709 _(b'%s - %s\n')
1710 % (
1710 % (
1711 drevdesc,
1711 drevdesc,
1712 cmdutil.format_changeset_summary(ui, ctx, b'phabsend'),
1712 cmdutil.format_changeset_summary(ui, ctx, b'phabsend'),
1713 )
1713 )
1714 )
1714 )
1715
1715
1716 if ui.promptchoice(
1716 if ui.promptchoice(
1717 _(b'Send the above changes to %s (Y/n)?$$ &Yes $$ &No') % url
1717 _(b'Send the above changes to %s (Y/n)?$$ &Yes $$ &No') % url
1718 ):
1718 ):
1719 return False
1719 return False
1720
1720
1721 return True
1721 return True
1722
1722
1723
1723
1724 _knownstatusnames = {
1724 _knownstatusnames = {
1725 b'accepted',
1725 b'accepted',
1726 b'needsreview',
1726 b'needsreview',
1727 b'needsrevision',
1727 b'needsrevision',
1728 b'closed',
1728 b'closed',
1729 b'abandoned',
1729 b'abandoned',
1730 b'changesplanned',
1730 b'changesplanned',
1731 }
1731 }
1732
1732
1733
1733
1734 def _getstatusname(drev):
1734 def _getstatusname(drev):
1735 """get normalized status name from a Differential Revision"""
1735 """get normalized status name from a Differential Revision"""
1736 return drev[b'statusName'].replace(b' ', b'').lower()
1736 return drev[b'statusName'].replace(b' ', b'').lower()
1737
1737
1738
1738
1739 # Small language to specify differential revisions. Support symbols: (), :X,
1739 # Small language to specify differential revisions. Support symbols: (), :X,
1740 # +, and -.
1740 # +, and -.
1741
1741
1742 _elements = {
1742 _elements = {
1743 # token-type: binding-strength, primary, prefix, infix, suffix
1743 # token-type: binding-strength, primary, prefix, infix, suffix
1744 b'(': (12, None, (b'group', 1, b')'), None, None),
1744 b'(': (12, None, (b'group', 1, b')'), None, None),
1745 b':': (8, None, (b'ancestors', 8), None, None),
1745 b':': (8, None, (b'ancestors', 8), None, None),
1746 b'&': (5, None, None, (b'and_', 5), None),
1746 b'&': (5, None, None, (b'and_', 5), None),
1747 b'+': (4, None, None, (b'add', 4), None),
1747 b'+': (4, None, None, (b'add', 4), None),
1748 b'-': (4, None, None, (b'sub', 4), None),
1748 b'-': (4, None, None, (b'sub', 4), None),
1749 b')': (0, None, None, None, None),
1749 b')': (0, None, None, None, None),
1750 b'symbol': (0, b'symbol', None, None, None),
1750 b'symbol': (0, b'symbol', None, None, None),
1751 b'end': (0, None, None, None, None),
1751 b'end': (0, None, None, None, None),
1752 }
1752 }
1753
1753
1754
1754
1755 def _tokenize(text):
1755 def _tokenize(text):
1756 view = memoryview(text) # zero-copy slice
1756 view = memoryview(text) # zero-copy slice
1757 special = b'():+-& '
1757 special = b'():+-& '
1758 pos = 0
1758 pos = 0
1759 length = len(text)
1759 length = len(text)
1760 while pos < length:
1760 while pos < length:
1761 symbol = b''.join(
1761 symbol = b''.join(
1762 itertools.takewhile(
1762 itertools.takewhile(
1763 lambda ch: ch not in special, pycompat.iterbytestr(view[pos:])
1763 lambda ch: ch not in special, pycompat.iterbytestr(view[pos:])
1764 )
1764 )
1765 )
1765 )
1766 if symbol:
1766 if symbol:
1767 yield (b'symbol', symbol, pos)
1767 yield (b'symbol', symbol, pos)
1768 pos += len(symbol)
1768 pos += len(symbol)
1769 else: # special char, ignore space
1769 else: # special char, ignore space
1770 if text[pos : pos + 1] != b' ':
1770 if text[pos : pos + 1] != b' ':
1771 yield (text[pos : pos + 1], None, pos)
1771 yield (text[pos : pos + 1], None, pos)
1772 pos += 1
1772 pos += 1
1773 yield (b'end', None, pos)
1773 yield (b'end', None, pos)
1774
1774
1775
1775
1776 def _parse(text):
1776 def _parse(text):
1777 tree, pos = parser.parser(_elements).parse(_tokenize(text))
1777 tree, pos = parser.parser(_elements).parse(_tokenize(text))
1778 if pos != len(text):
1778 if pos != len(text):
1779 raise error.ParseError(b'invalid token', pos)
1779 raise error.ParseError(b'invalid token', pos)
1780 return tree
1780 return tree
1781
1781
1782
1782
1783 def _parsedrev(symbol):
1783 def _parsedrev(symbol):
1784 """str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None"""
1784 """str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None"""
1785 if symbol.startswith(b'D') and symbol[1:].isdigit():
1785 if symbol.startswith(b'D') and symbol[1:].isdigit():
1786 return int(symbol[1:])
1786 return int(symbol[1:])
1787 if symbol.isdigit():
1787 if symbol.isdigit():
1788 return int(symbol)
1788 return int(symbol)
1789
1789
1790
1790
1791 def _prefetchdrevs(tree):
1791 def _prefetchdrevs(tree):
1792 """return ({single-drev-id}, {ancestor-drev-id}) to prefetch"""
1792 """return ({single-drev-id}, {ancestor-drev-id}) to prefetch"""
1793 drevs = set()
1793 drevs = set()
1794 ancestordrevs = set()
1794 ancestordrevs = set()
1795 op = tree[0]
1795 op = tree[0]
1796 if op == b'symbol':
1796 if op == b'symbol':
1797 r = _parsedrev(tree[1])
1797 r = _parsedrev(tree[1])
1798 if r:
1798 if r:
1799 drevs.add(r)
1799 drevs.add(r)
1800 elif op == b'ancestors':
1800 elif op == b'ancestors':
1801 r, a = _prefetchdrevs(tree[1])
1801 r, a = _prefetchdrevs(tree[1])
1802 drevs.update(r)
1802 drevs.update(r)
1803 ancestordrevs.update(r)
1803 ancestordrevs.update(r)
1804 ancestordrevs.update(a)
1804 ancestordrevs.update(a)
1805 else:
1805 else:
1806 for t in tree[1:]:
1806 for t in tree[1:]:
1807 r, a = _prefetchdrevs(t)
1807 r, a = _prefetchdrevs(t)
1808 drevs.update(r)
1808 drevs.update(r)
1809 ancestordrevs.update(a)
1809 ancestordrevs.update(a)
1810 return drevs, ancestordrevs
1810 return drevs, ancestordrevs
1811
1811
1812
1812
1813 def querydrev(ui, spec):
1813 def querydrev(ui, spec):
1814 """return a list of "Differential Revision" dicts
1814 """return a list of "Differential Revision" dicts
1815
1815
1816 spec is a string using a simple query language, see docstring in phabread
1816 spec is a string using a simple query language, see docstring in phabread
1817 for details.
1817 for details.
1818
1818
1819 A "Differential Revision dict" looks like:
1819 A "Differential Revision dict" looks like:
1820
1820
1821 {
1821 {
1822 "activeDiffPHID": "PHID-DIFF-xoqnjkobbm6k4dk6hi72",
1822 "activeDiffPHID": "PHID-DIFF-xoqnjkobbm6k4dk6hi72",
1823 "authorPHID": "PHID-USER-tv3ohwc4v4jeu34otlye",
1823 "authorPHID": "PHID-USER-tv3ohwc4v4jeu34otlye",
1824 "auxiliary": {
1824 "auxiliary": {
1825 "phabricator:depends-on": [
1825 "phabricator:depends-on": [
1826 "PHID-DREV-gbapp366kutjebt7agcd"
1826 "PHID-DREV-gbapp366kutjebt7agcd"
1827 ]
1827 ]
1828 "phabricator:projects": [],
1828 "phabricator:projects": [],
1829 },
1829 },
1830 "branch": "default",
1830 "branch": "default",
1831 "ccs": [],
1831 "ccs": [],
1832 "commits": [],
1832 "commits": [],
1833 "dateCreated": "1499181406",
1833 "dateCreated": "1499181406",
1834 "dateModified": "1499182103",
1834 "dateModified": "1499182103",
1835 "diffs": [
1835 "diffs": [
1836 "3",
1836 "3",
1837 "4",
1837 "4",
1838 ],
1838 ],
1839 "hashes": [],
1839 "hashes": [],
1840 "id": "2",
1840 "id": "2",
1841 "lineCount": "2",
1841 "lineCount": "2",
1842 "phid": "PHID-DREV-672qvysjcczopag46qty",
1842 "phid": "PHID-DREV-672qvysjcczopag46qty",
1843 "properties": {},
1843 "properties": {},
1844 "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv",
1844 "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv",
1845 "reviewers": [],
1845 "reviewers": [],
1846 "sourcePath": null
1846 "sourcePath": null
1847 "status": "0",
1847 "status": "0",
1848 "statusName": "Needs Review",
1848 "statusName": "Needs Review",
1849 "summary": "",
1849 "summary": "",
1850 "testPlan": "",
1850 "testPlan": "",
1851 "title": "example",
1851 "title": "example",
1852 "uri": "https://phab.example.com/D2",
1852 "uri": "https://phab.example.com/D2",
1853 }
1853 }
1854 """
1854 """
1855 # TODO: replace differential.query and differential.querydiffs with
1855 # TODO: replace differential.query and differential.querydiffs with
1856 # differential.diff.search because the former (and their output) are
1856 # differential.diff.search because the former (and their output) are
1857 # frozen, and planned to be deprecated and removed.
1857 # frozen, and planned to be deprecated and removed.
1858
1858
1859 def fetch(params):
1859 def fetch(params):
1860 """params -> single drev or None"""
1860 """params -> single drev or None"""
1861 key = (params.get(b'ids') or params.get(b'phids') or [None])[0]
1861 key = (params.get(b'ids') or params.get(b'phids') or [None])[0]
1862 if key in prefetched:
1862 if key in prefetched:
1863 return prefetched[key]
1863 return prefetched[key]
1864 drevs = callconduit(ui, b'differential.query', params)
1864 drevs = callconduit(ui, b'differential.query', params)
1865 # Fill prefetched with the result
1865 # Fill prefetched with the result
1866 for drev in drevs:
1866 for drev in drevs:
1867 prefetched[drev[b'phid']] = drev
1867 prefetched[drev[b'phid']] = drev
1868 prefetched[int(drev[b'id'])] = drev
1868 prefetched[int(drev[b'id'])] = drev
1869 if key not in prefetched:
1869 if key not in prefetched:
1870 raise error.Abort(
1870 raise error.Abort(
1871 _(b'cannot get Differential Revision %r') % params
1871 _(b'cannot get Differential Revision %r') % params
1872 )
1872 )
1873 return prefetched[key]
1873 return prefetched[key]
1874
1874
1875 def getstack(topdrevids):
1875 def getstack(topdrevids):
1876 """given a top, get a stack from the bottom, [id] -> [id]"""
1876 """given a top, get a stack from the bottom, [id] -> [id]"""
1877 visited = set()
1877 visited = set()
1878 result = []
1878 result = []
1879 queue = [{b'ids': [i]} for i in topdrevids]
1879 queue = [{b'ids': [i]} for i in topdrevids]
1880 while queue:
1880 while queue:
1881 params = queue.pop()
1881 params = queue.pop()
1882 drev = fetch(params)
1882 drev = fetch(params)
1883 if drev[b'id'] in visited:
1883 if drev[b'id'] in visited:
1884 continue
1884 continue
1885 visited.add(drev[b'id'])
1885 visited.add(drev[b'id'])
1886 result.append(int(drev[b'id']))
1886 result.append(int(drev[b'id']))
1887 auxiliary = drev.get(b'auxiliary', {})
1887 auxiliary = drev.get(b'auxiliary', {})
1888 depends = auxiliary.get(b'phabricator:depends-on', [])
1888 depends = auxiliary.get(b'phabricator:depends-on', [])
1889 for phid in depends:
1889 for phid in depends:
1890 queue.append({b'phids': [phid]})
1890 queue.append({b'phids': [phid]})
1891 result.reverse()
1891 result.reverse()
1892 return smartset.baseset(result)
1892 return smartset.baseset(result)
1893
1893
1894 # Initialize prefetch cache
1894 # Initialize prefetch cache
1895 prefetched = {} # {id or phid: drev}
1895 prefetched = {} # {id or phid: drev}
1896
1896
1897 tree = _parse(spec)
1897 tree = _parse(spec)
1898 drevs, ancestordrevs = _prefetchdrevs(tree)
1898 drevs, ancestordrevs = _prefetchdrevs(tree)
1899
1899
1900 # developer config: phabricator.batchsize
1900 # developer config: phabricator.batchsize
1901 batchsize = ui.configint(b'phabricator', b'batchsize')
1901 batchsize = ui.configint(b'phabricator', b'batchsize')
1902
1902
1903 # Prefetch Differential Revisions in batch
1903 # Prefetch Differential Revisions in batch
1904 tofetch = set(drevs)
1904 tofetch = set(drevs)
1905 for r in ancestordrevs:
1905 for r in ancestordrevs:
1906 tofetch.update(range(max(1, r - batchsize), r + 1))
1906 tofetch.update(range(max(1, r - batchsize), r + 1))
1907 if drevs:
1907 if drevs:
1908 fetch({b'ids': list(tofetch)})
1908 fetch({b'ids': list(tofetch)})
1909 validids = sorted(set(getstack(list(ancestordrevs))) | set(drevs))
1909 validids = sorted(set(getstack(list(ancestordrevs))) | set(drevs))
1910
1910
1911 # Walk through the tree, return smartsets
1911 # Walk through the tree, return smartsets
1912 def walk(tree):
1912 def walk(tree):
1913 op = tree[0]
1913 op = tree[0]
1914 if op == b'symbol':
1914 if op == b'symbol':
1915 drev = _parsedrev(tree[1])
1915 drev = _parsedrev(tree[1])
1916 if drev:
1916 if drev:
1917 return smartset.baseset([drev])
1917 return smartset.baseset([drev])
1918 elif tree[1] in _knownstatusnames:
1918 elif tree[1] in _knownstatusnames:
1919 drevs = [
1919 drevs = [
1920 r
1920 r
1921 for r in validids
1921 for r in validids
1922 if _getstatusname(prefetched[r]) == tree[1]
1922 if _getstatusname(prefetched[r]) == tree[1]
1923 ]
1923 ]
1924 return smartset.baseset(drevs)
1924 return smartset.baseset(drevs)
1925 else:
1925 else:
1926 raise error.Abort(_(b'unknown symbol: %s') % tree[1])
1926 raise error.Abort(_(b'unknown symbol: %s') % tree[1])
1927 elif op in {b'and_', b'add', b'sub'}:
1927 elif op in {b'and_', b'add', b'sub'}:
1928 assert len(tree) == 3
1928 assert len(tree) == 3
1929 return getattr(operator, op)(walk(tree[1]), walk(tree[2]))
1929 return getattr(operator, pycompat.sysstr(op))(
1930 walk(tree[1]), walk(tree[2])
1931 )
1930 elif op == b'group':
1932 elif op == b'group':
1931 return walk(tree[1])
1933 return walk(tree[1])
1932 elif op == b'ancestors':
1934 elif op == b'ancestors':
1933 return getstack(walk(tree[1]))
1935 return getstack(walk(tree[1]))
1934 else:
1936 else:
1935 raise error.ProgrammingError(b'illegal tree: %r' % tree)
1937 raise error.ProgrammingError(b'illegal tree: %r' % tree)
1936
1938
1937 return [prefetched[r] for r in walk(tree)]
1939 return [prefetched[r] for r in walk(tree)]
1938
1940
1939
1941
1940 def getdescfromdrev(drev):
1942 def getdescfromdrev(drev):
1941 """get description (commit message) from "Differential Revision"
1943 """get description (commit message) from "Differential Revision"
1942
1944
1943 This is similar to differential.getcommitmessage API. But we only care
1945 This is similar to differential.getcommitmessage API. But we only care
1944 about limited fields: title, summary, test plan, and URL.
1946 about limited fields: title, summary, test plan, and URL.
1945 """
1947 """
1946 title = drev[b'title']
1948 title = drev[b'title']
1947 summary = drev[b'summary'].rstrip()
1949 summary = drev[b'summary'].rstrip()
1948 testplan = drev[b'testPlan'].rstrip()
1950 testplan = drev[b'testPlan'].rstrip()
1949 if testplan:
1951 if testplan:
1950 testplan = b'Test Plan:\n%s' % testplan
1952 testplan = b'Test Plan:\n%s' % testplan
1951 uri = b'Differential Revision: %s' % drev[b'uri']
1953 uri = b'Differential Revision: %s' % drev[b'uri']
1952 return b'\n\n'.join(filter(None, [title, summary, testplan, uri]))
1954 return b'\n\n'.join(filter(None, [title, summary, testplan, uri]))
1953
1955
1954
1956
1955 def get_amended_desc(drev, ctx, folded):
1957 def get_amended_desc(drev, ctx, folded):
1956 """similar to ``getdescfromdrev``, but supports a folded series of commits
1958 """similar to ``getdescfromdrev``, but supports a folded series of commits
1957
1959
1958 This is used when determining if an individual commit needs to have its
1960 This is used when determining if an individual commit needs to have its
1959 message amended after posting it for review. The determination is made for
1961 message amended after posting it for review. The determination is made for
1960 each individual commit, even when they were folded into one review.
1962 each individual commit, even when they were folded into one review.
1961 """
1963 """
1962 if not folded:
1964 if not folded:
1963 return getdescfromdrev(drev)
1965 return getdescfromdrev(drev)
1964
1966
1965 uri = b'Differential Revision: %s' % drev[b'uri']
1967 uri = b'Differential Revision: %s' % drev[b'uri']
1966
1968
1967 # Since the commit messages were combined when posting multiple commits
1969 # Since the commit messages were combined when posting multiple commits
1968 # with --fold, the fields can't be read from Phabricator here, or *all*
1970 # with --fold, the fields can't be read from Phabricator here, or *all*
1969 # affected local revisions will end up with the same commit message after
1971 # affected local revisions will end up with the same commit message after
1970 # the URI is amended in. Append in the DREV line, or update it if it
1972 # the URI is amended in. Append in the DREV line, or update it if it
1971 # exists. At worst, this means commit message or test plan updates on
1973 # exists. At worst, this means commit message or test plan updates on
1972 # Phabricator aren't propagated back to the repository, but that seems
1974 # Phabricator aren't propagated back to the repository, but that seems
1973 # reasonable for the case where local commits are effectively combined
1975 # reasonable for the case where local commits are effectively combined
1974 # in Phabricator.
1976 # in Phabricator.
1975 m = _differentialrevisiondescre.search(ctx.description())
1977 m = _differentialrevisiondescre.search(ctx.description())
1976 if not m:
1978 if not m:
1977 return b'\n\n'.join([ctx.description(), uri])
1979 return b'\n\n'.join([ctx.description(), uri])
1978
1980
1979 return _differentialrevisiondescre.sub(uri, ctx.description())
1981 return _differentialrevisiondescre.sub(uri, ctx.description())
1980
1982
1981
1983
1982 def getlocalcommits(diff):
1984 def getlocalcommits(diff):
1983 """get the set of local commits from a diff object
1985 """get the set of local commits from a diff object
1984
1986
1985 See ``getdiffmeta()`` for an example diff object.
1987 See ``getdiffmeta()`` for an example diff object.
1986 """
1988 """
1987 props = diff.get(b'properties') or {}
1989 props = diff.get(b'properties') or {}
1988 commits = props.get(b'local:commits') or {}
1990 commits = props.get(b'local:commits') or {}
1989 if len(commits) > 1:
1991 if len(commits) > 1:
1990 return {bin(c) for c in commits.keys()}
1992 return {bin(c) for c in commits.keys()}
1991
1993
1992 # Storing the diff metadata predates storing `local:commits`, so continue
1994 # Storing the diff metadata predates storing `local:commits`, so continue
1993 # to use that in the --no-fold case.
1995 # to use that in the --no-fold case.
1994 return {bin(getdiffmeta(diff).get(b'node', b'')) or None}
1996 return {bin(getdiffmeta(diff).get(b'node', b'')) or None}
1995
1997
1996
1998
1997 def getdiffmeta(diff):
1999 def getdiffmeta(diff):
1998 """get commit metadata (date, node, user, p1) from a diff object
2000 """get commit metadata (date, node, user, p1) from a diff object
1999
2001
2000 The metadata could be "hg:meta", sent by phabsend, like:
2002 The metadata could be "hg:meta", sent by phabsend, like:
2001
2003
2002 "properties": {
2004 "properties": {
2003 "hg:meta": {
2005 "hg:meta": {
2004 "branch": "default",
2006 "branch": "default",
2005 "date": "1499571514 25200",
2007 "date": "1499571514 25200",
2006 "node": "98c08acae292b2faf60a279b4189beb6cff1414d",
2008 "node": "98c08acae292b2faf60a279b4189beb6cff1414d",
2007 "user": "Foo Bar <foo@example.com>",
2009 "user": "Foo Bar <foo@example.com>",
2008 "parent": "6d0abad76b30e4724a37ab8721d630394070fe16"
2010 "parent": "6d0abad76b30e4724a37ab8721d630394070fe16"
2009 }
2011 }
2010 }
2012 }
2011
2013
2012 Or converted from "local:commits", sent by "arc", like:
2014 Or converted from "local:commits", sent by "arc", like:
2013
2015
2014 "properties": {
2016 "properties": {
2015 "local:commits": {
2017 "local:commits": {
2016 "98c08acae292b2faf60a279b4189beb6cff1414d": {
2018 "98c08acae292b2faf60a279b4189beb6cff1414d": {
2017 "author": "Foo Bar",
2019 "author": "Foo Bar",
2018 "authorEmail": "foo@example.com"
2020 "authorEmail": "foo@example.com"
2019 "branch": "default",
2021 "branch": "default",
2020 "commit": "98c08acae292b2faf60a279b4189beb6cff1414d",
2022 "commit": "98c08acae292b2faf60a279b4189beb6cff1414d",
2021 "local": "1000",
2023 "local": "1000",
2022 "message": "...",
2024 "message": "...",
2023 "parents": ["6d0abad76b30e4724a37ab8721d630394070fe16"],
2025 "parents": ["6d0abad76b30e4724a37ab8721d630394070fe16"],
2024 "rev": "98c08acae292b2faf60a279b4189beb6cff1414d",
2026 "rev": "98c08acae292b2faf60a279b4189beb6cff1414d",
2025 "summary": "...",
2027 "summary": "...",
2026 "tag": "",
2028 "tag": "",
2027 "time": 1499546314,
2029 "time": 1499546314,
2028 }
2030 }
2029 }
2031 }
2030 }
2032 }
2031
2033
2032 Note: metadata extracted from "local:commits" will lose time zone
2034 Note: metadata extracted from "local:commits" will lose time zone
2033 information.
2035 information.
2034 """
2036 """
2035 props = diff.get(b'properties') or {}
2037 props = diff.get(b'properties') or {}
2036 meta = props.get(b'hg:meta')
2038 meta = props.get(b'hg:meta')
2037 if not meta:
2039 if not meta:
2038 if props.get(b'local:commits'):
2040 if props.get(b'local:commits'):
2039 commit = sorted(props[b'local:commits'].values())[0]
2041 commit = sorted(props[b'local:commits'].values())[0]
2040 meta = {}
2042 meta = {}
2041 if b'author' in commit and b'authorEmail' in commit:
2043 if b'author' in commit and b'authorEmail' in commit:
2042 meta[b'user'] = b'%s <%s>' % (
2044 meta[b'user'] = b'%s <%s>' % (
2043 commit[b'author'],
2045 commit[b'author'],
2044 commit[b'authorEmail'],
2046 commit[b'authorEmail'],
2045 )
2047 )
2046 if b'time' in commit:
2048 if b'time' in commit:
2047 meta[b'date'] = b'%d 0' % int(commit[b'time'])
2049 meta[b'date'] = b'%d 0' % int(commit[b'time'])
2048 if b'branch' in commit:
2050 if b'branch' in commit:
2049 meta[b'branch'] = commit[b'branch']
2051 meta[b'branch'] = commit[b'branch']
2050 node = commit.get(b'commit', commit.get(b'rev'))
2052 node = commit.get(b'commit', commit.get(b'rev'))
2051 if node:
2053 if node:
2052 meta[b'node'] = node
2054 meta[b'node'] = node
2053 if len(commit.get(b'parents', ())) >= 1:
2055 if len(commit.get(b'parents', ())) >= 1:
2054 meta[b'parent'] = commit[b'parents'][0]
2056 meta[b'parent'] = commit[b'parents'][0]
2055 else:
2057 else:
2056 meta = {}
2058 meta = {}
2057 if b'date' not in meta and b'dateCreated' in diff:
2059 if b'date' not in meta and b'dateCreated' in diff:
2058 meta[b'date'] = b'%s 0' % diff[b'dateCreated']
2060 meta[b'date'] = b'%s 0' % diff[b'dateCreated']
2059 if b'branch' not in meta and diff.get(b'branch'):
2061 if b'branch' not in meta and diff.get(b'branch'):
2060 meta[b'branch'] = diff[b'branch']
2062 meta[b'branch'] = diff[b'branch']
2061 if b'parent' not in meta and diff.get(b'sourceControlBaseRevision'):
2063 if b'parent' not in meta and diff.get(b'sourceControlBaseRevision'):
2062 meta[b'parent'] = diff[b'sourceControlBaseRevision']
2064 meta[b'parent'] = diff[b'sourceControlBaseRevision']
2063 return meta
2065 return meta
2064
2066
2065
2067
2066 def _getdrevs(ui, stack, specs):
2068 def _getdrevs(ui, stack, specs):
2067 """convert user supplied DREVSPECs into "Differential Revision" dicts
2069 """convert user supplied DREVSPECs into "Differential Revision" dicts
2068
2070
2069 See ``hg help phabread`` for how to specify each DREVSPEC.
2071 See ``hg help phabread`` for how to specify each DREVSPEC.
2070 """
2072 """
2071 if len(specs) > 0:
2073 if len(specs) > 0:
2072
2074
2073 def _formatspec(s):
2075 def _formatspec(s):
2074 if stack:
2076 if stack:
2075 s = b':(%s)' % s
2077 s = b':(%s)' % s
2076 return b'(%s)' % s
2078 return b'(%s)' % s
2077
2079
2078 spec = b'+'.join(pycompat.maplist(_formatspec, specs))
2080 spec = b'+'.join(pycompat.maplist(_formatspec, specs))
2079
2081
2080 drevs = querydrev(ui, spec)
2082 drevs = querydrev(ui, spec)
2081 if drevs:
2083 if drevs:
2082 return drevs
2084 return drevs
2083
2085
2084 raise error.Abort(_(b"empty DREVSPEC set"))
2086 raise error.Abort(_(b"empty DREVSPEC set"))
2085
2087
2086
2088
2087 def readpatch(ui, drevs, write):
2089 def readpatch(ui, drevs, write):
2088 """generate plain-text patch readable by 'hg import'
2090 """generate plain-text patch readable by 'hg import'
2089
2091
2090 write takes a list of (DREV, bytes), where DREV is the differential number
2092 write takes a list of (DREV, bytes), where DREV is the differential number
2091 (as bytes, without the "D" prefix) and the bytes are the text of a patch
2093 (as bytes, without the "D" prefix) and the bytes are the text of a patch
2092 to be imported. drevs is what "querydrev" returns, results of
2094 to be imported. drevs is what "querydrev" returns, results of
2093 "differential.query".
2095 "differential.query".
2094 """
2096 """
2095 # Prefetch hg:meta property for all diffs
2097 # Prefetch hg:meta property for all diffs
2096 diffids = sorted({max(int(v) for v in drev[b'diffs']) for drev in drevs})
2098 diffids = sorted({max(int(v) for v in drev[b'diffs']) for drev in drevs})
2097 diffs = callconduit(ui, b'differential.querydiffs', {b'ids': diffids})
2099 diffs = callconduit(ui, b'differential.querydiffs', {b'ids': diffids})
2098
2100
2099 patches = []
2101 patches = []
2100
2102
2101 # Generate patch for each drev
2103 # Generate patch for each drev
2102 for drev in drevs:
2104 for drev in drevs:
2103 ui.note(_(b'reading D%s\n') % drev[b'id'])
2105 ui.note(_(b'reading D%s\n') % drev[b'id'])
2104
2106
2105 diffid = max(int(v) for v in drev[b'diffs'])
2107 diffid = max(int(v) for v in drev[b'diffs'])
2106 body = callconduit(ui, b'differential.getrawdiff', {b'diffID': diffid})
2108 body = callconduit(ui, b'differential.getrawdiff', {b'diffID': diffid})
2107 desc = getdescfromdrev(drev)
2109 desc = getdescfromdrev(drev)
2108 header = b'# HG changeset patch\n'
2110 header = b'# HG changeset patch\n'
2109
2111
2110 # Try to preserve metadata from hg:meta property. Write hg patch
2112 # Try to preserve metadata from hg:meta property. Write hg patch
2111 # headers that can be read by the "import" command. See patchheadermap
2113 # headers that can be read by the "import" command. See patchheadermap
2112 # and extract in mercurial/patch.py for supported headers.
2114 # and extract in mercurial/patch.py for supported headers.
2113 meta = getdiffmeta(diffs[b'%d' % diffid])
2115 meta = getdiffmeta(diffs[b'%d' % diffid])
2114 for k in _metanamemap.keys():
2116 for k in _metanamemap.keys():
2115 if k in meta:
2117 if k in meta:
2116 header += b'# %s %s\n' % (_metanamemap[k], meta[k])
2118 header += b'# %s %s\n' % (_metanamemap[k], meta[k])
2117
2119
2118 content = b'%s%s\n%s' % (header, desc, body)
2120 content = b'%s%s\n%s' % (header, desc, body)
2119 patches.append((drev[b'id'], content))
2121 patches.append((drev[b'id'], content))
2120
2122
2121 # Write patches to the supplied callback
2123 # Write patches to the supplied callback
2122 write(patches)
2124 write(patches)
2123
2125
2124
2126
2125 @vcrcommand(
2127 @vcrcommand(
2126 b'phabread',
2128 b'phabread',
2127 [(b'', b'stack', False, _(b'read dependencies'))],
2129 [(b'', b'stack', False, _(b'read dependencies'))],
2128 _(b'DREVSPEC... [OPTIONS]'),
2130 _(b'DREVSPEC... [OPTIONS]'),
2129 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2131 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2130 optionalrepo=True,
2132 optionalrepo=True,
2131 )
2133 )
2132 def phabread(ui, repo, *specs, **opts):
2134 def phabread(ui, repo, *specs, **opts):
2133 """print patches from Phabricator suitable for importing
2135 """print patches from Phabricator suitable for importing
2134
2136
2135 DREVSPEC could be a Differential Revision identity, like ``D123``, or just
2137 DREVSPEC could be a Differential Revision identity, like ``D123``, or just
2136 the number ``123``. It could also have common operators like ``+``, ``-``,
2138 the number ``123``. It could also have common operators like ``+``, ``-``,
2137 ``&``, ``(``, ``)`` for complex queries. Prefix ``:`` could be used to
2139 ``&``, ``(``, ``)`` for complex queries. Prefix ``:`` could be used to
2138 select a stack. If multiple DREVSPEC values are given, the result is the
2140 select a stack. If multiple DREVSPEC values are given, the result is the
2139 union of each individually evaluated value. No attempt is currently made
2141 union of each individually evaluated value. No attempt is currently made
2140 to reorder the values to run from parent to child.
2142 to reorder the values to run from parent to child.
2141
2143
2142 ``abandoned``, ``accepted``, ``closed``, ``needsreview``, ``needsrevision``
2144 ``abandoned``, ``accepted``, ``closed``, ``needsreview``, ``needsrevision``
2143 could be used to filter patches by status. For performance reason, they
2145 could be used to filter patches by status. For performance reason, they
2144 only represent a subset of non-status selections and cannot be used alone.
2146 only represent a subset of non-status selections and cannot be used alone.
2145
2147
2146 For example, ``:D6+8-(2+D4)`` selects a stack up to D6, plus D8 and exclude
2148 For example, ``:D6+8-(2+D4)`` selects a stack up to D6, plus D8 and exclude
2147 D2 and D4. ``:D9 & needsreview`` selects "Needs Review" revisions in a
2149 D2 and D4. ``:D9 & needsreview`` selects "Needs Review" revisions in a
2148 stack up to D9.
2150 stack up to D9.
2149
2151
2150 If --stack is given, follow dependencies information and read all patches.
2152 If --stack is given, follow dependencies information and read all patches.
2151 It is equivalent to the ``:`` operator.
2153 It is equivalent to the ``:`` operator.
2152 """
2154 """
2153 opts = pycompat.byteskwargs(opts)
2155 opts = pycompat.byteskwargs(opts)
2154 drevs = _getdrevs(ui, opts.get(b'stack'), specs)
2156 drevs = _getdrevs(ui, opts.get(b'stack'), specs)
2155
2157
2156 def _write(patches):
2158 def _write(patches):
2157 for drev, content in patches:
2159 for drev, content in patches:
2158 ui.write(content)
2160 ui.write(content)
2159
2161
2160 readpatch(ui, drevs, _write)
2162 readpatch(ui, drevs, _write)
2161
2163
2162
2164
2163 @vcrcommand(
2165 @vcrcommand(
2164 b'phabimport',
2166 b'phabimport',
2165 [(b'', b'stack', False, _(b'import dependencies as well'))],
2167 [(b'', b'stack', False, _(b'import dependencies as well'))],
2166 _(b'DREVSPEC... [OPTIONS]'),
2168 _(b'DREVSPEC... [OPTIONS]'),
2167 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2169 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2168 )
2170 )
2169 def phabimport(ui, repo, *specs, **opts):
2171 def phabimport(ui, repo, *specs, **opts):
2170 """import patches from Phabricator for the specified Differential Revisions
2172 """import patches from Phabricator for the specified Differential Revisions
2171
2173
2172 The patches are read and applied starting at the parent of the working
2174 The patches are read and applied starting at the parent of the working
2173 directory.
2175 directory.
2174
2176
2175 See ``hg help phabread`` for how to specify DREVSPEC.
2177 See ``hg help phabread`` for how to specify DREVSPEC.
2176 """
2178 """
2177 opts = pycompat.byteskwargs(opts)
2179 opts = pycompat.byteskwargs(opts)
2178
2180
2179 # --bypass avoids losing exec and symlink bits when importing on Windows,
2181 # --bypass avoids losing exec and symlink bits when importing on Windows,
2180 # and allows importing with a dirty wdir. It also aborts instead of leaving
2182 # and allows importing with a dirty wdir. It also aborts instead of leaving
2181 # rejects.
2183 # rejects.
2182 opts[b'bypass'] = True
2184 opts[b'bypass'] = True
2183
2185
2184 # Mandatory default values, synced with commands.import
2186 # Mandatory default values, synced with commands.import
2185 opts[b'strip'] = 1
2187 opts[b'strip'] = 1
2186 opts[b'prefix'] = b''
2188 opts[b'prefix'] = b''
2187 # Evolve 9.3.0 assumes this key is present in cmdutil.tryimportone()
2189 # Evolve 9.3.0 assumes this key is present in cmdutil.tryimportone()
2188 opts[b'obsolete'] = False
2190 opts[b'obsolete'] = False
2189
2191
2190 if ui.configbool(b'phabimport', b'secret'):
2192 if ui.configbool(b'phabimport', b'secret'):
2191 opts[b'secret'] = True
2193 opts[b'secret'] = True
2192 if ui.configbool(b'phabimport', b'obsolete'):
2194 if ui.configbool(b'phabimport', b'obsolete'):
2193 opts[b'obsolete'] = True # Handled by evolve wrapping tryimportone()
2195 opts[b'obsolete'] = True # Handled by evolve wrapping tryimportone()
2194
2196
2195 def _write(patches):
2197 def _write(patches):
2196 parents = repo[None].parents()
2198 parents = repo[None].parents()
2197
2199
2198 with repo.wlock(), repo.lock(), repo.transaction(b'phabimport'):
2200 with repo.wlock(), repo.lock(), repo.transaction(b'phabimport'):
2199 for drev, contents in patches:
2201 for drev, contents in patches:
2200 ui.status(_(b'applying patch from D%s\n') % drev)
2202 ui.status(_(b'applying patch from D%s\n') % drev)
2201
2203
2202 with patch.extract(ui, io.BytesIO(contents)) as patchdata:
2204 with patch.extract(ui, io.BytesIO(contents)) as patchdata:
2203 msg, node, rej = cmdutil.tryimportone(
2205 msg, node, rej = cmdutil.tryimportone(
2204 ui,
2206 ui,
2205 repo,
2207 repo,
2206 patchdata,
2208 patchdata,
2207 parents,
2209 parents,
2208 opts,
2210 opts,
2209 [],
2211 [],
2210 None, # Never update wdir to another revision
2212 None, # Never update wdir to another revision
2211 )
2213 )
2212
2214
2213 if not node:
2215 if not node:
2214 raise error.Abort(_(b'D%s: no diffs found') % drev)
2216 raise error.Abort(_(b'D%s: no diffs found') % drev)
2215
2217
2216 ui.note(msg + b'\n')
2218 ui.note(msg + b'\n')
2217 parents = [repo[node]]
2219 parents = [repo[node]]
2218
2220
2219 drevs = _getdrevs(ui, opts.get(b'stack'), specs)
2221 drevs = _getdrevs(ui, opts.get(b'stack'), specs)
2220
2222
2221 readpatch(repo.ui, drevs, _write)
2223 readpatch(repo.ui, drevs, _write)
2222
2224
2223
2225
2224 @vcrcommand(
2226 @vcrcommand(
2225 b'phabupdate',
2227 b'phabupdate',
2226 [
2228 [
2227 (b'', b'accept', False, _(b'accept revisions')),
2229 (b'', b'accept', False, _(b'accept revisions')),
2228 (b'', b'reject', False, _(b'reject revisions')),
2230 (b'', b'reject', False, _(b'reject revisions')),
2229 (b'', b'request-review', False, _(b'request review on revisions')),
2231 (b'', b'request-review', False, _(b'request review on revisions')),
2230 (b'', b'abandon', False, _(b'abandon revisions')),
2232 (b'', b'abandon', False, _(b'abandon revisions')),
2231 (b'', b'reclaim', False, _(b'reclaim revisions')),
2233 (b'', b'reclaim', False, _(b'reclaim revisions')),
2232 (b'', b'close', False, _(b'close revisions')),
2234 (b'', b'close', False, _(b'close revisions')),
2233 (b'', b'reopen', False, _(b'reopen revisions')),
2235 (b'', b'reopen', False, _(b'reopen revisions')),
2234 (b'', b'plan-changes', False, _(b'plan changes for revisions')),
2236 (b'', b'plan-changes', False, _(b'plan changes for revisions')),
2235 (b'', b'resign', False, _(b'resign as a reviewer from revisions')),
2237 (b'', b'resign', False, _(b'resign as a reviewer from revisions')),
2236 (b'', b'commandeer', False, _(b'commandeer revisions')),
2238 (b'', b'commandeer', False, _(b'commandeer revisions')),
2237 (b'm', b'comment', b'', _(b'comment on the last revision')),
2239 (b'm', b'comment', b'', _(b'comment on the last revision')),
2238 (b'r', b'rev', b'', _(b'local revision to update'), _(b'REV')),
2240 (b'r', b'rev', b'', _(b'local revision to update'), _(b'REV')),
2239 ],
2241 ],
2240 _(b'[DREVSPEC...| -r REV...] [OPTIONS]'),
2242 _(b'[DREVSPEC...| -r REV...] [OPTIONS]'),
2241 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2243 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2242 optionalrepo=True,
2244 optionalrepo=True,
2243 )
2245 )
2244 def phabupdate(ui, repo, *specs, **opts):
2246 def phabupdate(ui, repo, *specs, **opts):
2245 """update Differential Revision in batch
2247 """update Differential Revision in batch
2246
2248
2247 DREVSPEC selects revisions. See :hg:`help phabread` for its usage.
2249 DREVSPEC selects revisions. See :hg:`help phabread` for its usage.
2248 """
2250 """
2249 opts = pycompat.byteskwargs(opts)
2251 opts = pycompat.byteskwargs(opts)
2250 transactions = [
2252 transactions = [
2251 b'abandon',
2253 b'abandon',
2252 b'accept',
2254 b'accept',
2253 b'close',
2255 b'close',
2254 b'commandeer',
2256 b'commandeer',
2255 b'plan-changes',
2257 b'plan-changes',
2256 b'reclaim',
2258 b'reclaim',
2257 b'reject',
2259 b'reject',
2258 b'reopen',
2260 b'reopen',
2259 b'request-review',
2261 b'request-review',
2260 b'resign',
2262 b'resign',
2261 ]
2263 ]
2262 flags = [n for n in transactions if opts.get(n.replace(b'-', b'_'))]
2264 flags = [n for n in transactions if opts.get(n.replace(b'-', b'_'))]
2263 if len(flags) > 1:
2265 if len(flags) > 1:
2264 raise error.Abort(_(b'%s cannot be used together') % b', '.join(flags))
2266 raise error.Abort(_(b'%s cannot be used together') % b', '.join(flags))
2265
2267
2266 actions = []
2268 actions = []
2267 for f in flags:
2269 for f in flags:
2268 actions.append({b'type': f, b'value': True})
2270 actions.append({b'type': f, b'value': True})
2269
2271
2270 revs = opts.get(b'rev')
2272 revs = opts.get(b'rev')
2271 if revs:
2273 if revs:
2272 if not repo:
2274 if not repo:
2273 raise error.InputError(_(b'--rev requires a repository'))
2275 raise error.InputError(_(b'--rev requires a repository'))
2274
2276
2275 if specs:
2277 if specs:
2276 raise error.InputError(_(b'cannot specify both DREVSPEC and --rev'))
2278 raise error.InputError(_(b'cannot specify both DREVSPEC and --rev'))
2277
2279
2278 drevmap = getdrevmap(repo, logcmdutil.revrange(repo, [revs]))
2280 drevmap = getdrevmap(repo, logcmdutil.revrange(repo, [revs]))
2279 specs = []
2281 specs = []
2280 unknown = []
2282 unknown = []
2281 for r, d in drevmap.items():
2283 for r, d in drevmap.items():
2282 if d is None:
2284 if d is None:
2283 unknown.append(repo[r])
2285 unknown.append(repo[r])
2284 else:
2286 else:
2285 specs.append(b'D%d' % d)
2287 specs.append(b'D%d' % d)
2286 if unknown:
2288 if unknown:
2287 raise error.InputError(
2289 raise error.InputError(
2288 _(b'selected revisions without a Differential: %s')
2290 _(b'selected revisions without a Differential: %s')
2289 % scmutil.nodesummaries(repo, unknown)
2291 % scmutil.nodesummaries(repo, unknown)
2290 )
2292 )
2291
2293
2292 drevs = _getdrevs(ui, opts.get(b'stack'), specs)
2294 drevs = _getdrevs(ui, opts.get(b'stack'), specs)
2293 for i, drev in enumerate(drevs):
2295 for i, drev in enumerate(drevs):
2294 if i + 1 == len(drevs) and opts.get(b'comment'):
2296 if i + 1 == len(drevs) and opts.get(b'comment'):
2295 actions.append({b'type': b'comment', b'value': opts[b'comment']})
2297 actions.append({b'type': b'comment', b'value': opts[b'comment']})
2296 if actions:
2298 if actions:
2297 params = {
2299 params = {
2298 b'objectIdentifier': drev[b'phid'],
2300 b'objectIdentifier': drev[b'phid'],
2299 b'transactions': actions,
2301 b'transactions': actions,
2300 }
2302 }
2301 callconduit(ui, b'differential.revision.edit', params)
2303 callconduit(ui, b'differential.revision.edit', params)
2302
2304
2303
2305
2304 @eh.templatekeyword(b'phabreview', requires={b'ctx'})
2306 @eh.templatekeyword(b'phabreview', requires={b'ctx'})
2305 def template_review(context, mapping):
2307 def template_review(context, mapping):
2306 """:phabreview: Object describing the review for this changeset.
2308 """:phabreview: Object describing the review for this changeset.
2307 Has attributes `url` and `id`.
2309 Has attributes `url` and `id`.
2308 """
2310 """
2309 ctx = context.resource(mapping, b'ctx')
2311 ctx = context.resource(mapping, b'ctx')
2310 m = _differentialrevisiondescre.search(ctx.description())
2312 m = _differentialrevisiondescre.search(ctx.description())
2311 if m:
2313 if m:
2312 return templateutil.hybriddict(
2314 return templateutil.hybriddict(
2313 {
2315 {
2314 b'url': m.group('url'),
2316 b'url': m.group('url'),
2315 b'id': b"D%s" % m.group('id'),
2317 b'id': b"D%s" % m.group('id'),
2316 }
2318 }
2317 )
2319 )
2318 else:
2320 else:
2319 tags = ctx.repo().nodetags(ctx.node())
2321 tags = ctx.repo().nodetags(ctx.node())
2320 for t in tags:
2322 for t in tags:
2321 if _differentialrevisiontagre.match(t):
2323 if _differentialrevisiontagre.match(t):
2322 url = ctx.repo().ui.config(b'phabricator', b'url')
2324 url = ctx.repo().ui.config(b'phabricator', b'url')
2323 if not url.endswith(b'/'):
2325 if not url.endswith(b'/'):
2324 url += b'/'
2326 url += b'/'
2325 url += t
2327 url += t
2326
2328
2327 return templateutil.hybriddict(
2329 return templateutil.hybriddict(
2328 {
2330 {
2329 b'url': url,
2331 b'url': url,
2330 b'id': t,
2332 b'id': t,
2331 }
2333 }
2332 )
2334 )
2333 return None
2335 return None
2334
2336
2335
2337
2336 @eh.templatekeyword(b'phabstatus', requires={b'ctx', b'repo', b'ui'})
2338 @eh.templatekeyword(b'phabstatus', requires={b'ctx', b'repo', b'ui'})
2337 def template_status(context, mapping):
2339 def template_status(context, mapping):
2338 """:phabstatus: String. Status of Phabricator differential."""
2340 """:phabstatus: String. Status of Phabricator differential."""
2339 ctx = context.resource(mapping, b'ctx')
2341 ctx = context.resource(mapping, b'ctx')
2340 repo = context.resource(mapping, b'repo')
2342 repo = context.resource(mapping, b'repo')
2341 ui = context.resource(mapping, b'ui')
2343 ui = context.resource(mapping, b'ui')
2342
2344
2343 rev = ctx.rev()
2345 rev = ctx.rev()
2344 try:
2346 try:
2345 drevid = getdrevmap(repo, [rev])[rev]
2347 drevid = getdrevmap(repo, [rev])[rev]
2346 except KeyError:
2348 except KeyError:
2347 return None
2349 return None
2348 drevs = callconduit(ui, b'differential.query', {b'ids': [drevid]})
2350 drevs = callconduit(ui, b'differential.query', {b'ids': [drevid]})
2349 for drev in drevs:
2351 for drev in drevs:
2350 if int(drev[b'id']) == drevid:
2352 if int(drev[b'id']) == drevid:
2351 return templateutil.hybriddict(
2353 return templateutil.hybriddict(
2352 {
2354 {
2353 b'url': drev[b'uri'],
2355 b'url': drev[b'uri'],
2354 b'status': drev[b'statusName'],
2356 b'status': drev[b'statusName'],
2355 }
2357 }
2356 )
2358 )
2357 return None
2359 return None
2358
2360
2359
2361
2360 @show.showview(b'phabstatus', csettopic=b'work')
2362 @show.showview(b'phabstatus', csettopic=b'work')
2361 def phabstatusshowview(ui, repo, displayer):
2363 def phabstatusshowview(ui, repo, displayer):
2362 """Phabricator differiential status"""
2364 """Phabricator differiential status"""
2363 revs = repo.revs('sort(_underway(), topo)')
2365 revs = repo.revs('sort(_underway(), topo)')
2364 drevmap = getdrevmap(repo, revs)
2366 drevmap = getdrevmap(repo, revs)
2365 unknownrevs, drevids, revsbydrevid = [], set(), {}
2367 unknownrevs, drevids, revsbydrevid = [], set(), {}
2366 for rev, drevid in drevmap.items():
2368 for rev, drevid in drevmap.items():
2367 if drevid is not None:
2369 if drevid is not None:
2368 drevids.add(drevid)
2370 drevids.add(drevid)
2369 revsbydrevid.setdefault(drevid, set()).add(rev)
2371 revsbydrevid.setdefault(drevid, set()).add(rev)
2370 else:
2372 else:
2371 unknownrevs.append(rev)
2373 unknownrevs.append(rev)
2372
2374
2373 drevs = callconduit(ui, b'differential.query', {b'ids': list(drevids)})
2375 drevs = callconduit(ui, b'differential.query', {b'ids': list(drevids)})
2374 drevsbyrev = {}
2376 drevsbyrev = {}
2375 for drev in drevs:
2377 for drev in drevs:
2376 for rev in revsbydrevid[int(drev[b'id'])]:
2378 for rev in revsbydrevid[int(drev[b'id'])]:
2377 drevsbyrev[rev] = drev
2379 drevsbyrev[rev] = drev
2378
2380
2379 def phabstatus(ctx):
2381 def phabstatus(ctx):
2380 drev = drevsbyrev[ctx.rev()]
2382 drev = drevsbyrev[ctx.rev()]
2381 status = ui.label(
2383 status = ui.label(
2382 b'%(statusName)s' % drev,
2384 b'%(statusName)s' % drev,
2383 b'phabricator.status.%s' % _getstatusname(drev),
2385 b'phabricator.status.%s' % _getstatusname(drev),
2384 )
2386 )
2385 ui.write(b"\n%s %s\n" % (drev[b'uri'], status))
2387 ui.write(b"\n%s %s\n" % (drev[b'uri'], status))
2386
2388
2387 revs -= smartset.baseset(unknownrevs)
2389 revs -= smartset.baseset(unknownrevs)
2388 revdag = graphmod.dagwalker(repo, revs)
2390 revdag = graphmod.dagwalker(repo, revs)
2389
2391
2390 ui.setconfig(b'experimental', b'graphshorten', True)
2392 ui.setconfig(b'experimental', b'graphshorten', True)
2391 displayer._exthook = phabstatus
2393 displayer._exthook = phabstatus
2392 nodelen = show.longestshortest(repo, revs)
2394 nodelen = show.longestshortest(repo, revs)
2393 logcmdutil.displaygraph(
2395 logcmdutil.displaygraph(
2394 ui,
2396 ui,
2395 repo,
2397 repo,
2396 revdag,
2398 revdag,
2397 displayer,
2399 displayer,
2398 graphmod.asciiedges,
2400 graphmod.asciiedges,
2399 props={b'nodelen': nodelen},
2401 props={b'nodelen': nodelen},
2400 )
2402 )
@@ -1,1890 +1,1890 b''
1 """ Multicast DNS Service Discovery for Python, v0.12
1 """ Multicast DNS Service Discovery for Python, v0.12
2 Copyright (C) 2003, Paul Scott-Murphy
2 Copyright (C) 2003, Paul Scott-Murphy
3
3
4 This module provides a framework for the use of DNS Service Discovery
4 This module provides a framework for the use of DNS Service Discovery
5 using IP multicast. It has been tested against the JRendezvous
5 using IP multicast. It has been tested against the JRendezvous
6 implementation from <a href="http://strangeberry.com">StrangeBerry</a>,
6 implementation from <a href="http://strangeberry.com">StrangeBerry</a>,
7 and against the mDNSResponder from Mac OS X 10.3.8.
7 and against the mDNSResponder from Mac OS X 10.3.8.
8
8
9 This library is free software; you can redistribute it and/or
9 This library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Lesser General Public
10 modify it under the terms of the GNU Lesser General Public
11 License as published by the Free Software Foundation; either
11 License as published by the Free Software Foundation; either
12 version 2.1 of the License, or (at your option) any later version.
12 version 2.1 of the License, or (at your option) any later version.
13
13
14 This library is distributed in the hope that it will be useful,
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
17 Lesser General Public License for more details.
18
18
19 You should have received a copy of the GNU Lesser General Public
19 You should have received a copy of the GNU Lesser General Public
20 License along with this library; if not, see
20 License along with this library; if not, see
21 <http://www.gnu.org/licenses/>.
21 <http://www.gnu.org/licenses/>.
22
22
23 """
23 """
24
24
25 """0.12 update - allow selection of binding interface
25 """0.12 update - allow selection of binding interface
26 typo fix - Thanks A. M. Kuchlingi
26 typo fix - Thanks A. M. Kuchlingi
27 removed all use of word 'Rendezvous' - this is an API change"""
27 removed all use of word 'Rendezvous' - this is an API change"""
28
28
29 """0.11 update - correction to comments for addListener method
29 """0.11 update - correction to comments for addListener method
30 support for new record types seen from OS X
30 support for new record types seen from OS X
31 - IPv6 address
31 - IPv6 address
32 - hostinfo
32 - hostinfo
33 ignore unknown DNS record types
33 ignore unknown DNS record types
34 fixes to name decoding
34 fixes to name decoding
35 works alongside other processes using port 5353 (e.g. Mac OS X)
35 works alongside other processes using port 5353 (e.g. Mac OS X)
36 tested against Mac OS X 10.3.2's mDNSResponder
36 tested against Mac OS X 10.3.2's mDNSResponder
37 corrections to removal of list entries for service browser"""
37 corrections to removal of list entries for service browser"""
38
38
39 """0.10 update - Jonathon Paisley contributed these corrections:
39 """0.10 update - Jonathon Paisley contributed these corrections:
40 always multicast replies, even when query is unicast
40 always multicast replies, even when query is unicast
41 correct a pointer encoding problem
41 correct a pointer encoding problem
42 can now write records in any order
42 can now write records in any order
43 traceback shown on failure
43 traceback shown on failure
44 better TXT record parsing
44 better TXT record parsing
45 server is now separate from name
45 server is now separate from name
46 can cancel a service browser
46 can cancel a service browser
47
47
48 modified some unit tests to accommodate these changes"""
48 modified some unit tests to accommodate these changes"""
49
49
50 """0.09 update - remove all records on service unregistration
50 """0.09 update - remove all records on service unregistration
51 fix DOS security problem with readName"""
51 fix DOS security problem with readName"""
52
52
53 """0.08 update - changed licensing to LGPL"""
53 """0.08 update - changed licensing to LGPL"""
54
54
55 """0.07 update - faster shutdown on engine
55 """0.07 update - faster shutdown on engine
56 pointer encoding of outgoing names
56 pointer encoding of outgoing names
57 ServiceBrowser now works
57 ServiceBrowser now works
58 new unit tests"""
58 new unit tests"""
59
59
60 """0.06 update - small improvements with unit tests
60 """0.06 update - small improvements with unit tests
61 added defined exception types
61 added defined exception types
62 new style objects
62 new style objects
63 fixed hostname/interface problem
63 fixed hostname/interface problem
64 fixed socket timeout problem
64 fixed socket timeout problem
65 fixed addServiceListener() typo bug
65 fixed addServiceListener() typo bug
66 using select() for socket reads
66 using select() for socket reads
67 tested on Debian unstable with Python 2.2.2"""
67 tested on Debian unstable with Python 2.2.2"""
68
68
69 """0.05 update - ensure case insensitivity on domain names
69 """0.05 update - ensure case insensitivity on domain names
70 support for unicast DNS queries"""
70 support for unicast DNS queries"""
71
71
72 """0.04 update - added some unit tests
72 """0.04 update - added some unit tests
73 added __ne__ adjuncts where required
73 added __ne__ adjuncts where required
74 ensure names end in '.local.'
74 ensure names end in '.local.'
75 timeout on receiving socket for clean shutdown"""
75 timeout on receiving socket for clean shutdown"""
76
76
77 __author__ = b"Paul Scott-Murphy"
77 __author__ = b"Paul Scott-Murphy"
78 __email__ = b"paul at scott dash murphy dot com"
78 __email__ = b"paul at scott dash murphy dot com"
79 __version__ = b"0.12"
79 __version__ = b"0.12"
80
80
81 import errno
81 import errno
82 import itertools
82 import itertools
83 import select
83 import select
84 import socket
84 import socket
85 import struct
85 import struct
86 import threading
86 import threading
87 import time
87 import time
88 import traceback
88 import traceback
89
89
90 from mercurial import pycompat
90 from mercurial import pycompat
91
91
92 __all__ = [b"Zeroconf", b"ServiceInfo", b"ServiceBrowser"]
92 __all__ = [b"Zeroconf", b"ServiceInfo", b"ServiceBrowser"]
93
93
94 # hook for threads
94 # hook for threads
95
95
96 globals()[b'_GLOBAL_DONE'] = 0
96 globals()[b'_GLOBAL_DONE'] = 0
97
97
98 # Some timing constants
98 # Some timing constants
99
99
100 _UNREGISTER_TIME = 125
100 _UNREGISTER_TIME = 125
101 _CHECK_TIME = 175
101 _CHECK_TIME = 175
102 _REGISTER_TIME = 225
102 _REGISTER_TIME = 225
103 _LISTENER_TIME = 200
103 _LISTENER_TIME = 200
104 _BROWSER_TIME = 500
104 _BROWSER_TIME = 500
105
105
106 # Some DNS constants
106 # Some DNS constants
107
107
108 _MDNS_ADDR = r'224.0.0.251'
108 _MDNS_ADDR = r'224.0.0.251'
109 _MDNS_PORT = 5353
109 _MDNS_PORT = 5353
110 _DNS_PORT = 53
110 _DNS_PORT = 53
111 _DNS_TTL = 60 * 60 # one hour default TTL
111 _DNS_TTL = 60 * 60 # one hour default TTL
112
112
113 _MAX_MSG_TYPICAL = 1460 # unused
113 _MAX_MSG_TYPICAL = 1460 # unused
114 _MAX_MSG_ABSOLUTE = 8972
114 _MAX_MSG_ABSOLUTE = 8972
115
115
116 _FLAGS_QR_MASK = 0x8000 # query response mask
116 _FLAGS_QR_MASK = 0x8000 # query response mask
117 _FLAGS_QR_QUERY = 0x0000 # query
117 _FLAGS_QR_QUERY = 0x0000 # query
118 _FLAGS_QR_RESPONSE = 0x8000 # response
118 _FLAGS_QR_RESPONSE = 0x8000 # response
119
119
120 _FLAGS_AA = 0x0400 # Authoritative answer
120 _FLAGS_AA = 0x0400 # Authoritative answer
121 _FLAGS_TC = 0x0200 # Truncated
121 _FLAGS_TC = 0x0200 # Truncated
122 _FLAGS_RD = 0x0100 # Recursion desired
122 _FLAGS_RD = 0x0100 # Recursion desired
123 _FLAGS_RA = 0x8000 # Recursion available
123 _FLAGS_RA = 0x8000 # Recursion available
124
124
125 _FLAGS_Z = 0x0040 # Zero
125 _FLAGS_Z = 0x0040 # Zero
126 _FLAGS_AD = 0x0020 # Authentic data
126 _FLAGS_AD = 0x0020 # Authentic data
127 _FLAGS_CD = 0x0010 # Checking disabled
127 _FLAGS_CD = 0x0010 # Checking disabled
128
128
129 _CLASS_IN = 1
129 _CLASS_IN = 1
130 _CLASS_CS = 2
130 _CLASS_CS = 2
131 _CLASS_CH = 3
131 _CLASS_CH = 3
132 _CLASS_HS = 4
132 _CLASS_HS = 4
133 _CLASS_NONE = 254
133 _CLASS_NONE = 254
134 _CLASS_ANY = 255
134 _CLASS_ANY = 255
135 _CLASS_MASK = 0x7FFF
135 _CLASS_MASK = 0x7FFF
136 _CLASS_UNIQUE = 0x8000
136 _CLASS_UNIQUE = 0x8000
137
137
138 _TYPE_A = 1
138 _TYPE_A = 1
139 _TYPE_NS = 2
139 _TYPE_NS = 2
140 _TYPE_MD = 3
140 _TYPE_MD = 3
141 _TYPE_MF = 4
141 _TYPE_MF = 4
142 _TYPE_CNAME = 5
142 _TYPE_CNAME = 5
143 _TYPE_SOA = 6
143 _TYPE_SOA = 6
144 _TYPE_MB = 7
144 _TYPE_MB = 7
145 _TYPE_MG = 8
145 _TYPE_MG = 8
146 _TYPE_MR = 9
146 _TYPE_MR = 9
147 _TYPE_NULL = 10
147 _TYPE_NULL = 10
148 _TYPE_WKS = 11
148 _TYPE_WKS = 11
149 _TYPE_PTR = 12
149 _TYPE_PTR = 12
150 _TYPE_HINFO = 13
150 _TYPE_HINFO = 13
151 _TYPE_MINFO = 14
151 _TYPE_MINFO = 14
152 _TYPE_MX = 15
152 _TYPE_MX = 15
153 _TYPE_TXT = 16
153 _TYPE_TXT = 16
154 _TYPE_AAAA = 28
154 _TYPE_AAAA = 28
155 _TYPE_SRV = 33
155 _TYPE_SRV = 33
156 _TYPE_ANY = 255
156 _TYPE_ANY = 255
157
157
158 # Mapping constants to names
158 # Mapping constants to names
159
159
160 _CLASSES = {
160 _CLASSES = {
161 _CLASS_IN: b"in",
161 _CLASS_IN: b"in",
162 _CLASS_CS: b"cs",
162 _CLASS_CS: b"cs",
163 _CLASS_CH: b"ch",
163 _CLASS_CH: b"ch",
164 _CLASS_HS: b"hs",
164 _CLASS_HS: b"hs",
165 _CLASS_NONE: b"none",
165 _CLASS_NONE: b"none",
166 _CLASS_ANY: b"any",
166 _CLASS_ANY: b"any",
167 }
167 }
168
168
169 _TYPES = {
169 _TYPES = {
170 _TYPE_A: b"a",
170 _TYPE_A: b"a",
171 _TYPE_NS: b"ns",
171 _TYPE_NS: b"ns",
172 _TYPE_MD: b"md",
172 _TYPE_MD: b"md",
173 _TYPE_MF: b"mf",
173 _TYPE_MF: b"mf",
174 _TYPE_CNAME: b"cname",
174 _TYPE_CNAME: b"cname",
175 _TYPE_SOA: b"soa",
175 _TYPE_SOA: b"soa",
176 _TYPE_MB: b"mb",
176 _TYPE_MB: b"mb",
177 _TYPE_MG: b"mg",
177 _TYPE_MG: b"mg",
178 _TYPE_MR: b"mr",
178 _TYPE_MR: b"mr",
179 _TYPE_NULL: b"null",
179 _TYPE_NULL: b"null",
180 _TYPE_WKS: b"wks",
180 _TYPE_WKS: b"wks",
181 _TYPE_PTR: b"ptr",
181 _TYPE_PTR: b"ptr",
182 _TYPE_HINFO: b"hinfo",
182 _TYPE_HINFO: b"hinfo",
183 _TYPE_MINFO: b"minfo",
183 _TYPE_MINFO: b"minfo",
184 _TYPE_MX: b"mx",
184 _TYPE_MX: b"mx",
185 _TYPE_TXT: b"txt",
185 _TYPE_TXT: b"txt",
186 _TYPE_AAAA: b"quada",
186 _TYPE_AAAA: b"quada",
187 _TYPE_SRV: b"srv",
187 _TYPE_SRV: b"srv",
188 _TYPE_ANY: b"any",
188 _TYPE_ANY: b"any",
189 }
189 }
190
190
191 # utility functions
191 # utility functions
192
192
193
193
194 def currentTimeMillis():
194 def currentTimeMillis():
195 """Current system time in milliseconds"""
195 """Current system time in milliseconds"""
196 return time.time() * 1000
196 return time.time() * 1000
197
197
198
198
199 # Exceptions
199 # Exceptions
200
200
201
201
202 class NonLocalNameException(Exception):
202 class NonLocalNameException(Exception):
203 pass
203 pass
204
204
205
205
206 class NonUniqueNameException(Exception):
206 class NonUniqueNameException(Exception):
207 pass
207 pass
208
208
209
209
210 class NamePartTooLongException(Exception):
210 class NamePartTooLongException(Exception):
211 pass
211 pass
212
212
213
213
214 class AbstractMethodException(Exception):
214 class AbstractMethodException(Exception):
215 pass
215 pass
216
216
217
217
218 class BadTypeInNameException(Exception):
218 class BadTypeInNameException(Exception):
219 pass
219 pass
220
220
221
221
222 class BadDomainName(Exception):
222 class BadDomainName(Exception):
223 def __init__(self, pos):
223 def __init__(self, pos):
224 Exception.__init__(self, b"at position %s" % pos)
224 Exception.__init__(self, b"at position %s" % pos)
225
225
226
226
227 class BadDomainNameCircular(BadDomainName):
227 class BadDomainNameCircular(BadDomainName):
228 pass
228 pass
229
229
230
230
231 # implementation classes
231 # implementation classes
232
232
233
233
234 class DNSEntry:
234 class DNSEntry:
235 """A DNS entry"""
235 """A DNS entry"""
236
236
237 def __init__(self, name, type, clazz):
237 def __init__(self, name, type, clazz):
238 self.key = name.lower()
238 self.key = name.lower()
239 self.name = name
239 self.name = name
240 self.type = type
240 self.type = type
241 self.clazz = clazz & _CLASS_MASK
241 self.clazz = clazz & _CLASS_MASK
242 self.unique = (clazz & _CLASS_UNIQUE) != 0
242 self.unique = (clazz & _CLASS_UNIQUE) != 0
243
243
244 def __eq__(self, other):
244 def __eq__(self, other):
245 """Equality test on name, type, and class"""
245 """Equality test on name, type, and class"""
246 if isinstance(other, DNSEntry):
246 if isinstance(other, DNSEntry):
247 return (
247 return (
248 self.name == other.name
248 self.name == other.name
249 and self.type == other.type
249 and self.type == other.type
250 and self.clazz == other.clazz
250 and self.clazz == other.clazz
251 )
251 )
252 return 0
252 return 0
253
253
254 def __ne__(self, other):
254 def __ne__(self, other):
255 """Non-equality test"""
255 """Non-equality test"""
256 return not self.__eq__(other)
256 return not self.__eq__(other)
257
257
258 def getClazz(self, clazz):
258 def getClazz(self, clazz):
259 """Class accessor"""
259 """Class accessor"""
260 try:
260 try:
261 return _CLASSES[clazz]
261 return _CLASSES[clazz]
262 except KeyError:
262 except KeyError:
263 return b"?(%s)" % clazz
263 return b"?(%s)" % clazz
264
264
265 def getType(self, type):
265 def getType(self, type):
266 """Type accessor"""
266 """Type accessor"""
267 try:
267 try:
268 return _TYPES[type]
268 return _TYPES[type]
269 except KeyError:
269 except KeyError:
270 return b"?(%s)" % type
270 return b"?(%s)" % type
271
271
272 def toString(self, hdr, other):
272 def toString(self, hdr, other):
273 """String representation with additional information"""
273 """String representation with additional information"""
274 result = b"%s[%s,%s" % (
274 result = b"%s[%s,%s" % (
275 hdr,
275 hdr,
276 self.getType(self.type),
276 self.getType(self.type),
277 self.getClazz(self.clazz),
277 self.getClazz(self.clazz),
278 )
278 )
279 if self.unique:
279 if self.unique:
280 result += b"-unique,"
280 result += b"-unique,"
281 else:
281 else:
282 result += b","
282 result += b","
283 result += self.name
283 result += self.name
284 if other is not None:
284 if other is not None:
285 result += b",%s]" % other
285 result += b",%s]" % other
286 else:
286 else:
287 result += b"]"
287 result += b"]"
288 return result
288 return result
289
289
290
290
291 class DNSQuestion(DNSEntry):
291 class DNSQuestion(DNSEntry):
292 """A DNS question entry"""
292 """A DNS question entry"""
293
293
294 def __init__(self, name, type, clazz):
294 def __init__(self, name, type, clazz):
295 if isinstance(name, str):
295 if isinstance(name, str):
296 name = name.encode('ascii')
296 name = name.encode('ascii')
297 if not name.endswith(b".local."):
297 if not name.endswith(b".local."):
298 raise NonLocalNameException(name)
298 raise NonLocalNameException(name)
299 DNSEntry.__init__(self, name, type, clazz)
299 DNSEntry.__init__(self, name, type, clazz)
300
300
301 def answeredBy(self, rec):
301 def answeredBy(self, rec):
302 """Returns true if the question is answered by the record"""
302 """Returns true if the question is answered by the record"""
303 return (
303 return (
304 self.clazz == rec.clazz
304 self.clazz == rec.clazz
305 and (self.type == rec.type or self.type == _TYPE_ANY)
305 and (self.type == rec.type or self.type == _TYPE_ANY)
306 and self.name == rec.name
306 and self.name == rec.name
307 )
307 )
308
308
309 def __repr__(self):
309 def __repr__(self):
310 """String representation"""
310 """String representation"""
311 return DNSEntry.toString(self, b"question", None)
311 return DNSEntry.toString(self, b"question", None)
312
312
313
313
314 class DNSRecord(DNSEntry):
314 class DNSRecord(DNSEntry):
315 """A DNS record - like a DNS entry, but has a TTL"""
315 """A DNS record - like a DNS entry, but has a TTL"""
316
316
317 def __init__(self, name, type, clazz, ttl):
317 def __init__(self, name, type, clazz, ttl):
318 DNSEntry.__init__(self, name, type, clazz)
318 DNSEntry.__init__(self, name, type, clazz)
319 self.ttl = ttl
319 self.ttl = ttl
320 self.created = currentTimeMillis()
320 self.created = currentTimeMillis()
321
321
322 def __eq__(self, other):
322 def __eq__(self, other):
323 """Tests equality as per DNSRecord"""
323 """Tests equality as per DNSRecord"""
324 if isinstance(other, DNSRecord):
324 if isinstance(other, DNSRecord):
325 return DNSEntry.__eq__(self, other)
325 return DNSEntry.__eq__(self, other)
326 return 0
326 return 0
327
327
328 def suppressedBy(self, msg):
328 def suppressedBy(self, msg):
329 """Returns true if any answer in a message can suffice for the
329 """Returns true if any answer in a message can suffice for the
330 information held in this record."""
330 information held in this record."""
331 for record in msg.answers:
331 for record in msg.answers:
332 if self.suppressedByAnswer(record):
332 if self.suppressedByAnswer(record):
333 return 1
333 return 1
334 return 0
334 return 0
335
335
336 def suppressedByAnswer(self, other):
336 def suppressedByAnswer(self, other):
337 """Returns true if another record has same name, type and class,
337 """Returns true if another record has same name, type and class,
338 and if its TTL is at least half of this record's."""
338 and if its TTL is at least half of this record's."""
339 if self == other and other.ttl > (self.ttl / 2):
339 if self == other and other.ttl > (self.ttl / 2):
340 return 1
340 return 1
341 return 0
341 return 0
342
342
343 def getExpirationTime(self, percent):
343 def getExpirationTime(self, percent):
344 """Returns the time at which this record will have expired
344 """Returns the time at which this record will have expired
345 by a certain percentage."""
345 by a certain percentage."""
346 return self.created + (percent * self.ttl * 10)
346 return self.created + (percent * self.ttl * 10)
347
347
348 def getRemainingTTL(self, now):
348 def getRemainingTTL(self, now):
349 """Returns the remaining TTL in seconds."""
349 """Returns the remaining TTL in seconds."""
350 return max(0, (self.getExpirationTime(100) - now) / 1000)
350 return max(0, (self.getExpirationTime(100) - now) / 1000)
351
351
352 def isExpired(self, now):
352 def isExpired(self, now):
353 """Returns true if this record has expired."""
353 """Returns true if this record has expired."""
354 return self.getExpirationTime(100) <= now
354 return self.getExpirationTime(100) <= now
355
355
356 def isStale(self, now):
356 def isStale(self, now):
357 """Returns true if this record is at least half way expired."""
357 """Returns true if this record is at least half way expired."""
358 return self.getExpirationTime(50) <= now
358 return self.getExpirationTime(50) <= now
359
359
360 def resetTTL(self, other):
360 def resetTTL(self, other):
361 """Sets this record's TTL and created time to that of
361 """Sets this record's TTL and created time to that of
362 another record."""
362 another record."""
363 self.created = other.created
363 self.created = other.created
364 self.ttl = other.ttl
364 self.ttl = other.ttl
365
365
366 def write(self, out):
366 def write(self, out):
367 """Abstract method"""
367 """Abstract method"""
368 raise AbstractMethodException
368 raise AbstractMethodException
369
369
370 def toString(self, other):
370 def toString(self, other):
371 """String representation with additional information"""
371 """String representation with additional information"""
372 arg = b"%s/%s,%s" % (
372 arg = b"%s/%s,%s" % (
373 self.ttl,
373 self.ttl,
374 self.getRemainingTTL(currentTimeMillis()),
374 self.getRemainingTTL(currentTimeMillis()),
375 other,
375 other,
376 )
376 )
377 return DNSEntry.toString(self, b"record", arg)
377 return DNSEntry.toString(self, b"record", arg)
378
378
379
379
380 class DNSAddress(DNSRecord):
380 class DNSAddress(DNSRecord):
381 """A DNS address record"""
381 """A DNS address record"""
382
382
383 def __init__(self, name, type, clazz, ttl, address):
383 def __init__(self, name, type, clazz, ttl, address):
384 DNSRecord.__init__(self, name, type, clazz, ttl)
384 DNSRecord.__init__(self, name, type, clazz, ttl)
385 self.address = address
385 self.address = address
386
386
387 def write(self, out):
387 def write(self, out):
388 """Used in constructing an outgoing packet"""
388 """Used in constructing an outgoing packet"""
389 out.writeString(self.address, len(self.address))
389 out.writeString(self.address, len(self.address))
390
390
391 def __eq__(self, other):
391 def __eq__(self, other):
392 """Tests equality on address"""
392 """Tests equality on address"""
393 if isinstance(other, DNSAddress):
393 if isinstance(other, DNSAddress):
394 return self.address == other.address
394 return self.address == other.address
395 return 0
395 return 0
396
396
397 def __repr__(self):
397 def __repr__(self):
398 """String representation"""
398 """String representation"""
399 try:
399 try:
400 return socket.inet_ntoa(self.address)
400 return socket.inet_ntoa(self.address)
401 except Exception:
401 except Exception:
402 return self.address
402 return self.address
403
403
404
404
405 class DNSHinfo(DNSRecord):
405 class DNSHinfo(DNSRecord):
406 """A DNS host information record"""
406 """A DNS host information record"""
407
407
408 def __init__(self, name, type, clazz, ttl, cpu, os):
408 def __init__(self, name, type, clazz, ttl, cpu, os):
409 DNSRecord.__init__(self, name, type, clazz, ttl)
409 DNSRecord.__init__(self, name, type, clazz, ttl)
410 self.cpu = cpu
410 self.cpu = cpu
411 self.os = os
411 self.os = os
412
412
413 def write(self, out):
413 def write(self, out):
414 """Used in constructing an outgoing packet"""
414 """Used in constructing an outgoing packet"""
415 out.writeString(self.cpu, len(self.cpu))
415 out.writeString(self.cpu, len(self.cpu))
416 out.writeString(self.os, len(self.os))
416 out.writeString(self.os, len(self.os))
417
417
418 def __eq__(self, other):
418 def __eq__(self, other):
419 """Tests equality on cpu and os"""
419 """Tests equality on cpu and os"""
420 if isinstance(other, DNSHinfo):
420 if isinstance(other, DNSHinfo):
421 return self.cpu == other.cpu and self.os == other.os
421 return self.cpu == other.cpu and self.os == other.os
422 return 0
422 return 0
423
423
424 def __repr__(self):
424 def __repr__(self):
425 """String representation"""
425 """String representation"""
426 return self.cpu + b" " + self.os
426 return self.cpu + b" " + self.os
427
427
428
428
429 class DNSPointer(DNSRecord):
429 class DNSPointer(DNSRecord):
430 """A DNS pointer record"""
430 """A DNS pointer record"""
431
431
432 def __init__(self, name, type, clazz, ttl, alias):
432 def __init__(self, name, type, clazz, ttl, alias):
433 DNSRecord.__init__(self, name, type, clazz, ttl)
433 DNSRecord.__init__(self, name, type, clazz, ttl)
434 self.alias = alias
434 self.alias = alias
435
435
436 def write(self, out):
436 def write(self, out):
437 """Used in constructing an outgoing packet"""
437 """Used in constructing an outgoing packet"""
438 out.writeName(self.alias)
438 out.writeName(self.alias)
439
439
440 def __eq__(self, other):
440 def __eq__(self, other):
441 """Tests equality on alias"""
441 """Tests equality on alias"""
442 if isinstance(other, DNSPointer):
442 if isinstance(other, DNSPointer):
443 return self.alias == other.alias
443 return self.alias == other.alias
444 return 0
444 return 0
445
445
446 def __repr__(self):
446 def __repr__(self):
447 """String representation"""
447 """String representation"""
448 return self.toString(self.alias)
448 return self.toString(self.alias)
449
449
450
450
451 class DNSText(DNSRecord):
451 class DNSText(DNSRecord):
452 """A DNS text record"""
452 """A DNS text record"""
453
453
454 def __init__(self, name, type, clazz, ttl, text):
454 def __init__(self, name, type, clazz, ttl, text):
455 DNSRecord.__init__(self, name, type, clazz, ttl)
455 DNSRecord.__init__(self, name, type, clazz, ttl)
456 self.text = text
456 self.text = text
457
457
458 def write(self, out):
458 def write(self, out):
459 """Used in constructing an outgoing packet"""
459 """Used in constructing an outgoing packet"""
460 out.writeString(self.text, len(self.text))
460 out.writeString(self.text, len(self.text))
461
461
462 def __eq__(self, other):
462 def __eq__(self, other):
463 """Tests equality on text"""
463 """Tests equality on text"""
464 if isinstance(other, DNSText):
464 if isinstance(other, DNSText):
465 return self.text == other.text
465 return self.text == other.text
466 return 0
466 return 0
467
467
468 def __repr__(self):
468 def __repr__(self):
469 """String representation"""
469 """String representation"""
470 if len(self.text) > 10:
470 if len(self.text) > 10:
471 return self.toString(self.text[:7] + b"...")
471 return self.toString(self.text[:7] + b"...")
472 else:
472 else:
473 return self.toString(self.text)
473 return self.toString(self.text)
474
474
475
475
476 class DNSService(DNSRecord):
476 class DNSService(DNSRecord):
477 """A DNS service record"""
477 """A DNS service record"""
478
478
479 def __init__(self, name, type, clazz, ttl, priority, weight, port, server):
479 def __init__(self, name, type, clazz, ttl, priority, weight, port, server):
480 DNSRecord.__init__(self, name, type, clazz, ttl)
480 DNSRecord.__init__(self, name, type, clazz, ttl)
481 self.priority = priority
481 self.priority = priority
482 self.weight = weight
482 self.weight = weight
483 self.port = port
483 self.port = port
484 self.server = server
484 self.server = server
485
485
486 def write(self, out):
486 def write(self, out):
487 """Used in constructing an outgoing packet"""
487 """Used in constructing an outgoing packet"""
488 out.writeShort(self.priority)
488 out.writeShort(self.priority)
489 out.writeShort(self.weight)
489 out.writeShort(self.weight)
490 out.writeShort(self.port)
490 out.writeShort(self.port)
491 out.writeName(self.server)
491 out.writeName(self.server)
492
492
493 def __eq__(self, other):
493 def __eq__(self, other):
494 """Tests equality on priority, weight, port and server"""
494 """Tests equality on priority, weight, port and server"""
495 if isinstance(other, DNSService):
495 if isinstance(other, DNSService):
496 return (
496 return (
497 self.priority == other.priority
497 self.priority == other.priority
498 and self.weight == other.weight
498 and self.weight == other.weight
499 and self.port == other.port
499 and self.port == other.port
500 and self.server == other.server
500 and self.server == other.server
501 )
501 )
502 return 0
502 return 0
503
503
504 def __repr__(self):
504 def __repr__(self):
505 """String representation"""
505 """String representation"""
506 return self.toString(b"%s:%s" % (self.server, self.port))
506 return self.toString(b"%s:%s" % (self.server, self.port))
507
507
508
508
509 class DNSIncoming:
509 class DNSIncoming:
510 """Object representation of an incoming DNS packet"""
510 """Object representation of an incoming DNS packet"""
511
511
512 def __init__(self, data):
512 def __init__(self, data):
513 """Constructor from string holding bytes of packet"""
513 """Constructor from string holding bytes of packet"""
514 self.offset = 0
514 self.offset = 0
515 self.data = data
515 self.data = data
516 self.questions = []
516 self.questions = []
517 self.answers = []
517 self.answers = []
518 self.numquestions = 0
518 self.numquestions = 0
519 self.numanswers = 0
519 self.numanswers = 0
520 self.numauthorities = 0
520 self.numauthorities = 0
521 self.numadditionals = 0
521 self.numadditionals = 0
522
522
523 self.readHeader()
523 self.readHeader()
524 self.readQuestions()
524 self.readQuestions()
525 self.readOthers()
525 self.readOthers()
526
526
527 def readHeader(self):
527 def readHeader(self):
528 """Reads header portion of packet"""
528 """Reads header portion of packet"""
529 format = b'!HHHHHH'
529 format = b'!HHHHHH'
530 length = struct.calcsize(format)
530 length = struct.calcsize(format)
531 info = struct.unpack(
531 info = struct.unpack(
532 format, self.data[self.offset : self.offset + length]
532 format, self.data[self.offset : self.offset + length]
533 )
533 )
534 self.offset += length
534 self.offset += length
535
535
536 self.id = info[0]
536 self.id = info[0]
537 self.flags = info[1]
537 self.flags = info[1]
538 self.numquestions = info[2]
538 self.numquestions = info[2]
539 self.numanswers = info[3]
539 self.numanswers = info[3]
540 self.numauthorities = info[4]
540 self.numauthorities = info[4]
541 self.numadditionals = info[5]
541 self.numadditionals = info[5]
542
542
543 def readQuestions(self):
543 def readQuestions(self):
544 """Reads questions section of packet"""
544 """Reads questions section of packet"""
545 format = b'!HH'
545 format = b'!HH'
546 length = struct.calcsize(format)
546 length = struct.calcsize(format)
547 for i in range(0, self.numquestions):
547 for i in range(0, self.numquestions):
548 name = self.readName()
548 name = self.readName()
549 info = struct.unpack(
549 info = struct.unpack(
550 format, self.data[self.offset : self.offset + length]
550 format, self.data[self.offset : self.offset + length]
551 )
551 )
552 self.offset += length
552 self.offset += length
553
553
554 try:
554 try:
555 question = DNSQuestion(name, info[0], info[1])
555 question = DNSQuestion(name, info[0], info[1])
556 self.questions.append(question)
556 self.questions.append(question)
557 except NonLocalNameException:
557 except NonLocalNameException:
558 pass
558 pass
559
559
560 def readInt(self):
560 def readInt(self):
561 """Reads an integer from the packet"""
561 """Reads an integer from the packet"""
562 format = b'!I'
562 format = b'!I'
563 length = struct.calcsize(format)
563 length = struct.calcsize(format)
564 info = struct.unpack(
564 info = struct.unpack(
565 format, self.data[self.offset : self.offset + length]
565 format, self.data[self.offset : self.offset + length]
566 )
566 )
567 self.offset += length
567 self.offset += length
568 return info[0]
568 return info[0]
569
569
570 def readCharacterString(self):
570 def readCharacterString(self):
571 """Reads a character string from the packet"""
571 """Reads a character string from the packet"""
572 length = ord(self.data[self.offset])
572 length = ord(self.data[self.offset])
573 self.offset += 1
573 self.offset += 1
574 return self.readString(length)
574 return self.readString(length)
575
575
576 def readString(self, len):
576 def readString(self, len):
577 """Reads a string of a given length from the packet"""
577 """Reads a string of a given length from the packet"""
578 format = b'!%ds' % len
578 format = b'!%ds' % len
579 length = struct.calcsize(format)
579 length = struct.calcsize(format)
580 info = struct.unpack(
580 info = struct.unpack(
581 format, self.data[self.offset : self.offset + length]
581 format, self.data[self.offset : self.offset + length]
582 )
582 )
583 self.offset += length
583 self.offset += length
584 return info[0]
584 return info[0]
585
585
586 def readUnsignedShort(self):
586 def readUnsignedShort(self):
587 """Reads an unsigned short from the packet"""
587 """Reads an unsigned short from the packet"""
588 format = b'!H'
588 format = b'!H'
589 length = struct.calcsize(format)
589 length = struct.calcsize(format)
590 info = struct.unpack(
590 info = struct.unpack(
591 format, self.data[self.offset : self.offset + length]
591 format, self.data[self.offset : self.offset + length]
592 )
592 )
593 self.offset += length
593 self.offset += length
594 return info[0]
594 return info[0]
595
595
596 def readOthers(self):
596 def readOthers(self):
597 """Reads answers, authorities and additionals section of the packet"""
597 """Reads answers, authorities and additionals section of the packet"""
598 format = b'!HHiH'
598 format = b'!HHiH'
599 length = struct.calcsize(format)
599 length = struct.calcsize(format)
600 n = self.numanswers + self.numauthorities + self.numadditionals
600 n = self.numanswers + self.numauthorities + self.numadditionals
601 for i in range(0, n):
601 for i in range(0, n):
602 domain = self.readName()
602 domain = self.readName()
603 info = struct.unpack(
603 info = struct.unpack(
604 format, self.data[self.offset : self.offset + length]
604 format, self.data[self.offset : self.offset + length]
605 )
605 )
606 self.offset += length
606 self.offset += length
607
607
608 rec = None
608 rec = None
609 if info[0] == _TYPE_A:
609 if info[0] == _TYPE_A:
610 rec = DNSAddress(
610 rec = DNSAddress(
611 domain, info[0], info[1], info[2], self.readString(4)
611 domain, info[0], info[1], info[2], self.readString(4)
612 )
612 )
613 elif info[0] == _TYPE_CNAME or info[0] == _TYPE_PTR:
613 elif info[0] == _TYPE_CNAME or info[0] == _TYPE_PTR:
614 rec = DNSPointer(
614 rec = DNSPointer(
615 domain, info[0], info[1], info[2], self.readName()
615 domain, info[0], info[1], info[2], self.readName()
616 )
616 )
617 elif info[0] == _TYPE_TXT:
617 elif info[0] == _TYPE_TXT:
618 rec = DNSText(
618 rec = DNSText(
619 domain, info[0], info[1], info[2], self.readString(info[3])
619 domain, info[0], info[1], info[2], self.readString(info[3])
620 )
620 )
621 elif info[0] == _TYPE_SRV:
621 elif info[0] == _TYPE_SRV:
622 rec = DNSService(
622 rec = DNSService(
623 domain,
623 domain,
624 info[0],
624 info[0],
625 info[1],
625 info[1],
626 info[2],
626 info[2],
627 self.readUnsignedShort(),
627 self.readUnsignedShort(),
628 self.readUnsignedShort(),
628 self.readUnsignedShort(),
629 self.readUnsignedShort(),
629 self.readUnsignedShort(),
630 self.readName(),
630 self.readName(),
631 )
631 )
632 elif info[0] == _TYPE_HINFO:
632 elif info[0] == _TYPE_HINFO:
633 rec = DNSHinfo(
633 rec = DNSHinfo(
634 domain,
634 domain,
635 info[0],
635 info[0],
636 info[1],
636 info[1],
637 info[2],
637 info[2],
638 self.readCharacterString(),
638 self.readCharacterString(),
639 self.readCharacterString(),
639 self.readCharacterString(),
640 )
640 )
641 elif info[0] == _TYPE_AAAA:
641 elif info[0] == _TYPE_AAAA:
642 rec = DNSAddress(
642 rec = DNSAddress(
643 domain, info[0], info[1], info[2], self.readString(16)
643 domain, info[0], info[1], info[2], self.readString(16)
644 )
644 )
645 else:
645 else:
646 # Try to ignore types we don't know about
646 # Try to ignore types we don't know about
647 # this may mean the rest of the name is
647 # this may mean the rest of the name is
648 # unable to be parsed, and may show errors
648 # unable to be parsed, and may show errors
649 # so this is left for debugging. New types
649 # so this is left for debugging. New types
650 # encountered need to be parsed properly.
650 # encountered need to be parsed properly.
651 #
651 #
652 # print "UNKNOWN TYPE = " + str(info[0])
652 # print "UNKNOWN TYPE = " + str(info[0])
653 # raise BadTypeInNameException
653 # raise BadTypeInNameException
654 self.offset += info[3]
654 self.offset += info[3]
655
655
656 if rec is not None:
656 if rec is not None:
657 self.answers.append(rec)
657 self.answers.append(rec)
658
658
659 def isQuery(self):
659 def isQuery(self):
660 """Returns true if this is a query"""
660 """Returns true if this is a query"""
661 return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY
661 return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY
662
662
663 def isResponse(self):
663 def isResponse(self):
664 """Returns true if this is a response"""
664 """Returns true if this is a response"""
665 return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE
665 return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE
666
666
667 def readUTF(self, offset, len):
667 def readUTF(self, offset, len):
668 """Reads a UTF-8 string of a given length from the packet"""
668 """Reads a UTF-8 string of a given length from the packet"""
669 return self.data[offset : offset + len].decode('utf-8')
669 return self.data[offset : offset + len].decode('utf-8')
670
670
671 def readName(self):
671 def readName(self):
672 """Reads a domain name from the packet"""
672 """Reads a domain name from the packet"""
673 result = r''
673 result = r''
674 off = self.offset
674 off = self.offset
675 next = -1
675 next = -1
676 first = off
676 first = off
677
677
678 while True:
678 while True:
679 len = ord(self.data[off : off + 1])
679 len = ord(self.data[off : off + 1])
680 off += 1
680 off += 1
681 if len == 0:
681 if len == 0:
682 break
682 break
683 t = len & 0xC0
683 t = len & 0xC0
684 if t == 0x00:
684 if t == 0x00:
685 result = ''.join((result, self.readUTF(off, len) + '.'))
685 result = ''.join((result, self.readUTF(off, len) + '.'))
686 off += len
686 off += len
687 elif t == 0xC0:
687 elif t == 0xC0:
688 if next < 0:
688 if next < 0:
689 next = off + 1
689 next = off + 1
690 off = ((len & 0x3F) << 8) | ord(self.data[off : off + 1])
690 off = ((len & 0x3F) << 8) | ord(self.data[off : off + 1])
691 if off >= first:
691 if off >= first:
692 raise BadDomainNameCircular(off)
692 raise BadDomainNameCircular(off)
693 first = off
693 first = off
694 else:
694 else:
695 raise BadDomainName(off)
695 raise BadDomainName(off)
696
696
697 if next >= 0:
697 if next >= 0:
698 self.offset = next
698 self.offset = next
699 else:
699 else:
700 self.offset = off
700 self.offset = off
701
701
702 return result
702 return result
703
703
704
704
705 class DNSOutgoing:
705 class DNSOutgoing:
706 """Object representation of an outgoing packet"""
706 """Object representation of an outgoing packet"""
707
707
708 def __init__(self, flags, multicast=1):
708 def __init__(self, flags, multicast=1):
709 self.finished = 0
709 self.finished = 0
710 self.id = 0
710 self.id = 0
711 self.multicast = multicast
711 self.multicast = multicast
712 self.flags = flags
712 self.flags = flags
713 self.names = {}
713 self.names = {}
714 self.data = []
714 self.data = []
715 self.size = 12
715 self.size = 12
716
716
717 self.questions = []
717 self.questions = []
718 self.answers = []
718 self.answers = []
719 self.authorities = []
719 self.authorities = []
720 self.additionals = []
720 self.additionals = []
721
721
722 def addQuestion(self, record):
722 def addQuestion(self, record):
723 """Adds a question"""
723 """Adds a question"""
724 self.questions.append(record)
724 self.questions.append(record)
725
725
726 def addAnswer(self, inp, record):
726 def addAnswer(self, inp, record):
727 """Adds an answer"""
727 """Adds an answer"""
728 if not record.suppressedBy(inp):
728 if not record.suppressedBy(inp):
729 self.addAnswerAtTime(record, 0)
729 self.addAnswerAtTime(record, 0)
730
730
731 def addAnswerAtTime(self, record, now):
731 def addAnswerAtTime(self, record, now):
732 """Adds an answer if if does not expire by a certain time"""
732 """Adds an answer if if does not expire by a certain time"""
733 if record is not None:
733 if record is not None:
734 if now == 0 or not record.isExpired(now):
734 if now == 0 or not record.isExpired(now):
735 self.answers.append((record, now))
735 self.answers.append((record, now))
736
736
737 def addAuthoritativeAnswer(self, record):
737 def addAuthoritativeAnswer(self, record):
738 """Adds an authoritative answer"""
738 """Adds an authoritative answer"""
739 self.authorities.append(record)
739 self.authorities.append(record)
740
740
741 def addAdditionalAnswer(self, record):
741 def addAdditionalAnswer(self, record):
742 """Adds an additional answer"""
742 """Adds an additional answer"""
743 self.additionals.append(record)
743 self.additionals.append(record)
744
744
745 def writeByte(self, value):
745 def writeByte(self, value):
746 """Writes a single byte to the packet"""
746 """Writes a single byte to the packet"""
747 format = b'!c'
747 format = b'!c'
748 self.data.append(struct.pack(format, chr(value)))
748 self.data.append(struct.pack(format, chr(value)))
749 self.size += 1
749 self.size += 1
750
750
751 def insertShort(self, index, value):
751 def insertShort(self, index, value):
752 """Inserts an unsigned short in a certain position in the packet"""
752 """Inserts an unsigned short in a certain position in the packet"""
753 format = b'!H'
753 format = b'!H'
754 self.data.insert(index, struct.pack(format, value))
754 self.data.insert(index, struct.pack(format, value))
755 self.size += 2
755 self.size += 2
756
756
757 def writeShort(self, value):
757 def writeShort(self, value):
758 """Writes an unsigned short to the packet"""
758 """Writes an unsigned short to the packet"""
759 format = b'!H'
759 format = b'!H'
760 self.data.append(struct.pack(format, value))
760 self.data.append(struct.pack(format, value))
761 self.size += 2
761 self.size += 2
762
762
763 def writeInt(self, value):
763 def writeInt(self, value):
764 """Writes an unsigned integer to the packet"""
764 """Writes an unsigned integer to the packet"""
765 format = b'!I'
765 format = b'!I'
766 self.data.append(struct.pack(format, int(value)))
766 self.data.append(struct.pack(format, int(value)))
767 self.size += 4
767 self.size += 4
768
768
769 def writeString(self, value, length):
769 def writeString(self, value, length):
770 """Writes a string to the packet"""
770 """Writes a string to the packet"""
771 format = '!' + str(length) + 's'
771 format = '!' + str(length) + 's'
772 self.data.append(struct.pack(format, value))
772 self.data.append(struct.pack(format, value))
773 self.size += length
773 self.size += length
774
774
775 def writeUTF(self, s):
775 def writeUTF(self, s):
776 """Writes a UTF-8 string of a given length to the packet"""
776 """Writes a UTF-8 string of a given length to the packet"""
777 utfstr = s.encode('utf-8')
777 utfstr = s.encode('utf-8')
778 length = len(utfstr)
778 length = len(utfstr)
779 if length > 64:
779 if length > 64:
780 raise NamePartTooLongException
780 raise NamePartTooLongException
781 self.writeByte(length)
781 self.writeByte(length)
782 self.writeString(utfstr, length)
782 self.writeString(utfstr, length)
783
783
784 def writeName(self, name):
784 def writeName(self, name):
785 """Writes a domain name to the packet"""
785 """Writes a domain name to the packet"""
786
786
787 try:
787 try:
788 # Find existing instance of this name in packet
788 # Find existing instance of this name in packet
789 #
789 #
790 index = self.names[name]
790 index = self.names[name]
791 except KeyError:
791 except KeyError:
792 # No record of this name already, so write it
792 # No record of this name already, so write it
793 # out as normal, recording the location of the name
793 # out as normal, recording the location of the name
794 # for future pointers to it.
794 # for future pointers to it.
795 #
795 #
796 self.names[name] = self.size
796 self.names[name] = self.size
797 parts = name.split(b'.')
797 parts = name.split(b'.')
798 if parts[-1] == b'':
798 if parts[-1] == b'':
799 parts = parts[:-1]
799 parts = parts[:-1]
800 for part in parts:
800 for part in parts:
801 self.writeUTF(part)
801 self.writeUTF(part)
802 self.writeByte(0)
802 self.writeByte(0)
803 return
803 return
804
804
805 # An index was found, so write a pointer to it
805 # An index was found, so write a pointer to it
806 #
806 #
807 self.writeByte((index >> 8) | 0xC0)
807 self.writeByte((index >> 8) | 0xC0)
808 self.writeByte(index)
808 self.writeByte(index)
809
809
810 def writeQuestion(self, question):
810 def writeQuestion(self, question):
811 """Writes a question to the packet"""
811 """Writes a question to the packet"""
812 self.writeName(question.name)
812 self.writeName(question.name)
813 self.writeShort(question.type)
813 self.writeShort(question.type)
814 self.writeShort(question.clazz)
814 self.writeShort(question.clazz)
815
815
816 def writeRecord(self, record, now):
816 def writeRecord(self, record, now):
817 """Writes a record (answer, authoritative answer, additional) to
817 """Writes a record (answer, authoritative answer, additional) to
818 the packet"""
818 the packet"""
819 self.writeName(record.name)
819 self.writeName(record.name)
820 self.writeShort(record.type)
820 self.writeShort(record.type)
821 if record.unique and self.multicast:
821 if record.unique and self.multicast:
822 self.writeShort(record.clazz | _CLASS_UNIQUE)
822 self.writeShort(record.clazz | _CLASS_UNIQUE)
823 else:
823 else:
824 self.writeShort(record.clazz)
824 self.writeShort(record.clazz)
825 if now == 0:
825 if now == 0:
826 self.writeInt(record.ttl)
826 self.writeInt(record.ttl)
827 else:
827 else:
828 self.writeInt(record.getRemainingTTL(now))
828 self.writeInt(record.getRemainingTTL(now))
829 index = len(self.data)
829 index = len(self.data)
830 # Adjust size for the short we will write before this record
830 # Adjust size for the short we will write before this record
831 #
831 #
832 self.size += 2
832 self.size += 2
833 record.write(self)
833 record.write(self)
834 self.size -= 2
834 self.size -= 2
835
835
836 length = len(b''.join(self.data[index:]))
836 length = len(b''.join(self.data[index:]))
837 self.insertShort(index, length) # Here is the short we adjusted for
837 self.insertShort(index, length) # Here is the short we adjusted for
838
838
839 def packet(self):
839 def packet(self):
840 """Returns a string containing the packet's bytes
840 """Returns a string containing the packet's bytes
841
841
842 No further parts should be added to the packet once this
842 No further parts should be added to the packet once this
843 is done."""
843 is done."""
844 if not self.finished:
844 if not self.finished:
845 self.finished = 1
845 self.finished = 1
846 for question in self.questions:
846 for question in self.questions:
847 self.writeQuestion(question)
847 self.writeQuestion(question)
848 for answer, time_ in self.answers:
848 for answer, time_ in self.answers:
849 self.writeRecord(answer, time_)
849 self.writeRecord(answer, time_)
850 for authority in self.authorities:
850 for authority in self.authorities:
851 self.writeRecord(authority, 0)
851 self.writeRecord(authority, 0)
852 for additional in self.additionals:
852 for additional in self.additionals:
853 self.writeRecord(additional, 0)
853 self.writeRecord(additional, 0)
854
854
855 self.insertShort(0, len(self.additionals))
855 self.insertShort(0, len(self.additionals))
856 self.insertShort(0, len(self.authorities))
856 self.insertShort(0, len(self.authorities))
857 self.insertShort(0, len(self.answers))
857 self.insertShort(0, len(self.answers))
858 self.insertShort(0, len(self.questions))
858 self.insertShort(0, len(self.questions))
859 self.insertShort(0, self.flags)
859 self.insertShort(0, self.flags)
860 if self.multicast:
860 if self.multicast:
861 self.insertShort(0, 0)
861 self.insertShort(0, 0)
862 else:
862 else:
863 self.insertShort(0, self.id)
863 self.insertShort(0, self.id)
864 return b''.join(self.data)
864 return b''.join(self.data)
865
865
866
866
867 class DNSCache:
867 class DNSCache:
868 """A cache of DNS entries"""
868 """A cache of DNS entries"""
869
869
870 def __init__(self):
870 def __init__(self):
871 self.cache = {}
871 self.cache = {}
872
872
873 def add(self, entry):
873 def add(self, entry):
874 """Adds an entry"""
874 """Adds an entry"""
875 try:
875 try:
876 list = self.cache[entry.key]
876 list = self.cache[entry.key]
877 except KeyError:
877 except KeyError:
878 list = self.cache[entry.key] = []
878 list = self.cache[entry.key] = []
879 list.append(entry)
879 list.append(entry)
880
880
881 def remove(self, entry):
881 def remove(self, entry):
882 """Removes an entry"""
882 """Removes an entry"""
883 try:
883 try:
884 list = self.cache[entry.key]
884 list = self.cache[entry.key]
885 list.remove(entry)
885 list.remove(entry)
886 except KeyError:
886 except KeyError:
887 pass
887 pass
888
888
889 def get(self, entry):
889 def get(self, entry):
890 """Gets an entry by key. Will return None if there is no
890 """Gets an entry by key. Will return None if there is no
891 matching entry."""
891 matching entry."""
892 try:
892 try:
893 list = self.cache[entry.key]
893 list = self.cache[entry.key]
894 return list[list.index(entry)]
894 return list[list.index(entry)]
895 except (KeyError, ValueError):
895 except (KeyError, ValueError):
896 return None
896 return None
897
897
898 def getByDetails(self, name, type, clazz):
898 def getByDetails(self, name, type, clazz):
899 """Gets an entry by details. Will return None if there is
899 """Gets an entry by details. Will return None if there is
900 no matching entry."""
900 no matching entry."""
901 entry = DNSEntry(name, type, clazz)
901 entry = DNSEntry(name, type, clazz)
902 return self.get(entry)
902 return self.get(entry)
903
903
904 def entriesWithName(self, name):
904 def entriesWithName(self, name):
905 """Returns a list of entries whose key matches the name."""
905 """Returns a list of entries whose key matches the name."""
906 try:
906 try:
907 return self.cache[name]
907 return self.cache[name]
908 except KeyError:
908 except KeyError:
909 return []
909 return []
910
910
911 def entries(self):
911 def entries(self):
912 """Returns a list of all entries"""
912 """Returns a list of all entries"""
913 try:
913 try:
914 return list(itertools.chain.from_iterable(self.cache.values()))
914 return list(itertools.chain.from_iterable(self.cache.values()))
915 except Exception:
915 except Exception:
916 return []
916 return []
917
917
918
918
919 class Engine(threading.Thread):
919 class Engine(threading.Thread):
920 """An engine wraps read access to sockets, allowing objects that
920 """An engine wraps read access to sockets, allowing objects that
921 need to receive data from sockets to be called back when the
921 need to receive data from sockets to be called back when the
922 sockets are ready.
922 sockets are ready.
923
923
924 A reader needs a handle_read() method, which is called when the socket
924 A reader needs a handle_read() method, which is called when the socket
925 it is interested in is ready for reading.
925 it is interested in is ready for reading.
926
926
927 Writers are not implemented here, because we only send short
927 Writers are not implemented here, because we only send short
928 packets.
928 packets.
929 """
929 """
930
930
931 def __init__(self, zeroconf):
931 def __init__(self, zeroconf):
932 threading.Thread.__init__(self)
932 threading.Thread.__init__(self)
933 self.zeroconf = zeroconf
933 self.zeroconf = zeroconf
934 self.readers = {} # maps socket to reader
934 self.readers = {} # maps socket to reader
935 self.timeout = 5
935 self.timeout = 5
936 self.condition = threading.Condition()
936 self.condition = threading.Condition()
937 self.start()
937 self.start()
938
938
939 def run(self):
939 def run(self):
940 while not globals()[b'_GLOBAL_DONE']:
940 while not globals()[b'_GLOBAL_DONE']:
941 rs = self.getReaders()
941 rs = self.getReaders()
942 if len(rs) == 0:
942 if len(rs) == 0:
943 # No sockets to manage, but we wait for the timeout
943 # No sockets to manage, but we wait for the timeout
944 # or addition of a socket
944 # or addition of a socket
945 #
945 #
946 self.condition.acquire()
946 self.condition.acquire()
947 self.condition.wait(self.timeout)
947 self.condition.wait(self.timeout)
948 self.condition.release()
948 self.condition.release()
949 else:
949 else:
950 try:
950 try:
951 rr, wr, er = select.select(rs, [], [], self.timeout)
951 rr, wr, er = select.select(rs, [], [], self.timeout)
952 for sock in rr:
952 for sock in rr:
953 try:
953 try:
954 self.readers[sock].handle_read()
954 self.readers[sock].handle_read()
955 except Exception:
955 except Exception:
956 if not globals()[b'_GLOBAL_DONE']:
956 if not globals()[b'_GLOBAL_DONE']:
957 traceback.print_exc()
957 traceback.print_exc()
958 except Exception:
958 except Exception:
959 pass
959 pass
960
960
961 def getReaders(self):
961 def getReaders(self):
962 self.condition.acquire()
962 self.condition.acquire()
963 result = self.readers.keys()
963 result = self.readers.keys()
964 self.condition.release()
964 self.condition.release()
965 return result
965 return result
966
966
967 def addReader(self, reader, socket):
967 def addReader(self, reader, socket):
968 self.condition.acquire()
968 self.condition.acquire()
969 self.readers[socket] = reader
969 self.readers[socket] = reader
970 self.condition.notify()
970 self.condition.notify()
971 self.condition.release()
971 self.condition.release()
972
972
973 def delReader(self, socket):
973 def delReader(self, socket):
974 self.condition.acquire()
974 self.condition.acquire()
975 del self.readers[socket]
975 del self.readers[socket]
976 self.condition.notify()
976 self.condition.notify()
977 self.condition.release()
977 self.condition.release()
978
978
979 def notify(self):
979 def notify(self):
980 self.condition.acquire()
980 self.condition.acquire()
981 self.condition.notify()
981 self.condition.notify()
982 self.condition.release()
982 self.condition.release()
983
983
984
984
985 class Listener:
985 class Listener:
986 """A Listener is used by this module to listen on the multicast
986 """A Listener is used by this module to listen on the multicast
987 group to which DNS messages are sent, allowing the implementation
987 group to which DNS messages are sent, allowing the implementation
988 to cache information as it arrives.
988 to cache information as it arrives.
989
989
990 It requires registration with an Engine object in order to have
990 It requires registration with an Engine object in order to have
991 the read() method called when a socket is available for reading."""
991 the read() method called when a socket is available for reading."""
992
992
993 def __init__(self, zeroconf):
993 def __init__(self, zeroconf):
994 self.zeroconf = zeroconf
994 self.zeroconf = zeroconf
995 self.zeroconf.engine.addReader(self, self.zeroconf.socket)
995 self.zeroconf.engine.addReader(self, self.zeroconf.socket)
996
996
997 def handle_read(self):
997 def handle_read(self):
998 sock = self.zeroconf.socket
998 sock = self.zeroconf.socket
999 try:
999 try:
1000 data, (addr, port) = sock.recvfrom(_MAX_MSG_ABSOLUTE)
1000 data, (addr, port) = sock.recvfrom(_MAX_MSG_ABSOLUTE)
1001 except socket.error as e:
1001 except socket.error as e:
1002 if e.errno == errno.EBADF:
1002 if e.errno == errno.EBADF:
1003 # some other thread may close the socket
1003 # some other thread may close the socket
1004 return
1004 return
1005 else:
1005 else:
1006 raise
1006 raise
1007 self.data = data
1007 self.data = data
1008 msg = DNSIncoming(data)
1008 msg = DNSIncoming(data)
1009 if msg.isQuery():
1009 if msg.isQuery():
1010 # Always multicast responses
1010 # Always multicast responses
1011 #
1011 #
1012 if port == _MDNS_PORT:
1012 if port == _MDNS_PORT:
1013 self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
1013 self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
1014 # If it's not a multicast query, reply via unicast
1014 # If it's not a multicast query, reply via unicast
1015 # and multicast
1015 # and multicast
1016 #
1016 #
1017 elif port == _DNS_PORT:
1017 elif port == _DNS_PORT:
1018 self.zeroconf.handleQuery(msg, addr, port)
1018 self.zeroconf.handleQuery(msg, addr, port)
1019 self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
1019 self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
1020 else:
1020 else:
1021 self.zeroconf.handleResponse(msg)
1021 self.zeroconf.handleResponse(msg)
1022
1022
1023
1023
1024 class Reaper(threading.Thread):
1024 class Reaper(threading.Thread):
1025 """A Reaper is used by this module to remove cache entries that
1025 """A Reaper is used by this module to remove cache entries that
1026 have expired."""
1026 have expired."""
1027
1027
1028 def __init__(self, zeroconf):
1028 def __init__(self, zeroconf):
1029 threading.Thread.__init__(self)
1029 threading.Thread.__init__(self)
1030 self.zeroconf = zeroconf
1030 self.zeroconf = zeroconf
1031 self.start()
1031 self.start()
1032
1032
1033 def run(self):
1033 def run(self):
1034 while True:
1034 while True:
1035 self.zeroconf.wait(10 * 1000)
1035 self.zeroconf.wait(10 * 1000)
1036 if globals()[b'_GLOBAL_DONE']:
1036 if globals()[b'_GLOBAL_DONE']:
1037 return
1037 return
1038 now = currentTimeMillis()
1038 now = currentTimeMillis()
1039 for record in self.zeroconf.cache.entries():
1039 for record in self.zeroconf.cache.entries():
1040 if record.isExpired(now):
1040 if record.isExpired(now):
1041 self.zeroconf.updateRecord(now, record)
1041 self.zeroconf.updateRecord(now, record)
1042 self.zeroconf.cache.remove(record)
1042 self.zeroconf.cache.remove(record)
1043
1043
1044
1044
1045 class ServiceBrowser(threading.Thread):
1045 class ServiceBrowser(threading.Thread):
1046 """Used to browse for a service of a specific type.
1046 """Used to browse for a service of a specific type.
1047
1047
1048 The listener object will have its addService() and
1048 The listener object will have its addService() and
1049 removeService() methods called when this browser
1049 removeService() methods called when this browser
1050 discovers changes in the services availability."""
1050 discovers changes in the services availability."""
1051
1051
1052 def __init__(self, zeroconf, type, listener):
1052 def __init__(self, zeroconf, type, listener):
1053 """Creates a browser for a specific type"""
1053 """Creates a browser for a specific type"""
1054 threading.Thread.__init__(self)
1054 threading.Thread.__init__(self)
1055 self.zeroconf = zeroconf
1055 self.zeroconf = zeroconf
1056 self.type = type
1056 self.type = type
1057 self.listener = listener
1057 self.listener = listener
1058 self.services = {}
1058 self.services = {}
1059 self.nexttime = currentTimeMillis()
1059 self.nexttime = currentTimeMillis()
1060 self.delay = _BROWSER_TIME
1060 self.delay = _BROWSER_TIME
1061 self.list = []
1061 self.list = []
1062
1062
1063 self.done = 0
1063 self.done = 0
1064
1064
1065 self.zeroconf.addListener(
1065 self.zeroconf.addListener(
1066 self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN)
1066 self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN)
1067 )
1067 )
1068 self.start()
1068 self.start()
1069
1069
1070 def updateRecord(self, zeroconf, now, record):
1070 def updateRecord(self, zeroconf, now, record):
1071 """Callback invoked by Zeroconf when new information arrives.
1071 """Callback invoked by Zeroconf when new information arrives.
1072
1072
1073 Updates information required by browser in the Zeroconf cache."""
1073 Updates information required by browser in the Zeroconf cache."""
1074 if record.type == _TYPE_PTR and record.name == self.type:
1074 if record.type == _TYPE_PTR and record.name == self.type:
1075 expired = record.isExpired(now)
1075 expired = record.isExpired(now)
1076 try:
1076 try:
1077 oldrecord = self.services[record.alias.lower()]
1077 oldrecord = self.services[record.alias.lower()]
1078 if not expired:
1078 if not expired:
1079 oldrecord.resetTTL(record)
1079 oldrecord.resetTTL(record)
1080 else:
1080 else:
1081 del self.services[record.alias.lower()]
1081 del self.services[record.alias.lower()]
1082 callback = lambda x: self.listener.removeService(
1082 callback = lambda x: self.listener.removeService(
1083 x, self.type, record.alias
1083 x, self.type, record.alias
1084 )
1084 )
1085 self.list.append(callback)
1085 self.list.append(callback)
1086 return
1086 return
1087 except Exception:
1087 except Exception:
1088 if not expired:
1088 if not expired:
1089 self.services[record.alias.lower()] = record
1089 self.services[record.alias.lower()] = record
1090 callback = lambda x: self.listener.addService(
1090 callback = lambda x: self.listener.addService(
1091 x, self.type, record.alias
1091 x, self.type, record.alias
1092 )
1092 )
1093 self.list.append(callback)
1093 self.list.append(callback)
1094
1094
1095 expires = record.getExpirationTime(75)
1095 expires = record.getExpirationTime(75)
1096 if expires < self.nexttime:
1096 if expires < self.nexttime:
1097 self.nexttime = expires
1097 self.nexttime = expires
1098
1098
1099 def cancel(self):
1099 def cancel(self):
1100 self.done = 1
1100 self.done = 1
1101 self.zeroconf.notifyAll()
1101 self.zeroconf.notifyAll()
1102
1102
1103 def run(self):
1103 def run(self):
1104 while True:
1104 while True:
1105 event = None
1105 event = None
1106 now = currentTimeMillis()
1106 now = currentTimeMillis()
1107 if len(self.list) == 0 and self.nexttime > now:
1107 if len(self.list) == 0 and self.nexttime > now:
1108 self.zeroconf.wait(self.nexttime - now)
1108 self.zeroconf.wait(self.nexttime - now)
1109 if globals()[b'_GLOBAL_DONE'] or self.done:
1109 if globals()[b'_GLOBAL_DONE'] or self.done:
1110 return
1110 return
1111 now = currentTimeMillis()
1111 now = currentTimeMillis()
1112
1112
1113 if self.nexttime <= now:
1113 if self.nexttime <= now:
1114 out = DNSOutgoing(_FLAGS_QR_QUERY)
1114 out = DNSOutgoing(_FLAGS_QR_QUERY)
1115 out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
1115 out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
1116 for record in self.services.values():
1116 for record in self.services.values():
1117 if not record.isExpired(now):
1117 if not record.isExpired(now):
1118 out.addAnswerAtTime(record, now)
1118 out.addAnswerAtTime(record, now)
1119 self.zeroconf.send(out)
1119 self.zeroconf.send(out)
1120 self.nexttime = now + self.delay
1120 self.nexttime = now + self.delay
1121 self.delay = min(20 * 1000, self.delay * 2)
1121 self.delay = min(20 * 1000, self.delay * 2)
1122
1122
1123 if len(self.list) > 0:
1123 if len(self.list) > 0:
1124 event = self.list.pop(0)
1124 event = self.list.pop(0)
1125
1125
1126 if event is not None:
1126 if event is not None:
1127 event(self.zeroconf)
1127 event(self.zeroconf)
1128
1128
1129
1129
1130 class ServiceInfo:
1130 class ServiceInfo:
1131 """Service information"""
1131 """Service information"""
1132
1132
1133 def __init__(
1133 def __init__(
1134 self,
1134 self,
1135 type,
1135 type,
1136 name,
1136 name,
1137 address=None,
1137 address=None,
1138 port=None,
1138 port=None,
1139 weight=0,
1139 weight=0,
1140 priority=0,
1140 priority=0,
1141 properties=None,
1141 properties=None,
1142 server=None,
1142 server=None,
1143 ):
1143 ):
1144 """Create a service description.
1144 """Create a service description.
1145
1145
1146 type: fully qualified service type name
1146 type: fully qualified service type name
1147 name: fully qualified service name
1147 name: fully qualified service name
1148 address: IP address as unsigned short, network byte order
1148 address: IP address as unsigned short, network byte order
1149 port: port that the service runs on
1149 port: port that the service runs on
1150 weight: weight of the service
1150 weight: weight of the service
1151 priority: priority of the service
1151 priority: priority of the service
1152 properties: dictionary of properties (or a string holding the bytes for
1152 properties: dictionary of properties (or a string holding the bytes for
1153 the text field)
1153 the text field)
1154 server: fully qualified name for service host (defaults to name)"""
1154 server: fully qualified name for service host (defaults to name)"""
1155
1155
1156 if not name.endswith(type):
1156 if not name.endswith(type):
1157 raise BadTypeInNameException
1157 raise BadTypeInNameException
1158 self.type = type
1158 self.type = type
1159 self.name = name
1159 self.name = name
1160 self.address = address
1160 self.address = address
1161 self.port = port
1161 self.port = port
1162 self.weight = weight
1162 self.weight = weight
1163 self.priority = priority
1163 self.priority = priority
1164 if server:
1164 if server:
1165 self.server = server
1165 self.server = server
1166 else:
1166 else:
1167 self.server = name
1167 self.server = name
1168 self.setProperties(properties)
1168 self.setProperties(properties)
1169
1169
1170 def setProperties(self, properties):
1170 def setProperties(self, properties):
1171 """Sets properties and text of this info from a dictionary"""
1171 """Sets properties and text of this info from a dictionary"""
1172 if isinstance(properties, dict):
1172 if isinstance(properties, dict):
1173 self.properties = properties
1173 self.properties = properties
1174 list = []
1174 list = []
1175 result = b''
1175 result = b''
1176 for key in properties:
1176 for key in properties:
1177 value = properties[key]
1177 value = properties[key]
1178 if value is None:
1178 if value is None:
1179 suffix = b''
1179 suffix = b''
1180 elif isinstance(value, str):
1180 elif isinstance(value, str):
1181 suffix = value
1181 suffix = value
1182 elif isinstance(value, int):
1182 elif isinstance(value, int):
1183 if value:
1183 if value:
1184 suffix = b'true'
1184 suffix = b'true'
1185 else:
1185 else:
1186 suffix = b'false'
1186 suffix = b'false'
1187 else:
1187 else:
1188 suffix = b''
1188 suffix = b''
1189 list.append(b'='.join((key, suffix)))
1189 list.append(b'='.join((key, suffix)))
1190 for item in list:
1190 for item in list:
1191 result = b''.join(
1191 result = b''.join(
1192 (
1192 (
1193 result,
1193 result,
1194 struct.pack(b'!c', pycompat.bytechr(len(item))),
1194 struct.pack(b'!c', pycompat.bytechr(len(item))),
1195 item,
1195 item,
1196 )
1196 )
1197 )
1197 )
1198 self.text = result
1198 self.text = result
1199 else:
1199 else:
1200 self.text = properties
1200 self.text = properties
1201
1201
1202 def setText(self, text):
1202 def setText(self, text):
1203 """Sets properties and text given a text field"""
1203 """Sets properties and text given a text field"""
1204 self.text = text
1204 self.text = text
1205 try:
1205 try:
1206 result = {}
1206 result = {}
1207 end = len(text)
1207 end = len(text)
1208 index = 0
1208 index = 0
1209 strs = []
1209 strs = []
1210 while index < end:
1210 while index < end:
1211 length = ord(text[index])
1211 length = ord(text[index])
1212 index += 1
1212 index += 1
1213 strs.append(text[index : index + length])
1213 strs.append(text[index : index + length])
1214 index += length
1214 index += length
1215
1215
1216 for s in strs:
1216 for s in strs:
1217 eindex = s.find(b'=')
1217 eindex = s.find(b'=')
1218 if eindex == -1:
1218 if eindex == -1:
1219 # No equals sign at all
1219 # No equals sign at all
1220 key = s
1220 key = s
1221 value = 0
1221 value = 0
1222 else:
1222 else:
1223 key = s[:eindex]
1223 key = s[:eindex]
1224 value = s[eindex + 1 :]
1224 value = s[eindex + 1 :]
1225 if value == b'true':
1225 if value == b'true':
1226 value = 1
1226 value = 1
1227 elif value == b'false' or not value:
1227 elif value == b'false' or not value:
1228 value = 0
1228 value = 0
1229
1229
1230 # Only update non-existent properties
1230 # Only update non-existent properties
1231 if key and result.get(key) is None:
1231 if key and result.get(key) is None:
1232 result[key] = value
1232 result[key] = value
1233
1233
1234 self.properties = result
1234 self.properties = result
1235 except Exception:
1235 except Exception:
1236 traceback.print_exc()
1236 traceback.print_exc()
1237 self.properties = None
1237 self.properties = None
1238
1238
1239 def getType(self):
1239 def getType(self):
1240 """Type accessor"""
1240 """Type accessor"""
1241 return self.type
1241 return self.type
1242
1242
1243 def getName(self):
1243 def getName(self):
1244 """Name accessor"""
1244 """Name accessor"""
1245 if self.type is not None and self.name.endswith(b"." + self.type):
1245 if self.type is not None and self.name.endswith(b"." + self.type):
1246 return self.name[: len(self.name) - len(self.type) - 1]
1246 return self.name[: len(self.name) - len(self.type) - 1]
1247 return self.name
1247 return self.name
1248
1248
1249 def getAddress(self):
1249 def getAddress(self):
1250 """Address accessor"""
1250 """Address accessor"""
1251 return self.address
1251 return self.address
1252
1252
1253 def getPort(self):
1253 def getPort(self):
1254 """Port accessor"""
1254 """Port accessor"""
1255 return self.port
1255 return self.port
1256
1256
1257 def getPriority(self):
1257 def getPriority(self):
1258 """Priority accessor"""
1258 """Priority accessor"""
1259 return self.priority
1259 return self.priority
1260
1260
1261 def getWeight(self):
1261 def getWeight(self):
1262 """Weight accessor"""
1262 """Weight accessor"""
1263 return self.weight
1263 return self.weight
1264
1264
1265 def getProperties(self):
1265 def getProperties(self):
1266 """Properties accessor"""
1266 """Properties accessor"""
1267 return self.properties
1267 return self.properties
1268
1268
1269 def getText(self):
1269 def getText(self):
1270 """Text accessor"""
1270 """Text accessor"""
1271 return self.text
1271 return self.text
1272
1272
1273 def getServer(self):
1273 def getServer(self):
1274 """Server accessor"""
1274 """Server accessor"""
1275 return self.server
1275 return self.server
1276
1276
1277 def updateRecord(self, zeroconf, now, record):
1277 def updateRecord(self, zeroconf, now, record):
1278 """Updates service information from a DNS record"""
1278 """Updates service information from a DNS record"""
1279 if record is not None and not record.isExpired(now):
1279 if record is not None and not record.isExpired(now):
1280 if record.type == _TYPE_A:
1280 if record.type == _TYPE_A:
1281 # if record.name == self.name:
1281 # if record.name == self.name:
1282 if record.name == self.server:
1282 if record.name == self.server:
1283 self.address = record.address
1283 self.address = record.address
1284 elif record.type == _TYPE_SRV:
1284 elif record.type == _TYPE_SRV:
1285 if record.name == self.name:
1285 if record.name == self.name:
1286 self.server = record.server
1286 self.server = record.server
1287 self.port = record.port
1287 self.port = record.port
1288 self.weight = record.weight
1288 self.weight = record.weight
1289 self.priority = record.priority
1289 self.priority = record.priority
1290 # self.address = None
1290 # self.address = None
1291 self.updateRecord(
1291 self.updateRecord(
1292 zeroconf,
1292 zeroconf,
1293 now,
1293 now,
1294 zeroconf.cache.getByDetails(
1294 zeroconf.cache.getByDetails(
1295 self.server, _TYPE_A, _CLASS_IN
1295 self.server, _TYPE_A, _CLASS_IN
1296 ),
1296 ),
1297 )
1297 )
1298 elif record.type == _TYPE_TXT:
1298 elif record.type == _TYPE_TXT:
1299 if record.name == self.name:
1299 if record.name == self.name:
1300 self.setText(record.text)
1300 self.setText(record.text)
1301
1301
1302 def request(self, zeroconf, timeout):
1302 def request(self, zeroconf, timeout):
1303 """Returns true if the service could be discovered on the
1303 """Returns true if the service could be discovered on the
1304 network, and updates this object with details discovered.
1304 network, and updates this object with details discovered.
1305 """
1305 """
1306 now = currentTimeMillis()
1306 now = currentTimeMillis()
1307 delay = _LISTENER_TIME
1307 delay = _LISTENER_TIME
1308 next = now + delay
1308 next = now + delay
1309 last = now + timeout
1309 last = now + timeout
1310 try:
1310 try:
1311 zeroconf.addListener(
1311 zeroconf.addListener(
1312 self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN)
1312 self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN)
1313 )
1313 )
1314 while (
1314 while (
1315 self.server is None or self.address is None or self.text is None
1315 self.server is None or self.address is None or self.text is None
1316 ):
1316 ):
1317 if last <= now:
1317 if last <= now:
1318 return 0
1318 return 0
1319 if next <= now:
1319 if next <= now:
1320 out = DNSOutgoing(_FLAGS_QR_QUERY)
1320 out = DNSOutgoing(_FLAGS_QR_QUERY)
1321 out.addQuestion(
1321 out.addQuestion(
1322 DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN)
1322 DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN)
1323 )
1323 )
1324 out.addAnswerAtTime(
1324 out.addAnswerAtTime(
1325 zeroconf.cache.getByDetails(
1325 zeroconf.cache.getByDetails(
1326 self.name, _TYPE_SRV, _CLASS_IN
1326 self.name, _TYPE_SRV, _CLASS_IN
1327 ),
1327 ),
1328 now,
1328 now,
1329 )
1329 )
1330 out.addQuestion(
1330 out.addQuestion(
1331 DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN)
1331 DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN)
1332 )
1332 )
1333 out.addAnswerAtTime(
1333 out.addAnswerAtTime(
1334 zeroconf.cache.getByDetails(
1334 zeroconf.cache.getByDetails(
1335 self.name, _TYPE_TXT, _CLASS_IN
1335 self.name, _TYPE_TXT, _CLASS_IN
1336 ),
1336 ),
1337 now,
1337 now,
1338 )
1338 )
1339 if self.server is not None:
1339 if self.server is not None:
1340 out.addQuestion(
1340 out.addQuestion(
1341 DNSQuestion(self.server, _TYPE_A, _CLASS_IN)
1341 DNSQuestion(self.server, _TYPE_A, _CLASS_IN)
1342 )
1342 )
1343 out.addAnswerAtTime(
1343 out.addAnswerAtTime(
1344 zeroconf.cache.getByDetails(
1344 zeroconf.cache.getByDetails(
1345 self.server, _TYPE_A, _CLASS_IN
1345 self.server, _TYPE_A, _CLASS_IN
1346 ),
1346 ),
1347 now,
1347 now,
1348 )
1348 )
1349 zeroconf.send(out)
1349 zeroconf.send(out)
1350 next = now + delay
1350 next = now + delay
1351 delay = delay * 2
1351 delay = delay * 2
1352
1352
1353 zeroconf.wait(min(next, last) - now)
1353 zeroconf.wait(min(next, last) - now)
1354 now = currentTimeMillis()
1354 now = currentTimeMillis()
1355 result = 1
1355 result = 1
1356 finally:
1356 finally:
1357 zeroconf.removeListener(self)
1357 zeroconf.removeListener(self)
1358
1358
1359 return result
1359 return result
1360
1360
1361 def __eq__(self, other):
1361 def __eq__(self, other):
1362 """Tests equality of service name"""
1362 """Tests equality of service name"""
1363 if isinstance(other, ServiceInfo):
1363 if isinstance(other, ServiceInfo):
1364 return other.name == self.name
1364 return other.name == self.name
1365 return 0
1365 return 0
1366
1366
1367 def __ne__(self, other):
1367 def __ne__(self, other):
1368 """Non-equality test"""
1368 """Non-equality test"""
1369 return not self.__eq__(other)
1369 return not self.__eq__(other)
1370
1370
1371 def __repr__(self):
1371 def __repr__(self):
1372 """String representation"""
1372 """String representation"""
1373 result = b"service[%s,%s:%s," % (
1373 result = b"service[%s,%s:%s," % (
1374 self.name,
1374 self.name,
1375 socket.inet_ntoa(self.getAddress()),
1375 socket.inet_ntoa(self.getAddress()),
1376 self.port,
1376 self.port,
1377 )
1377 )
1378 if self.text is None:
1378 if self.text is None:
1379 result += b"None"
1379 result += b"None"
1380 else:
1380 else:
1381 if len(self.text) < 20:
1381 if len(self.text) < 20:
1382 result += self.text
1382 result += self.text
1383 else:
1383 else:
1384 result += self.text[:17] + b"..."
1384 result += self.text[:17] + b"..."
1385 result += b"]"
1385 result += b"]"
1386 return result
1386 return result
1387
1387
1388
1388
1389 class Zeroconf:
1389 class Zeroconf:
1390 """Implementation of Zeroconf Multicast DNS Service Discovery
1390 """Implementation of Zeroconf Multicast DNS Service Discovery
1391
1391
1392 Supports registration, unregistration, queries and browsing.
1392 Supports registration, unregistration, queries and browsing.
1393 """
1393 """
1394
1394
1395 def __init__(self, bindaddress=None):
1395 def __init__(self, bindaddress=None):
1396 """Creates an instance of the Zeroconf class, establishing
1396 """Creates an instance of the Zeroconf class, establishing
1397 multicast communications, listening and reaping threads."""
1397 multicast communications, listening and reaping threads."""
1398 globals()[b'_GLOBAL_DONE'] = 0
1398 globals()[b'_GLOBAL_DONE'] = 0
1399 if bindaddress is None:
1399 if bindaddress is None:
1400 self.intf = socket.gethostbyname(socket.gethostname())
1400 self.intf = socket.gethostbyname(socket.gethostname())
1401 else:
1401 else:
1402 self.intf = bindaddress
1402 self.intf = bindaddress
1403 self.group = (b'', _MDNS_PORT)
1403 self.group = (b'', _MDNS_PORT)
1404 self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1404 self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1405 try:
1405 try:
1406 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1406 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1407 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
1407 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
1408 except Exception:
1408 except Exception:
1409 # SO_REUSEADDR should be equivalent to SO_REUSEPORT for
1409 # SO_REUSEADDR should be equivalent to SO_REUSEPORT for
1410 # multicast UDP sockets (p 731, "TCP/IP Illustrated,
1410 # multicast UDP sockets (p 731, "TCP/IP Illustrated,
1411 # Volume 2"), but some BSD-derived systems require
1411 # Volume 2"), but some BSD-derived systems require
1412 # SO_REUSEPORT to be specified explicitly. Also, not all
1412 # SO_REUSEPORT to be specified explicitly. Also, not all
1413 # versions of Python have SO_REUSEPORT available. So
1413 # versions of Python have SO_REUSEPORT available. So
1414 # if you're on a BSD-based system, and haven't upgraded
1414 # if you're on a BSD-based system, and haven't upgraded
1415 # to Python 2.3 yet, you may find this library doesn't
1415 # to Python 2.3 yet, you may find this library doesn't
1416 # work as expected.
1416 # work as expected.
1417 #
1417 #
1418 pass
1418 pass
1419 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, b"\xff")
1419 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, b"\xff")
1420 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, b"\x01")
1420 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, b"\x01")
1421 try:
1421 try:
1422 self.socket.bind(self.group)
1422 self.socket.bind(self.group)
1423 except Exception:
1423 except Exception:
1424 # Some versions of linux raise an exception even though
1424 # Some versions of linux raise an exception even though
1425 # SO_REUSEADDR and SO_REUSEPORT have been set, so ignore it
1425 # SO_REUSEADDR and SO_REUSEPORT have been set, so ignore it
1426 pass
1426 pass
1427 self.socket.setsockopt(
1427 self.socket.setsockopt(
1428 socket.SOL_IP,
1428 socket.SOL_IP,
1429 socket.IP_ADD_MEMBERSHIP,
1429 socket.IP_ADD_MEMBERSHIP,
1430 socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'),
1430 socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'),
1431 )
1431 )
1432
1432
1433 self.listeners = []
1433 self.listeners = []
1434 self.browsers = []
1434 self.browsers = []
1435 self.services = {}
1435 self.services = {}
1436 self.servicetypes = {}
1436 self.servicetypes = {}
1437
1437
1438 self.cache = DNSCache()
1438 self.cache = DNSCache()
1439
1439
1440 self.condition = threading.Condition()
1440 self.condition = threading.Condition()
1441
1441
1442 self.engine = Engine(self)
1442 self.engine = Engine(self)
1443 self.listener = Listener(self)
1443 self.listener = Listener(self)
1444 self.reaper = Reaper(self)
1444 self.reaper = Reaper(self)
1445
1445
1446 def isLoopback(self):
1446 def isLoopback(self):
1447 return self.intf.startswith(b"127.0.0.1")
1447 return self.intf.startswith(b"127.0.0.1")
1448
1448
1449 def isLinklocal(self):
1449 def isLinklocal(self):
1450 return self.intf.startswith(b"169.254.")
1450 return self.intf.startswith(b"169.254.")
1451
1451
1452 def wait(self, timeout):
1452 def wait(self, timeout):
1453 """Calling thread waits for a given number of milliseconds or
1453 """Calling thread waits for a given number of milliseconds or
1454 until notified."""
1454 until notified."""
1455 self.condition.acquire()
1455 self.condition.acquire()
1456 self.condition.wait(timeout / 1000)
1456 self.condition.wait(timeout / 1000)
1457 self.condition.release()
1457 self.condition.release()
1458
1458
1459 def notifyAll(self):
1459 def notifyAll(self):
1460 """Notifies all waiting threads"""
1460 """Notifies all waiting threads"""
1461 self.condition.acquire()
1461 self.condition.acquire()
1462 self.condition.notify_all()
1462 self.condition.notify_all()
1463 self.condition.release()
1463 self.condition.release()
1464
1464
1465 def getServiceInfo(self, type, name, timeout=3000):
1465 def getServiceInfo(self, type, name, timeout=3000):
1466 """Returns network's service information for a particular
1466 """Returns network's service information for a particular
1467 name and type, or None if no service matches by the timeout,
1467 name and type, or None if no service matches by the timeout,
1468 which defaults to 3 seconds."""
1468 which defaults to 3 seconds."""
1469 info = ServiceInfo(type, name)
1469 info = ServiceInfo(type, name)
1470 if info.request(self, timeout):
1470 if info.request(self, timeout):
1471 return info
1471 return info
1472 return None
1472 return None
1473
1473
1474 def addServiceListener(self, type, listener):
1474 def addServiceListener(self, type, listener):
1475 """Adds a listener for a particular service type. This object
1475 """Adds a listener for a particular service type. This object
1476 will then have its updateRecord method called when information
1476 will then have its updateRecord method called when information
1477 arrives for that type."""
1477 arrives for that type."""
1478 self.removeServiceListener(listener)
1478 self.removeServiceListener(listener)
1479 self.browsers.append(ServiceBrowser(self, type, listener))
1479 self.browsers.append(ServiceBrowser(self, type, listener))
1480
1480
1481 def removeServiceListener(self, listener):
1481 def removeServiceListener(self, listener):
1482 """Removes a listener from the set that is currently listening."""
1482 """Removes a listener from the set that is currently listening."""
1483 for browser in self.browsers:
1483 for browser in self.browsers:
1484 if browser.listener == listener:
1484 if browser.listener == listener:
1485 browser.cancel()
1485 browser.cancel()
1486 del browser
1486 del browser
1487
1487
1488 def registerService(self, info, ttl=_DNS_TTL):
1488 def registerService(self, info, ttl=_DNS_TTL):
1489 """Registers service information to the network with a default TTL
1489 """Registers service information to the network with a default TTL
1490 of 60 seconds. Zeroconf will then respond to requests for
1490 of 60 seconds. Zeroconf will then respond to requests for
1491 information for that service. The name of the service may be
1491 information for that service. The name of the service may be
1492 changed if needed to make it unique on the network."""
1492 changed if needed to make it unique on the network."""
1493 self.checkService(info)
1493 self.checkService(info)
1494 self.services[info.name.lower()] = info
1494 self.services[info.name.lower()] = info
1495 if info.type in self.servicetypes:
1495 if info.type in self.servicetypes:
1496 self.servicetypes[info.type] += 1
1496 self.servicetypes[info.type] += 1
1497 else:
1497 else:
1498 self.servicetypes[info.type] = 1
1498 self.servicetypes[info.type] = 1
1499 now = currentTimeMillis()
1499 now = currentTimeMillis()
1500 nexttime = now
1500 nexttime = now
1501 i = 0
1501 i = 0
1502 while i < 3:
1502 while i < 3:
1503 if now < nexttime:
1503 if now < nexttime:
1504 self.wait(nexttime - now)
1504 self.wait(nexttime - now)
1505 now = currentTimeMillis()
1505 now = currentTimeMillis()
1506 continue
1506 continue
1507 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1507 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1508 out.addAnswerAtTime(
1508 out.addAnswerAtTime(
1509 DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, ttl, info.name), 0
1509 DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, ttl, info.name), 0
1510 )
1510 )
1511 out.addAnswerAtTime(
1511 out.addAnswerAtTime(
1512 DNSService(
1512 DNSService(
1513 info.name,
1513 info.name,
1514 _TYPE_SRV,
1514 _TYPE_SRV,
1515 _CLASS_IN,
1515 _CLASS_IN,
1516 ttl,
1516 ttl,
1517 info.priority,
1517 info.priority,
1518 info.weight,
1518 info.weight,
1519 info.port,
1519 info.port,
1520 info.server,
1520 info.server,
1521 ),
1521 ),
1522 0,
1522 0,
1523 )
1523 )
1524 out.addAnswerAtTime(
1524 out.addAnswerAtTime(
1525 DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0
1525 DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0
1526 )
1526 )
1527 if info.address:
1527 if info.address:
1528 out.addAnswerAtTime(
1528 out.addAnswerAtTime(
1529 DNSAddress(
1529 DNSAddress(
1530 info.server, _TYPE_A, _CLASS_IN, ttl, info.address
1530 info.server, _TYPE_A, _CLASS_IN, ttl, info.address
1531 ),
1531 ),
1532 0,
1532 0,
1533 )
1533 )
1534 self.send(out)
1534 self.send(out)
1535 i += 1
1535 i += 1
1536 nexttime += _REGISTER_TIME
1536 nexttime += _REGISTER_TIME
1537
1537
1538 def unregisterService(self, info):
1538 def unregisterService(self, info):
1539 """Unregister a service."""
1539 """Unregister a service."""
1540 try:
1540 try:
1541 del self.services[info.name.lower()]
1541 del self.services[info.name.lower()]
1542 if self.servicetypes[info.type] > 1:
1542 if self.servicetypes[info.type] > 1:
1543 self.servicetypes[info.type] -= 1
1543 self.servicetypes[info.type] -= 1
1544 else:
1544 else:
1545 del self.servicetypes[info.type]
1545 del self.servicetypes[info.type]
1546 except KeyError:
1546 except KeyError:
1547 pass
1547 pass
1548 now = currentTimeMillis()
1548 now = currentTimeMillis()
1549 nexttime = now
1549 nexttime = now
1550 i = 0
1550 i = 0
1551 while i < 3:
1551 while i < 3:
1552 if now < nexttime:
1552 if now < nexttime:
1553 self.wait(nexttime - now)
1553 self.wait(nexttime - now)
1554 now = currentTimeMillis()
1554 now = currentTimeMillis()
1555 continue
1555 continue
1556 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1556 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1557 out.addAnswerAtTime(
1557 out.addAnswerAtTime(
1558 DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0
1558 DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0
1559 )
1559 )
1560 out.addAnswerAtTime(
1560 out.addAnswerAtTime(
1561 DNSService(
1561 DNSService(
1562 info.name,
1562 info.name,
1563 _TYPE_SRV,
1563 _TYPE_SRV,
1564 _CLASS_IN,
1564 _CLASS_IN,
1565 0,
1565 0,
1566 info.priority,
1566 info.priority,
1567 info.weight,
1567 info.weight,
1568 info.port,
1568 info.port,
1569 info.name,
1569 info.name,
1570 ),
1570 ),
1571 0,
1571 0,
1572 )
1572 )
1573 out.addAnswerAtTime(
1573 out.addAnswerAtTime(
1574 DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0
1574 DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0
1575 )
1575 )
1576 if info.address:
1576 if info.address:
1577 out.addAnswerAtTime(
1577 out.addAnswerAtTime(
1578 DNSAddress(
1578 DNSAddress(
1579 info.server, _TYPE_A, _CLASS_IN, 0, info.address
1579 info.server, _TYPE_A, _CLASS_IN, 0, info.address
1580 ),
1580 ),
1581 0,
1581 0,
1582 )
1582 )
1583 self.send(out)
1583 self.send(out)
1584 i += 1
1584 i += 1
1585 nexttime += _UNREGISTER_TIME
1585 nexttime += _UNREGISTER_TIME
1586
1586
1587 def unregisterAllServices(self):
1587 def unregisterAllServices(self):
1588 """Unregister all registered services."""
1588 """Unregister all registered services."""
1589 if len(self.services) > 0:
1589 if len(self.services) > 0:
1590 now = currentTimeMillis()
1590 now = currentTimeMillis()
1591 nexttime = now
1591 nexttime = now
1592 i = 0
1592 i = 0
1593 while i < 3:
1593 while i < 3:
1594 if now < nexttime:
1594 if now < nexttime:
1595 self.wait(nexttime - now)
1595 self.wait(nexttime - now)
1596 now = currentTimeMillis()
1596 now = currentTimeMillis()
1597 continue
1597 continue
1598 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1598 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1599 for info in self.services.values():
1599 for info in self.services.values():
1600 out.addAnswerAtTime(
1600 out.addAnswerAtTime(
1601 DNSPointer(
1601 DNSPointer(
1602 info.type, _TYPE_PTR, _CLASS_IN, 0, info.name
1602 info.type, _TYPE_PTR, _CLASS_IN, 0, info.name
1603 ),
1603 ),
1604 0,
1604 0,
1605 )
1605 )
1606 out.addAnswerAtTime(
1606 out.addAnswerAtTime(
1607 DNSService(
1607 DNSService(
1608 info.name,
1608 info.name,
1609 _TYPE_SRV,
1609 _TYPE_SRV,
1610 _CLASS_IN,
1610 _CLASS_IN,
1611 0,
1611 0,
1612 info.priority,
1612 info.priority,
1613 info.weight,
1613 info.weight,
1614 info.port,
1614 info.port,
1615 info.server,
1615 info.server,
1616 ),
1616 ),
1617 0,
1617 0,
1618 )
1618 )
1619 out.addAnswerAtTime(
1619 out.addAnswerAtTime(
1620 DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text),
1620 DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text),
1621 0,
1621 0,
1622 )
1622 )
1623 if info.address:
1623 if info.address:
1624 out.addAnswerAtTime(
1624 out.addAnswerAtTime(
1625 DNSAddress(
1625 DNSAddress(
1626 info.server, _TYPE_A, _CLASS_IN, 0, info.address
1626 info.server, _TYPE_A, _CLASS_IN, 0, info.address
1627 ),
1627 ),
1628 0,
1628 0,
1629 )
1629 )
1630 self.send(out)
1630 self.send(out)
1631 i += 1
1631 i += 1
1632 nexttime += _UNREGISTER_TIME
1632 nexttime += _UNREGISTER_TIME
1633
1633
1634 def checkService(self, info):
1634 def checkService(self, info):
1635 """Checks the network for a unique service name, modifying the
1635 """Checks the network for a unique service name, modifying the
1636 ServiceInfo passed in if it is not unique."""
1636 ServiceInfo passed in if it is not unique."""
1637 now = currentTimeMillis()
1637 now = currentTimeMillis()
1638 nexttime = now
1638 nexttime = now
1639 i = 0
1639 i = 0
1640 while i < 3:
1640 while i < 3:
1641 for record in self.cache.entriesWithName(info.type):
1641 for record in self.cache.entriesWithName(info.type):
1642 if (
1642 if (
1643 record.type == _TYPE_PTR
1643 record.type == _TYPE_PTR
1644 and not record.isExpired(now)
1644 and not record.isExpired(now)
1645 and record.alias == info.name
1645 and record.alias == info.name
1646 ):
1646 ):
1647 if info.name.find(b'.') < 0:
1647 if info.name.find(b'.') < 0:
1648 info.name = b"%s.[%s:%d].%s" % (
1648 info.name = b"%s.[%s:%d].%s" % (
1649 info.name,
1649 info.name,
1650 info.address,
1650 info.address,
1651 info.port,
1651 info.port,
1652 info.type,
1652 info.type,
1653 )
1653 )
1654 self.checkService(info)
1654 self.checkService(info)
1655 return
1655 return
1656 raise NonUniqueNameException
1656 raise NonUniqueNameException
1657 if now < nexttime:
1657 if now < nexttime:
1658 self.wait(nexttime - now)
1658 self.wait(nexttime - now)
1659 now = currentTimeMillis()
1659 now = currentTimeMillis()
1660 continue
1660 continue
1661 out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA)
1661 out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA)
1662 self.debug = out
1662 self.debug = out
1663 out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN))
1663 out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN))
1664 out.addAuthoritativeAnswer(
1664 out.addAuthoritativeAnswer(
1665 DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name)
1665 DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name)
1666 )
1666 )
1667 self.send(out)
1667 self.send(out)
1668 i += 1
1668 i += 1
1669 nexttime += _CHECK_TIME
1669 nexttime += _CHECK_TIME
1670
1670
1671 def addListener(self, listener, question):
1671 def addListener(self, listener, question):
1672 """Adds a listener for a given question. The listener will have
1672 """Adds a listener for a given question. The listener will have
1673 its updateRecord method called when information is available to
1673 its updateRecord method called when information is available to
1674 answer the question."""
1674 answer the question."""
1675 now = currentTimeMillis()
1675 now = currentTimeMillis()
1676 self.listeners.append(listener)
1676 self.listeners.append(listener)
1677 if question is not None:
1677 if question is not None:
1678 for record in self.cache.entriesWithName(question.name):
1678 for record in self.cache.entriesWithName(question.name):
1679 if question.answeredBy(record) and not record.isExpired(now):
1679 if question.answeredBy(record) and not record.isExpired(now):
1680 listener.updateRecord(self, now, record)
1680 listener.updateRecord(self, now, record)
1681 self.notifyAll()
1681 self.notifyAll()
1682
1682
1683 def removeListener(self, listener):
1683 def removeListener(self, listener):
1684 """Removes a listener."""
1684 """Removes a listener."""
1685 try:
1685 try:
1686 self.listeners.remove(listener)
1686 self.listeners.remove(listener)
1687 self.notifyAll()
1687 self.notifyAll()
1688 except Exception:
1688 except Exception:
1689 pass
1689 pass
1690
1690
1691 def updateRecord(self, now, rec):
1691 def updateRecord(self, now, rec):
1692 """Used to notify listeners of new information that has updated
1692 """Used to notify listeners of new information that has updated
1693 a record."""
1693 a record."""
1694 for listener in self.listeners:
1694 for listener in self.listeners:
1695 listener.updateRecord(self, now, rec)
1695 listener.updateRecord(self, now, rec)
1696 self.notifyAll()
1696 self.notifyAll()
1697
1697
1698 def handleResponse(self, msg):
1698 def handleResponse(self, msg):
1699 """Deal with incoming response packets. All answers
1699 """Deal with incoming response packets. All answers
1700 are held in the cache, and listeners are notified."""
1700 are held in the cache, and listeners are notified."""
1701 now = currentTimeMillis()
1701 now = currentTimeMillis()
1702 for record in msg.answers:
1702 for record in msg.answers:
1703 expired = record.isExpired(now)
1703 expired = record.isExpired(now)
1704 if record in self.cache.entries():
1704 if record in self.cache.entries():
1705 if expired:
1705 if expired:
1706 self.cache.remove(record)
1706 self.cache.remove(record)
1707 else:
1707 else:
1708 entry = self.cache.get(record)
1708 entry = self.cache.get(record)
1709 if entry is not None:
1709 if entry is not None:
1710 entry.resetTTL(record)
1710 entry.resetTTL(record)
1711 record = entry
1711 record = entry
1712 else:
1712 else:
1713 self.cache.add(record)
1713 self.cache.add(record)
1714
1714
1715 self.updateRecord(now, record)
1715 self.updateRecord(now, record)
1716
1716
1717 def handleQuery(self, msg, addr, port):
1717 def handleQuery(self, msg, addr, port):
1718 """Deal with incoming query packets. Provides a response if
1718 """Deal with incoming query packets. Provides a response if
1719 possible."""
1719 possible."""
1720 out = None
1720 out = None
1721
1721
1722 # Support unicast client responses
1722 # Support unicast client responses
1723 #
1723 #
1724 if port != _MDNS_PORT:
1724 if port != _MDNS_PORT:
1725 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0)
1725 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0)
1726 for question in msg.questions:
1726 for question in msg.questions:
1727 out.addQuestion(question)
1727 out.addQuestion(question)
1728
1728
1729 for question in msg.questions:
1729 for question in msg.questions:
1730 if question.type == _TYPE_PTR:
1730 if question.type == _TYPE_PTR:
1731 if question.name == b"_services._dns-sd._udp.local.":
1731 if question.name == b"_services._dns-sd._udp.local.":
1732 for stype in self.servicetypes.keys():
1732 for stype in self.servicetypes.keys():
1733 if out is None:
1733 if out is None:
1734 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1734 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1735 out.addAnswer(
1735 out.addAnswer(
1736 msg,
1736 msg,
1737 DNSPointer(
1737 DNSPointer(
1738 b"_services._dns-sd._udp.local.",
1738 b"_services._dns-sd._udp.local.",
1739 _TYPE_PTR,
1739 _TYPE_PTR,
1740 _CLASS_IN,
1740 _CLASS_IN,
1741 _DNS_TTL,
1741 _DNS_TTL,
1742 stype,
1742 stype,
1743 ),
1743 ),
1744 )
1744 )
1745 for service in self.services.values():
1745 for service in self.services.values():
1746 if question.name == service.type:
1746 if question.name == service.type:
1747 if out is None:
1747 if out is None:
1748 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1748 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1749 out.addAnswer(
1749 out.addAnswer(
1750 msg,
1750 msg,
1751 DNSPointer(
1751 DNSPointer(
1752 service.type,
1752 service.type,
1753 _TYPE_PTR,
1753 _TYPE_PTR,
1754 _CLASS_IN,
1754 _CLASS_IN,
1755 _DNS_TTL,
1755 _DNS_TTL,
1756 service.name,
1756 service.name,
1757 ),
1757 ),
1758 )
1758 )
1759 else:
1759 else:
1760 try:
1760 try:
1761 if out is None:
1761 if out is None:
1762 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1762 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1763
1763
1764 # Answer A record queries for any service addresses we know
1764 # Answer A record queries for any service addresses we know
1765 if question.type == _TYPE_A or question.type == _TYPE_ANY:
1765 if question.type == _TYPE_A or question.type == _TYPE_ANY:
1766 for service in self.services.values():
1766 for service in self.services.values():
1767 if service.server == question.name.lower():
1767 if service.server == question.name.lower():
1768 out.addAnswer(
1768 out.addAnswer(
1769 msg,
1769 msg,
1770 DNSAddress(
1770 DNSAddress(
1771 question.name,
1771 question.name,
1772 _TYPE_A,
1772 _TYPE_A,
1773 _CLASS_IN | _CLASS_UNIQUE,
1773 _CLASS_IN | _CLASS_UNIQUE,
1774 _DNS_TTL,
1774 _DNS_TTL,
1775 service.address,
1775 service.address,
1776 ),
1776 ),
1777 )
1777 )
1778
1778
1779 service = self.services.get(question.name.lower(), None)
1779 service = self.services.get(question.name.lower(), None)
1780 if not service:
1780 if not service:
1781 continue
1781 continue
1782
1782
1783 if question.type == _TYPE_SRV or question.type == _TYPE_ANY:
1783 if question.type == _TYPE_SRV or question.type == _TYPE_ANY:
1784 out.addAnswer(
1784 out.addAnswer(
1785 msg,
1785 msg,
1786 DNSService(
1786 DNSService(
1787 question.name,
1787 question.name,
1788 _TYPE_SRV,
1788 _TYPE_SRV,
1789 _CLASS_IN | _CLASS_UNIQUE,
1789 _CLASS_IN | _CLASS_UNIQUE,
1790 _DNS_TTL,
1790 _DNS_TTL,
1791 service.priority,
1791 service.priority,
1792 service.weight,
1792 service.weight,
1793 service.port,
1793 service.port,
1794 service.server,
1794 service.server,
1795 ),
1795 ),
1796 )
1796 )
1797 if question.type == _TYPE_TXT or question.type == _TYPE_ANY:
1797 if question.type == _TYPE_TXT or question.type == _TYPE_ANY:
1798 out.addAnswer(
1798 out.addAnswer(
1799 msg,
1799 msg,
1800 DNSText(
1800 DNSText(
1801 question.name,
1801 question.name,
1802 _TYPE_TXT,
1802 _TYPE_TXT,
1803 _CLASS_IN | _CLASS_UNIQUE,
1803 _CLASS_IN | _CLASS_UNIQUE,
1804 _DNS_TTL,
1804 _DNS_TTL,
1805 service.text,
1805 service.text,
1806 ),
1806 ),
1807 )
1807 )
1808 if question.type == _TYPE_SRV:
1808 if question.type == _TYPE_SRV:
1809 out.addAdditionalAnswer(
1809 out.addAdditionalAnswer(
1810 DNSAddress(
1810 DNSAddress(
1811 service.server,
1811 service.server,
1812 _TYPE_A,
1812 _TYPE_A,
1813 _CLASS_IN | _CLASS_UNIQUE,
1813 _CLASS_IN | _CLASS_UNIQUE,
1814 _DNS_TTL,
1814 _DNS_TTL,
1815 service.address,
1815 service.address,
1816 )
1816 )
1817 )
1817 )
1818 except Exception:
1818 except Exception:
1819 traceback.print_exc()
1819 traceback.print_exc()
1820
1820
1821 if out is not None and out.answers:
1821 if out is not None and out.answers:
1822 out.id = msg.id
1822 out.id = msg.id
1823 self.send(out, addr, port)
1823 self.send(out, addr, port)
1824
1824
1825 def send(self, out, addr=_MDNS_ADDR, port=_MDNS_PORT):
1825 def send(self, out, addr=_MDNS_ADDR, port=_MDNS_PORT):
1826 """Sends an outgoing packet."""
1826 """Sends an outgoing packet."""
1827 # This is a quick test to see if we can parse the packets we generate
1827 # This is a quick test to see if we can parse the packets we generate
1828 # temp = DNSIncoming(out.packet())
1828 # temp = DNSIncoming(out.packet())
1829 try:
1829 try:
1830 self.socket.sendto(out.packet(), 0, (addr, port))
1830 self.socket.sendto(out.packet(), 0, (addr, port))
1831 except Exception:
1831 except Exception:
1832 # Ignore this, it may be a temporary loss of network connection
1832 # Ignore this, it may be a temporary loss of network connection
1833 pass
1833 pass
1834
1834
1835 def close(self):
1835 def close(self):
1836 """Ends the background threads, and prevent this instance from
1836 """Ends the background threads, and prevent this instance from
1837 servicing further queries."""
1837 servicing further queries."""
1838 if globals()[b'_GLOBAL_DONE'] == 0:
1838 if globals()[b'_GLOBAL_DONE'] == 0:
1839 globals()[b'_GLOBAL_DONE'] = 1
1839 globals()[b'_GLOBAL_DONE'] = 1
1840 self.notifyAll()
1840 self.notifyAll()
1841 self.engine.notify()
1841 self.engine.notify()
1842 self.unregisterAllServices()
1842 self.unregisterAllServices()
1843 self.socket.setsockopt(
1843 self.socket.setsockopt(
1844 socket.SOL_IP,
1844 socket.SOL_IP,
1845 socket.IP_DROP_MEMBERSHIP,
1845 socket.IP_DROP_MEMBERSHIP,
1846 socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'),
1846 socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'),
1847 )
1847 )
1848 self.socket.close()
1848 self.socket.close()
1849
1849
1850
1850
1851 # Test a few module features, including service registration, service
1851 # Test a few module features, including service registration, service
1852 # query (for Zoe), and service unregistration.
1852 # query (for Zoe), and service unregistration.
1853
1853
1854 if __name__ == '__main__':
1854 if __name__ == '__main__':
1855 print(b"Multicast DNS Service Discovery for Python, version", __version__)
1855 print(b"Multicast DNS Service Discovery for Python, version", __version__)
1856 r = Zeroconf()
1856 r = Zeroconf()
1857 print(b"1. Testing registration of a service...")
1857 print(b"1. Testing registration of a service...")
1858 desc = {b'version': b'0.10', b'a': b'test value', b'b': b'another value'}
1858 desc = {b'version': b'0.10', b'a': b'test value', b'b': b'another value'}
1859 info = ServiceInfo(
1859 info = ServiceInfo(
1860 b"_http._tcp.local.",
1860 b"_http._tcp.local.",
1861 b"My Service Name._http._tcp.local.",
1861 b"My Service Name._http._tcp.local.",
1862 socket.inet_aton(b"127.0.0.1"),
1862 socket.inet_aton("127.0.0.1"),
1863 1234,
1863 1234,
1864 0,
1864 0,
1865 0,
1865 0,
1866 desc,
1866 desc,
1867 )
1867 )
1868 print(b" Registering service...")
1868 print(b" Registering service...")
1869 r.registerService(info)
1869 r.registerService(info)
1870 print(b" Registration done.")
1870 print(b" Registration done.")
1871 print(b"2. Testing query of service information...")
1871 print(b"2. Testing query of service information...")
1872 print(
1872 print(
1873 b" Getting ZOE service:",
1873 b" Getting ZOE service:",
1874 str(r.getServiceInfo(b"_http._tcp.local.", b"ZOE._http._tcp.local.")),
1874 str(r.getServiceInfo(b"_http._tcp.local.", b"ZOE._http._tcp.local.")),
1875 )
1875 )
1876 print(b" Query done.")
1876 print(b" Query done.")
1877 print(b"3. Testing query of own service...")
1877 print(b"3. Testing query of own service...")
1878 print(
1878 print(
1879 b" Getting self:",
1879 b" Getting self:",
1880 str(
1880 str(
1881 r.getServiceInfo(
1881 r.getServiceInfo(
1882 b"_http._tcp.local.", b"My Service Name._http._tcp.local."
1882 b"_http._tcp.local.", b"My Service Name._http._tcp.local."
1883 )
1883 )
1884 ),
1884 ),
1885 )
1885 )
1886 print(b" Query done.")
1886 print(b" Query done.")
1887 print(b"4. Testing unregister of service information...")
1887 print(b"4. Testing unregister of service information...")
1888 r.unregisterService(info)
1888 r.unregisterService(info)
1889 print(b" Unregister done.")
1889 print(b" Unregister done.")
1890 r.close()
1890 r.close()
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now