##// END OF EJS Templates
merge with stable
Augie Fackler -
r44232:29adf0a0 merge default
parent child Browse files
Show More
@@ -1,189 +1,190 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,202 +1,203 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
@@ -1,1261 +1,1261 b''
1 # githelp.py - Try to map Git commands to Mercurial equivalents.
1 # githelp.py - Try to map Git commands to Mercurial equivalents.
2 #
2 #
3 # Copyright 2013 Facebook, Inc.
3 # Copyright 2013 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 """try mapping git commands to Mercurial commands
7 """try mapping git commands to Mercurial commands
8
8
9 Tries to map a given git command to a Mercurial command:
9 Tries to map a given git command to a Mercurial command:
10
10
11 $ hg githelp -- git checkout master
11 $ hg githelp -- git checkout master
12 hg update master
12 hg update master
13
13
14 If an unknown command or parameter combination is detected, an error is
14 If an unknown command or parameter combination is detected, an error is
15 produced.
15 produced.
16 """
16 """
17
17
18 from __future__ import absolute_import
18 from __future__ import absolute_import
19
19
20 import getopt
20 import getopt
21 import re
21 import re
22
22
23 from mercurial.i18n import _
23 from mercurial.i18n import _
24 from mercurial import (
24 from mercurial import (
25 encoding,
25 encoding,
26 error,
26 error,
27 fancyopts,
27 fancyopts,
28 pycompat,
28 pycompat,
29 registrar,
29 registrar,
30 scmutil,
30 scmutil,
31 )
31 )
32 from mercurial.utils import procutil
32 from mercurial.utils import procutil
33
33
34 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
34 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
35 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
35 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
36 # be specifying the version(s) of Mercurial they are tested with, or
36 # be specifying the version(s) of Mercurial they are tested with, or
37 # leave the attribute unspecified.
37 # leave the attribute unspecified.
38 testedwith = b'ships-with-hg-core'
38 testedwith = b'ships-with-hg-core'
39
39
40 cmdtable = {}
40 cmdtable = {}
41 command = registrar.command(cmdtable)
41 command = registrar.command(cmdtable)
42
42
43
43
44 def convert(s):
44 def convert(s):
45 if s.startswith(b"origin/"):
45 if s.startswith(b"origin/"):
46 return s[7:]
46 return s[7:]
47 if b'HEAD' in s:
47 if b'HEAD' in s:
48 s = s.replace(b'HEAD', b'.')
48 s = s.replace(b'HEAD', b'.')
49 # HEAD~ in git is .~1 in mercurial
49 # HEAD~ in git is .~1 in mercurial
50 s = re.sub(b'~$', b'~1', s)
50 s = re.sub(b'~$', b'~1', s)
51 return s
51 return s
52
52
53
53
54 @command(
54 @command(
55 b'githelp|git',
55 b'githelp|git',
56 [],
56 [],
57 _(b'hg githelp'),
57 _(b'hg githelp'),
58 helpcategory=command.CATEGORY_HELP,
58 helpcategory=command.CATEGORY_HELP,
59 helpbasic=True,
59 helpbasic=True,
60 )
60 )
61 def githelp(ui, repo, *args, **kwargs):
61 def githelp(ui, repo, *args, **kwargs):
62 '''suggests the Mercurial equivalent of the given git command
62 '''suggests the Mercurial equivalent of the given git command
63
63
64 Usage: hg githelp -- <git command>
64 Usage: hg githelp -- <git command>
65 '''
65 '''
66
66
67 if len(args) == 0 or (len(args) == 1 and args[0] == b'git'):
67 if len(args) == 0 or (len(args) == 1 and args[0] == b'git'):
68 raise error.Abort(
68 raise error.Abort(
69 _(b'missing git command - usage: hg githelp -- <git command>')
69 _(b'missing git command - usage: hg githelp -- <git command>')
70 )
70 )
71
71
72 if args[0] == b'git':
72 if args[0] == b'git':
73 args = args[1:]
73 args = args[1:]
74
74
75 cmd = args[0]
75 cmd = args[0]
76 if not cmd in gitcommands:
76 if not cmd in gitcommands:
77 raise error.Abort(_(b"error: unknown git command %s") % cmd)
77 raise error.Abort(_(b"error: unknown git command %s") % cmd)
78
78
79 ui.pager(b'githelp')
79 ui.pager(b'githelp')
80 args = args[1:]
80 args = args[1:]
81 return gitcommands[cmd](ui, repo, *args, **kwargs)
81 return gitcommands[cmd](ui, repo, *args, **kwargs)
82
82
83
83
84 def parseoptions(ui, cmdoptions, args):
84 def parseoptions(ui, cmdoptions, args):
85 cmdoptions = list(cmdoptions)
85 cmdoptions = list(cmdoptions)
86 opts = {}
86 opts = {}
87 args = list(args)
87 args = list(args)
88 while True:
88 while True:
89 try:
89 try:
90 args = fancyopts.fancyopts(list(args), cmdoptions, opts, True)
90 args = fancyopts.fancyopts(list(args), cmdoptions, opts, True)
91 break
91 break
92 except getopt.GetoptError as ex:
92 except getopt.GetoptError as ex:
93 if "requires argument" in ex.msg:
93 if "requires argument" in ex.msg:
94 raise
94 raise
95 if ('--' + ex.opt) in ex.msg:
95 if ('--' + ex.opt) in ex.msg:
96 flag = b'--' + pycompat.bytestr(ex.opt)
96 flag = b'--' + pycompat.bytestr(ex.opt)
97 elif ('-' + ex.opt) in ex.msg:
97 elif ('-' + ex.opt) in ex.msg:
98 flag = b'-' + pycompat.bytestr(ex.opt)
98 flag = b'-' + pycompat.bytestr(ex.opt)
99 else:
99 else:
100 raise error.Abort(
100 raise error.Abort(
101 _(b"unknown option %s") % pycompat.bytestr(ex.opt)
101 _(b"unknown option %s") % pycompat.bytestr(ex.opt)
102 )
102 )
103 try:
103 try:
104 args.remove(flag)
104 args.remove(flag)
105 except Exception:
105 except Exception:
106 msg = _(b"unknown option '%s' packed with other options")
106 msg = _(b"unknown option '%s' packed with other options")
107 hint = _(b"please try passing the option as its own flag: -%s")
107 hint = _(b"please try passing the option as its own flag: -%s")
108 raise error.Abort(
108 raise error.Abort(
109 msg % pycompat.bytestr(ex.opt),
109 msg % pycompat.bytestr(ex.opt),
110 hint=hint % pycompat.bytestr(ex.opt),
110 hint=hint % pycompat.bytestr(ex.opt),
111 )
111 )
112
112
113 ui.warn(_(b"ignoring unknown option %s\n") % flag)
113 ui.warn(_(b"ignoring unknown option %s\n") % flag)
114
114
115 args = list([convert(x) for x in args])
115 args = list([convert(x) for x in args])
116 opts = dict(
116 opts = dict(
117 [
117 [
118 (k, convert(v)) if isinstance(v, str) else (k, v)
118 (k, convert(v)) if isinstance(v, bytes) else (k, v)
119 for k, v in pycompat.iteritems(opts)
119 for k, v in pycompat.iteritems(opts)
120 ]
120 ]
121 )
121 )
122
122
123 return args, opts
123 return args, opts
124
124
125
125
126 class Command(object):
126 class Command(object):
127 def __init__(self, name):
127 def __init__(self, name):
128 self.name = name
128 self.name = name
129 self.args = []
129 self.args = []
130 self.opts = {}
130 self.opts = {}
131
131
132 def __bytes__(self):
132 def __bytes__(self):
133 cmd = b"hg " + self.name
133 cmd = b"hg " + self.name
134 if self.opts:
134 if self.opts:
135 for k, values in sorted(pycompat.iteritems(self.opts)):
135 for k, values in sorted(pycompat.iteritems(self.opts)):
136 for v in values:
136 for v in values:
137 if v:
137 if v:
138 if isinstance(v, int):
138 if isinstance(v, int):
139 fmt = b' %s %d'
139 fmt = b' %s %d'
140 else:
140 else:
141 fmt = b' %s %s'
141 fmt = b' %s %s'
142
142
143 cmd += fmt % (k, v)
143 cmd += fmt % (k, v)
144 else:
144 else:
145 cmd += b" %s" % (k,)
145 cmd += b" %s" % (k,)
146 if self.args:
146 if self.args:
147 cmd += b" "
147 cmd += b" "
148 cmd += b" ".join(self.args)
148 cmd += b" ".join(self.args)
149 return cmd
149 return cmd
150
150
151 __str__ = encoding.strmethod(__bytes__)
151 __str__ = encoding.strmethod(__bytes__)
152
152
153 def append(self, value):
153 def append(self, value):
154 self.args.append(value)
154 self.args.append(value)
155
155
156 def extend(self, values):
156 def extend(self, values):
157 self.args.extend(values)
157 self.args.extend(values)
158
158
159 def __setitem__(self, key, value):
159 def __setitem__(self, key, value):
160 values = self.opts.setdefault(key, [])
160 values = self.opts.setdefault(key, [])
161 values.append(value)
161 values.append(value)
162
162
163 def __and__(self, other):
163 def __and__(self, other):
164 return AndCommand(self, other)
164 return AndCommand(self, other)
165
165
166
166
167 class AndCommand(object):
167 class AndCommand(object):
168 def __init__(self, left, right):
168 def __init__(self, left, right):
169 self.left = left
169 self.left = left
170 self.right = right
170 self.right = right
171
171
172 def __str__(self):
172 def __str__(self):
173 return b"%s && %s" % (self.left, self.right)
173 return b"%s && %s" % (self.left, self.right)
174
174
175 def __and__(self, other):
175 def __and__(self, other):
176 return AndCommand(self, other)
176 return AndCommand(self, other)
177
177
178
178
179 def add(ui, repo, *args, **kwargs):
179 def add(ui, repo, *args, **kwargs):
180 cmdoptions = [
180 cmdoptions = [
181 (b'A', b'all', None, b''),
181 (b'A', b'all', None, b''),
182 (b'p', b'patch', None, b''),
182 (b'p', b'patch', None, b''),
183 ]
183 ]
184 args, opts = parseoptions(ui, cmdoptions, args)
184 args, opts = parseoptions(ui, cmdoptions, args)
185
185
186 if opts.get(b'patch'):
186 if opts.get(b'patch'):
187 ui.status(
187 ui.status(
188 _(
188 _(
189 b"note: Mercurial will commit when complete, "
189 b"note: Mercurial will commit when complete, "
190 b"as there is no staging area in Mercurial\n\n"
190 b"as there is no staging area in Mercurial\n\n"
191 )
191 )
192 )
192 )
193 cmd = Command(b'commit --interactive')
193 cmd = Command(b'commit --interactive')
194 else:
194 else:
195 cmd = Command(b"add")
195 cmd = Command(b"add")
196
196
197 if not opts.get(b'all'):
197 if not opts.get(b'all'):
198 cmd.extend(args)
198 cmd.extend(args)
199 else:
199 else:
200 ui.status(
200 ui.status(
201 _(
201 _(
202 b"note: use hg addremove to remove files that have "
202 b"note: use hg addremove to remove files that have "
203 b"been deleted\n\n"
203 b"been deleted\n\n"
204 )
204 )
205 )
205 )
206
206
207 ui.status((bytes(cmd)), b"\n")
207 ui.status((bytes(cmd)), b"\n")
208
208
209
209
210 def am(ui, repo, *args, **kwargs):
210 def am(ui, repo, *args, **kwargs):
211 cmdoptions = []
211 cmdoptions = []
212 args, opts = parseoptions(ui, cmdoptions, args)
212 args, opts = parseoptions(ui, cmdoptions, args)
213 cmd = Command(b'import')
213 cmd = Command(b'import')
214 ui.status(bytes(cmd), b"\n")
214 ui.status(bytes(cmd), b"\n")
215
215
216
216
217 def apply(ui, repo, *args, **kwargs):
217 def apply(ui, repo, *args, **kwargs):
218 cmdoptions = [
218 cmdoptions = [
219 (b'p', b'p', int, b''),
219 (b'p', b'p', int, b''),
220 (b'', b'directory', b'', b''),
220 (b'', b'directory', b'', b''),
221 ]
221 ]
222 args, opts = parseoptions(ui, cmdoptions, args)
222 args, opts = parseoptions(ui, cmdoptions, args)
223
223
224 cmd = Command(b'import --no-commit')
224 cmd = Command(b'import --no-commit')
225 if opts.get(b'p'):
225 if opts.get(b'p'):
226 cmd[b'-p'] = opts.get(b'p')
226 cmd[b'-p'] = opts.get(b'p')
227 if opts.get(b'directory'):
227 if opts.get(b'directory'):
228 cmd[b'--prefix'] = opts.get(b'directory')
228 cmd[b'--prefix'] = opts.get(b'directory')
229 cmd.extend(args)
229 cmd.extend(args)
230
230
231 ui.status((bytes(cmd)), b"\n")
231 ui.status((bytes(cmd)), b"\n")
232
232
233
233
234 def bisect(ui, repo, *args, **kwargs):
234 def bisect(ui, repo, *args, **kwargs):
235 ui.status(_(b"see 'hg help bisect' for how to use bisect\n\n"))
235 ui.status(_(b"see 'hg help bisect' for how to use bisect\n\n"))
236
236
237
237
238 def blame(ui, repo, *args, **kwargs):
238 def blame(ui, repo, *args, **kwargs):
239 cmdoptions = []
239 cmdoptions = []
240 args, opts = parseoptions(ui, cmdoptions, args)
240 args, opts = parseoptions(ui, cmdoptions, args)
241 cmd = Command(b'annotate -udl')
241 cmd = Command(b'annotate -udl')
242 cmd.extend([convert(v) for v in args])
242 cmd.extend([convert(v) for v in args])
243 ui.status((bytes(cmd)), b"\n")
243 ui.status((bytes(cmd)), b"\n")
244
244
245
245
246 def branch(ui, repo, *args, **kwargs):
246 def branch(ui, repo, *args, **kwargs):
247 cmdoptions = [
247 cmdoptions = [
248 (b'', b'set-upstream', None, b''),
248 (b'', b'set-upstream', None, b''),
249 (b'', b'set-upstream-to', b'', b''),
249 (b'', b'set-upstream-to', b'', b''),
250 (b'd', b'delete', None, b''),
250 (b'd', b'delete', None, b''),
251 (b'D', b'delete', None, b''),
251 (b'D', b'delete', None, b''),
252 (b'm', b'move', None, b''),
252 (b'm', b'move', None, b''),
253 (b'M', b'move', None, b''),
253 (b'M', b'move', None, b''),
254 ]
254 ]
255 args, opts = parseoptions(ui, cmdoptions, args)
255 args, opts = parseoptions(ui, cmdoptions, args)
256
256
257 cmd = Command(b"bookmark")
257 cmd = Command(b"bookmark")
258
258
259 if opts.get(b'set_upstream') or opts.get(b'set_upstream_to'):
259 if opts.get(b'set_upstream') or opts.get(b'set_upstream_to'):
260 ui.status(_(b"Mercurial has no concept of upstream branches\n"))
260 ui.status(_(b"Mercurial has no concept of upstream branches\n"))
261 return
261 return
262 elif opts.get(b'delete'):
262 elif opts.get(b'delete'):
263 cmd = Command(b"strip")
263 cmd = Command(b"strip")
264 for branch in args:
264 for branch in args:
265 cmd[b'-B'] = branch
265 cmd[b'-B'] = branch
266 else:
266 else:
267 cmd[b'-B'] = None
267 cmd[b'-B'] = None
268 elif opts.get(b'move'):
268 elif opts.get(b'move'):
269 if len(args) > 0:
269 if len(args) > 0:
270 if len(args) > 1:
270 if len(args) > 1:
271 old = args.pop(0)
271 old = args.pop(0)
272 else:
272 else:
273 # shell command to output the active bookmark for the active
273 # shell command to output the active bookmark for the active
274 # revision
274 # revision
275 old = b'`hg log -T"{activebookmark}" -r .`'
275 old = b'`hg log -T"{activebookmark}" -r .`'
276 else:
276 else:
277 raise error.Abort(_(b'missing newbranch argument'))
277 raise error.Abort(_(b'missing newbranch argument'))
278 new = args[0]
278 new = args[0]
279 cmd[b'-m'] = old
279 cmd[b'-m'] = old
280 cmd.append(new)
280 cmd.append(new)
281 else:
281 else:
282 if len(args) > 1:
282 if len(args) > 1:
283 cmd[b'-r'] = args[1]
283 cmd[b'-r'] = args[1]
284 cmd.append(args[0])
284 cmd.append(args[0])
285 elif len(args) == 1:
285 elif len(args) == 1:
286 cmd.append(args[0])
286 cmd.append(args[0])
287 ui.status((bytes(cmd)), b"\n")
287 ui.status((bytes(cmd)), b"\n")
288
288
289
289
290 def ispath(repo, string):
290 def ispath(repo, string):
291 """
291 """
292 The first argument to git checkout can either be a revision or a path. Let's
292 The first argument to git checkout can either be a revision or a path. Let's
293 generally assume it's a revision, unless it's obviously a path. There are
293 generally assume it's a revision, unless it's obviously a path. There are
294 too many ways to spell revisions in git for us to reasonably catch all of
294 too many ways to spell revisions in git for us to reasonably catch all of
295 them, so let's be conservative.
295 them, so let's be conservative.
296 """
296 """
297 if scmutil.isrevsymbol(repo, string):
297 if scmutil.isrevsymbol(repo, string):
298 # if it's definitely a revision let's not even check if a file of the
298 # if it's definitely a revision let's not even check if a file of the
299 # same name exists.
299 # same name exists.
300 return False
300 return False
301
301
302 cwd = repo.getcwd()
302 cwd = repo.getcwd()
303 if cwd == b'':
303 if cwd == b'':
304 repopath = string
304 repopath = string
305 else:
305 else:
306 repopath = cwd + b'/' + string
306 repopath = cwd + b'/' + string
307
307
308 exists = repo.wvfs.exists(repopath)
308 exists = repo.wvfs.exists(repopath)
309 if exists:
309 if exists:
310 return True
310 return True
311
311
312 manifest = repo[b'.'].manifest()
312 manifest = repo[b'.'].manifest()
313
313
314 didexist = (repopath in manifest) or manifest.hasdir(repopath)
314 didexist = (repopath in manifest) or manifest.hasdir(repopath)
315
315
316 return didexist
316 return didexist
317
317
318
318
319 def checkout(ui, repo, *args, **kwargs):
319 def checkout(ui, repo, *args, **kwargs):
320 cmdoptions = [
320 cmdoptions = [
321 (b'b', b'branch', b'', b''),
321 (b'b', b'branch', b'', b''),
322 (b'B', b'branch', b'', b''),
322 (b'B', b'branch', b'', b''),
323 (b'f', b'force', None, b''),
323 (b'f', b'force', None, b''),
324 (b'p', b'patch', None, b''),
324 (b'p', b'patch', None, b''),
325 ]
325 ]
326 paths = []
326 paths = []
327 if b'--' in args:
327 if b'--' in args:
328 sepindex = args.index(b'--')
328 sepindex = args.index(b'--')
329 paths.extend(args[sepindex + 1 :])
329 paths.extend(args[sepindex + 1 :])
330 args = args[:sepindex]
330 args = args[:sepindex]
331
331
332 args, opts = parseoptions(ui, cmdoptions, args)
332 args, opts = parseoptions(ui, cmdoptions, args)
333
333
334 rev = None
334 rev = None
335 if args and ispath(repo, args[0]):
335 if args and ispath(repo, args[0]):
336 paths = args + paths
336 paths = args + paths
337 elif args:
337 elif args:
338 rev = args[0]
338 rev = args[0]
339 paths = args[1:] + paths
339 paths = args[1:] + paths
340
340
341 cmd = Command(b'update')
341 cmd = Command(b'update')
342
342
343 if opts.get(b'force'):
343 if opts.get(b'force'):
344 if paths or rev:
344 if paths or rev:
345 cmd[b'-C'] = None
345 cmd[b'-C'] = None
346
346
347 if opts.get(b'patch'):
347 if opts.get(b'patch'):
348 cmd = Command(b'revert')
348 cmd = Command(b'revert')
349 cmd[b'-i'] = None
349 cmd[b'-i'] = None
350
350
351 if opts.get(b'branch'):
351 if opts.get(b'branch'):
352 if len(args) == 0:
352 if len(args) == 0:
353 cmd = Command(b'bookmark')
353 cmd = Command(b'bookmark')
354 cmd.append(opts.get(b'branch'))
354 cmd.append(opts.get(b'branch'))
355 else:
355 else:
356 cmd.append(args[0])
356 cmd.append(args[0])
357 bookcmd = Command(b'bookmark')
357 bookcmd = Command(b'bookmark')
358 bookcmd.append(opts.get(b'branch'))
358 bookcmd.append(opts.get(b'branch'))
359 cmd = cmd & bookcmd
359 cmd = cmd & bookcmd
360 # if there is any path argument supplied, use revert instead of update
360 # if there is any path argument supplied, use revert instead of update
361 elif len(paths) > 0:
361 elif len(paths) > 0:
362 ui.status(_(b"note: use --no-backup to avoid creating .orig files\n\n"))
362 ui.status(_(b"note: use --no-backup to avoid creating .orig files\n\n"))
363 cmd = Command(b'revert')
363 cmd = Command(b'revert')
364 if opts.get(b'patch'):
364 if opts.get(b'patch'):
365 cmd[b'-i'] = None
365 cmd[b'-i'] = None
366 if rev:
366 if rev:
367 cmd[b'-r'] = rev
367 cmd[b'-r'] = rev
368 cmd.extend(paths)
368 cmd.extend(paths)
369 elif rev:
369 elif rev:
370 if opts.get(b'patch'):
370 if opts.get(b'patch'):
371 cmd[b'-r'] = rev
371 cmd[b'-r'] = rev
372 else:
372 else:
373 cmd.append(rev)
373 cmd.append(rev)
374 elif opts.get(b'force'):
374 elif opts.get(b'force'):
375 cmd = Command(b'revert')
375 cmd = Command(b'revert')
376 cmd[b'--all'] = None
376 cmd[b'--all'] = None
377 else:
377 else:
378 raise error.Abort(_(b"a commit must be specified"))
378 raise error.Abort(_(b"a commit must be specified"))
379
379
380 ui.status((bytes(cmd)), b"\n")
380 ui.status((bytes(cmd)), b"\n")
381
381
382
382
383 def cherrypick(ui, repo, *args, **kwargs):
383 def cherrypick(ui, repo, *args, **kwargs):
384 cmdoptions = [
384 cmdoptions = [
385 (b'', b'continue', None, b''),
385 (b'', b'continue', None, b''),
386 (b'', b'abort', None, b''),
386 (b'', b'abort', None, b''),
387 (b'e', b'edit', None, b''),
387 (b'e', b'edit', None, b''),
388 ]
388 ]
389 args, opts = parseoptions(ui, cmdoptions, args)
389 args, opts = parseoptions(ui, cmdoptions, args)
390
390
391 cmd = Command(b'graft')
391 cmd = Command(b'graft')
392
392
393 if opts.get(b'edit'):
393 if opts.get(b'edit'):
394 cmd[b'--edit'] = None
394 cmd[b'--edit'] = None
395 if opts.get(b'continue'):
395 if opts.get(b'continue'):
396 cmd[b'--continue'] = None
396 cmd[b'--continue'] = None
397 elif opts.get(b'abort'):
397 elif opts.get(b'abort'):
398 ui.status(_(b"note: hg graft does not have --abort\n\n"))
398 ui.status(_(b"note: hg graft does not have --abort\n\n"))
399 return
399 return
400 else:
400 else:
401 cmd.extend(args)
401 cmd.extend(args)
402
402
403 ui.status((bytes(cmd)), b"\n")
403 ui.status((bytes(cmd)), b"\n")
404
404
405
405
406 def clean(ui, repo, *args, **kwargs):
406 def clean(ui, repo, *args, **kwargs):
407 cmdoptions = [
407 cmdoptions = [
408 (b'd', b'd', None, b''),
408 (b'd', b'd', None, b''),
409 (b'f', b'force', None, b''),
409 (b'f', b'force', None, b''),
410 (b'x', b'x', None, b''),
410 (b'x', b'x', None, b''),
411 ]
411 ]
412 args, opts = parseoptions(ui, cmdoptions, args)
412 args, opts = parseoptions(ui, cmdoptions, args)
413
413
414 cmd = Command(b'purge')
414 cmd = Command(b'purge')
415 if opts.get(b'x'):
415 if opts.get(b'x'):
416 cmd[b'--all'] = None
416 cmd[b'--all'] = None
417 cmd.extend(args)
417 cmd.extend(args)
418
418
419 ui.status((bytes(cmd)), b"\n")
419 ui.status((bytes(cmd)), b"\n")
420
420
421
421
422 def clone(ui, repo, *args, **kwargs):
422 def clone(ui, repo, *args, **kwargs):
423 cmdoptions = [
423 cmdoptions = [
424 (b'', b'bare', None, b''),
424 (b'', b'bare', None, b''),
425 (b'n', b'no-checkout', None, b''),
425 (b'n', b'no-checkout', None, b''),
426 (b'b', b'branch', b'', b''),
426 (b'b', b'branch', b'', b''),
427 ]
427 ]
428 args, opts = parseoptions(ui, cmdoptions, args)
428 args, opts = parseoptions(ui, cmdoptions, args)
429
429
430 if len(args) == 0:
430 if len(args) == 0:
431 raise error.Abort(_(b"a repository to clone must be specified"))
431 raise error.Abort(_(b"a repository to clone must be specified"))
432
432
433 cmd = Command(b'clone')
433 cmd = Command(b'clone')
434 cmd.append(args[0])
434 cmd.append(args[0])
435 if len(args) > 1:
435 if len(args) > 1:
436 cmd.append(args[1])
436 cmd.append(args[1])
437
437
438 if opts.get(b'bare'):
438 if opts.get(b'bare'):
439 cmd[b'-U'] = None
439 cmd[b'-U'] = None
440 ui.status(
440 ui.status(
441 _(
441 _(
442 b"note: Mercurial does not have bare clones. "
442 b"note: Mercurial does not have bare clones. "
443 b"-U will clone the repo without checking out a commit\n\n"
443 b"-U will clone the repo without checking out a commit\n\n"
444 )
444 )
445 )
445 )
446 elif opts.get(b'no_checkout'):
446 elif opts.get(b'no_checkout'):
447 cmd[b'-U'] = None
447 cmd[b'-U'] = None
448
448
449 if opts.get(b'branch'):
449 if opts.get(b'branch'):
450 cocmd = Command(b"update")
450 cocmd = Command(b"update")
451 cocmd.append(opts.get(b'branch'))
451 cocmd.append(opts.get(b'branch'))
452 cmd = cmd & cocmd
452 cmd = cmd & cocmd
453
453
454 ui.status((bytes(cmd)), b"\n")
454 ui.status((bytes(cmd)), b"\n")
455
455
456
456
457 def commit(ui, repo, *args, **kwargs):
457 def commit(ui, repo, *args, **kwargs):
458 cmdoptions = [
458 cmdoptions = [
459 (b'a', b'all', None, b''),
459 (b'a', b'all', None, b''),
460 (b'm', b'message', b'', b''),
460 (b'm', b'message', b'', b''),
461 (b'p', b'patch', None, b''),
461 (b'p', b'patch', None, b''),
462 (b'C', b'reuse-message', b'', b''),
462 (b'C', b'reuse-message', b'', b''),
463 (b'F', b'file', b'', b''),
463 (b'F', b'file', b'', b''),
464 (b'', b'author', b'', b''),
464 (b'', b'author', b'', b''),
465 (b'', b'date', b'', b''),
465 (b'', b'date', b'', b''),
466 (b'', b'amend', None, b''),
466 (b'', b'amend', None, b''),
467 (b'', b'no-edit', None, b''),
467 (b'', b'no-edit', None, b''),
468 ]
468 ]
469 args, opts = parseoptions(ui, cmdoptions, args)
469 args, opts = parseoptions(ui, cmdoptions, args)
470
470
471 cmd = Command(b'commit')
471 cmd = Command(b'commit')
472 if opts.get(b'patch'):
472 if opts.get(b'patch'):
473 cmd = Command(b'commit --interactive')
473 cmd = Command(b'commit --interactive')
474
474
475 if opts.get(b'amend'):
475 if opts.get(b'amend'):
476 if opts.get(b'no_edit'):
476 if opts.get(b'no_edit'):
477 cmd = Command(b'amend')
477 cmd = Command(b'amend')
478 else:
478 else:
479 cmd[b'--amend'] = None
479 cmd[b'--amend'] = None
480
480
481 if opts.get(b'reuse_message'):
481 if opts.get(b'reuse_message'):
482 cmd[b'-M'] = opts.get(b'reuse_message')
482 cmd[b'-M'] = opts.get(b'reuse_message')
483
483
484 if opts.get(b'message'):
484 if opts.get(b'message'):
485 cmd[b'-m'] = b"'%s'" % (opts.get(b'message'),)
485 cmd[b'-m'] = b"'%s'" % (opts.get(b'message'),)
486
486
487 if opts.get(b'all'):
487 if opts.get(b'all'):
488 ui.status(
488 ui.status(
489 _(
489 _(
490 b"note: Mercurial doesn't have a staging area, "
490 b"note: Mercurial doesn't have a staging area, "
491 b"so there is no --all. -A will add and remove files "
491 b"so there is no --all. -A will add and remove files "
492 b"for you though.\n\n"
492 b"for you though.\n\n"
493 )
493 )
494 )
494 )
495
495
496 if opts.get(b'file'):
496 if opts.get(b'file'):
497 cmd[b'-l'] = opts.get(b'file')
497 cmd[b'-l'] = opts.get(b'file')
498
498
499 if opts.get(b'author'):
499 if opts.get(b'author'):
500 cmd[b'-u'] = opts.get(b'author')
500 cmd[b'-u'] = opts.get(b'author')
501
501
502 if opts.get(b'date'):
502 if opts.get(b'date'):
503 cmd[b'-d'] = opts.get(b'date')
503 cmd[b'-d'] = opts.get(b'date')
504
504
505 cmd.extend(args)
505 cmd.extend(args)
506
506
507 ui.status((bytes(cmd)), b"\n")
507 ui.status((bytes(cmd)), b"\n")
508
508
509
509
510 def deprecated(ui, repo, *args, **kwargs):
510 def deprecated(ui, repo, *args, **kwargs):
511 ui.warn(
511 ui.warn(
512 _(
512 _(
513 b'this command has been deprecated in the git project, '
513 b'this command has been deprecated in the git project, '
514 b'thus isn\'t supported by this tool\n\n'
514 b'thus isn\'t supported by this tool\n\n'
515 )
515 )
516 )
516 )
517
517
518
518
519 def diff(ui, repo, *args, **kwargs):
519 def diff(ui, repo, *args, **kwargs):
520 cmdoptions = [
520 cmdoptions = [
521 (b'a', b'all', None, b''),
521 (b'a', b'all', None, b''),
522 (b'', b'cached', None, b''),
522 (b'', b'cached', None, b''),
523 (b'R', b'reverse', None, b''),
523 (b'R', b'reverse', None, b''),
524 ]
524 ]
525 args, opts = parseoptions(ui, cmdoptions, args)
525 args, opts = parseoptions(ui, cmdoptions, args)
526
526
527 cmd = Command(b'diff')
527 cmd = Command(b'diff')
528
528
529 if opts.get(b'cached'):
529 if opts.get(b'cached'):
530 ui.status(
530 ui.status(
531 _(
531 _(
532 b'note: Mercurial has no concept of a staging area, '
532 b'note: Mercurial has no concept of a staging area, '
533 b'so --cached does nothing\n\n'
533 b'so --cached does nothing\n\n'
534 )
534 )
535 )
535 )
536
536
537 if opts.get(b'reverse'):
537 if opts.get(b'reverse'):
538 cmd[b'--reverse'] = None
538 cmd[b'--reverse'] = None
539
539
540 for a in list(args):
540 for a in list(args):
541 args.remove(a)
541 args.remove(a)
542 try:
542 try:
543 repo.revs(a)
543 repo.revs(a)
544 cmd[b'-r'] = a
544 cmd[b'-r'] = a
545 except Exception:
545 except Exception:
546 cmd.append(a)
546 cmd.append(a)
547
547
548 ui.status((bytes(cmd)), b"\n")
548 ui.status((bytes(cmd)), b"\n")
549
549
550
550
551 def difftool(ui, repo, *args, **kwargs):
551 def difftool(ui, repo, *args, **kwargs):
552 ui.status(
552 ui.status(
553 _(
553 _(
554 b'Mercurial does not enable external difftool by default. You '
554 b'Mercurial does not enable external difftool by default. You '
555 b'need to enable the extdiff extension in your .hgrc file by adding\n'
555 b'need to enable the extdiff extension in your .hgrc file by adding\n'
556 b'extdiff =\n'
556 b'extdiff =\n'
557 b'to the [extensions] section and then running\n\n'
557 b'to the [extensions] section and then running\n\n'
558 b'hg extdiff -p <program>\n\n'
558 b'hg extdiff -p <program>\n\n'
559 b'See \'hg help extdiff\' and \'hg help -e extdiff\' for more '
559 b'See \'hg help extdiff\' and \'hg help -e extdiff\' for more '
560 b'information.\n'
560 b'information.\n'
561 )
561 )
562 )
562 )
563
563
564
564
565 def fetch(ui, repo, *args, **kwargs):
565 def fetch(ui, repo, *args, **kwargs):
566 cmdoptions = [
566 cmdoptions = [
567 (b'', b'all', None, b''),
567 (b'', b'all', None, b''),
568 (b'f', b'force', None, b''),
568 (b'f', b'force', None, b''),
569 ]
569 ]
570 args, opts = parseoptions(ui, cmdoptions, args)
570 args, opts = parseoptions(ui, cmdoptions, args)
571
571
572 cmd = Command(b'pull')
572 cmd = Command(b'pull')
573
573
574 if len(args) > 0:
574 if len(args) > 0:
575 cmd.append(args[0])
575 cmd.append(args[0])
576 if len(args) > 1:
576 if len(args) > 1:
577 ui.status(
577 ui.status(
578 _(
578 _(
579 b"note: Mercurial doesn't have refspecs. "
579 b"note: Mercurial doesn't have refspecs. "
580 b"-r can be used to specify which commits you want to "
580 b"-r can be used to specify which commits you want to "
581 b"pull. -B can be used to specify which bookmark you "
581 b"pull. -B can be used to specify which bookmark you "
582 b"want to pull.\n\n"
582 b"want to pull.\n\n"
583 )
583 )
584 )
584 )
585 for v in args[1:]:
585 for v in args[1:]:
586 if v in repo._bookmarks:
586 if v in repo._bookmarks:
587 cmd[b'-B'] = v
587 cmd[b'-B'] = v
588 else:
588 else:
589 cmd[b'-r'] = v
589 cmd[b'-r'] = v
590
590
591 ui.status((bytes(cmd)), b"\n")
591 ui.status((bytes(cmd)), b"\n")
592
592
593
593
594 def grep(ui, repo, *args, **kwargs):
594 def grep(ui, repo, *args, **kwargs):
595 cmdoptions = []
595 cmdoptions = []
596 args, opts = parseoptions(ui, cmdoptions, args)
596 args, opts = parseoptions(ui, cmdoptions, args)
597
597
598 cmd = Command(b'grep')
598 cmd = Command(b'grep')
599
599
600 # For basic usage, git grep and hg grep are the same. They both have the
600 # For basic usage, git grep and hg grep are the same. They both have the
601 # pattern first, followed by paths.
601 # pattern first, followed by paths.
602 cmd.extend(args)
602 cmd.extend(args)
603
603
604 ui.status((bytes(cmd)), b"\n")
604 ui.status((bytes(cmd)), b"\n")
605
605
606
606
607 def init(ui, repo, *args, **kwargs):
607 def init(ui, repo, *args, **kwargs):
608 cmdoptions = []
608 cmdoptions = []
609 args, opts = parseoptions(ui, cmdoptions, args)
609 args, opts = parseoptions(ui, cmdoptions, args)
610
610
611 cmd = Command(b'init')
611 cmd = Command(b'init')
612
612
613 if len(args) > 0:
613 if len(args) > 0:
614 cmd.append(args[0])
614 cmd.append(args[0])
615
615
616 ui.status((bytes(cmd)), b"\n")
616 ui.status((bytes(cmd)), b"\n")
617
617
618
618
619 def log(ui, repo, *args, **kwargs):
619 def log(ui, repo, *args, **kwargs):
620 cmdoptions = [
620 cmdoptions = [
621 (b'', b'follow', None, b''),
621 (b'', b'follow', None, b''),
622 (b'', b'decorate', None, b''),
622 (b'', b'decorate', None, b''),
623 (b'n', b'number', b'', b''),
623 (b'n', b'number', b'', b''),
624 (b'1', b'1', None, b''),
624 (b'1', b'1', None, b''),
625 (b'', b'pretty', b'', b''),
625 (b'', b'pretty', b'', b''),
626 (b'', b'format', b'', b''),
626 (b'', b'format', b'', b''),
627 (b'', b'oneline', None, b''),
627 (b'', b'oneline', None, b''),
628 (b'', b'stat', None, b''),
628 (b'', b'stat', None, b''),
629 (b'', b'graph', None, b''),
629 (b'', b'graph', None, b''),
630 (b'p', b'patch', None, b''),
630 (b'p', b'patch', None, b''),
631 ]
631 ]
632 args, opts = parseoptions(ui, cmdoptions, args)
632 args, opts = parseoptions(ui, cmdoptions, args)
633 ui.status(
633 ui.status(
634 _(
634 _(
635 b'note: -v prints the entire commit message like Git does. To '
635 b'note: -v prints the entire commit message like Git does. To '
636 b'print just the first line, drop the -v.\n\n'
636 b'print just the first line, drop the -v.\n\n'
637 )
637 )
638 )
638 )
639 ui.status(
639 ui.status(
640 _(
640 _(
641 b"note: see hg help revset for information on how to filter "
641 b"note: see hg help revset for information on how to filter "
642 b"log output\n\n"
642 b"log output\n\n"
643 )
643 )
644 )
644 )
645
645
646 cmd = Command(b'log')
646 cmd = Command(b'log')
647 cmd[b'-v'] = None
647 cmd[b'-v'] = None
648
648
649 if opts.get(b'number'):
649 if opts.get(b'number'):
650 cmd[b'-l'] = opts.get(b'number')
650 cmd[b'-l'] = opts.get(b'number')
651 if opts.get(b'1'):
651 if opts.get(b'1'):
652 cmd[b'-l'] = b'1'
652 cmd[b'-l'] = b'1'
653 if opts.get(b'stat'):
653 if opts.get(b'stat'):
654 cmd[b'--stat'] = None
654 cmd[b'--stat'] = None
655 if opts.get(b'graph'):
655 if opts.get(b'graph'):
656 cmd[b'-G'] = None
656 cmd[b'-G'] = None
657 if opts.get(b'patch'):
657 if opts.get(b'patch'):
658 cmd[b'-p'] = None
658 cmd[b'-p'] = None
659
659
660 if opts.get(b'pretty') or opts.get(b'format') or opts.get(b'oneline'):
660 if opts.get(b'pretty') or opts.get(b'format') or opts.get(b'oneline'):
661 format = opts.get(b'format', b'')
661 format = opts.get(b'format', b'')
662 if b'format:' in format:
662 if b'format:' in format:
663 ui.status(
663 ui.status(
664 _(
664 _(
665 b"note: --format format:??? equates to Mercurial's "
665 b"note: --format format:??? equates to Mercurial's "
666 b"--template. See hg help templates for more info.\n\n"
666 b"--template. See hg help templates for more info.\n\n"
667 )
667 )
668 )
668 )
669 cmd[b'--template'] = b'???'
669 cmd[b'--template'] = b'???'
670 else:
670 else:
671 ui.status(
671 ui.status(
672 _(
672 _(
673 b"note: --pretty/format/oneline equate to Mercurial's "
673 b"note: --pretty/format/oneline equate to Mercurial's "
674 b"--style or --template. See hg help templates for "
674 b"--style or --template. See hg help templates for "
675 b"more info.\n\n"
675 b"more info.\n\n"
676 )
676 )
677 )
677 )
678 cmd[b'--style'] = b'???'
678 cmd[b'--style'] = b'???'
679
679
680 if len(args) > 0:
680 if len(args) > 0:
681 if b'..' in args[0]:
681 if b'..' in args[0]:
682 since, until = args[0].split(b'..')
682 since, until = args[0].split(b'..')
683 cmd[b'-r'] = b"'%s::%s'" % (since, until)
683 cmd[b'-r'] = b"'%s::%s'" % (since, until)
684 del args[0]
684 del args[0]
685 cmd.extend(args)
685 cmd.extend(args)
686
686
687 ui.status((bytes(cmd)), b"\n")
687 ui.status((bytes(cmd)), b"\n")
688
688
689
689
690 def lsfiles(ui, repo, *args, **kwargs):
690 def lsfiles(ui, repo, *args, **kwargs):
691 cmdoptions = [
691 cmdoptions = [
692 (b'c', b'cached', None, b''),
692 (b'c', b'cached', None, b''),
693 (b'd', b'deleted', None, b''),
693 (b'd', b'deleted', None, b''),
694 (b'm', b'modified', None, b''),
694 (b'm', b'modified', None, b''),
695 (b'o', b'others', None, b''),
695 (b'o', b'others', None, b''),
696 (b'i', b'ignored', None, b''),
696 (b'i', b'ignored', None, b''),
697 (b's', b'stage', None, b''),
697 (b's', b'stage', None, b''),
698 (b'z', b'_zero', None, b''),
698 (b'z', b'_zero', None, b''),
699 ]
699 ]
700 args, opts = parseoptions(ui, cmdoptions, args)
700 args, opts = parseoptions(ui, cmdoptions, args)
701
701
702 if (
702 if (
703 opts.get(b'modified')
703 opts.get(b'modified')
704 or opts.get(b'deleted')
704 or opts.get(b'deleted')
705 or opts.get(b'others')
705 or opts.get(b'others')
706 or opts.get(b'ignored')
706 or opts.get(b'ignored')
707 ):
707 ):
708 cmd = Command(b'status')
708 cmd = Command(b'status')
709 if opts.get(b'deleted'):
709 if opts.get(b'deleted'):
710 cmd[b'-d'] = None
710 cmd[b'-d'] = None
711 if opts.get(b'modified'):
711 if opts.get(b'modified'):
712 cmd[b'-m'] = None
712 cmd[b'-m'] = None
713 if opts.get(b'others'):
713 if opts.get(b'others'):
714 cmd[b'-o'] = None
714 cmd[b'-o'] = None
715 if opts.get(b'ignored'):
715 if opts.get(b'ignored'):
716 cmd[b'-i'] = None
716 cmd[b'-i'] = None
717 else:
717 else:
718 cmd = Command(b'files')
718 cmd = Command(b'files')
719 if opts.get(b'stage'):
719 if opts.get(b'stage'):
720 ui.status(
720 ui.status(
721 _(
721 _(
722 b"note: Mercurial doesn't have a staging area, ignoring "
722 b"note: Mercurial doesn't have a staging area, ignoring "
723 b"--stage\n"
723 b"--stage\n"
724 )
724 )
725 )
725 )
726 if opts.get(b'_zero'):
726 if opts.get(b'_zero'):
727 cmd[b'-0'] = None
727 cmd[b'-0'] = None
728 cmd.append(b'.')
728 cmd.append(b'.')
729 for include in args:
729 for include in args:
730 cmd[b'-I'] = procutil.shellquote(include)
730 cmd[b'-I'] = procutil.shellquote(include)
731
731
732 ui.status((bytes(cmd)), b"\n")
732 ui.status((bytes(cmd)), b"\n")
733
733
734
734
735 def merge(ui, repo, *args, **kwargs):
735 def merge(ui, repo, *args, **kwargs):
736 cmdoptions = []
736 cmdoptions = []
737 args, opts = parseoptions(ui, cmdoptions, args)
737 args, opts = parseoptions(ui, cmdoptions, args)
738
738
739 cmd = Command(b'merge')
739 cmd = Command(b'merge')
740
740
741 if len(args) > 0:
741 if len(args) > 0:
742 cmd.append(args[len(args) - 1])
742 cmd.append(args[len(args) - 1])
743
743
744 ui.status((bytes(cmd)), b"\n")
744 ui.status((bytes(cmd)), b"\n")
745
745
746
746
747 def mergebase(ui, repo, *args, **kwargs):
747 def mergebase(ui, repo, *args, **kwargs):
748 cmdoptions = []
748 cmdoptions = []
749 args, opts = parseoptions(ui, cmdoptions, args)
749 args, opts = parseoptions(ui, cmdoptions, args)
750
750
751 if len(args) != 2:
751 if len(args) != 2:
752 args = [b'A', b'B']
752 args = [b'A', b'B']
753
753
754 cmd = Command(
754 cmd = Command(
755 b"log -T '{node}\\n' -r 'ancestor(%s,%s)'" % (args[0], args[1])
755 b"log -T '{node}\\n' -r 'ancestor(%s,%s)'" % (args[0], args[1])
756 )
756 )
757
757
758 ui.status(
758 ui.status(
759 _(b'note: ancestors() is part of the revset language\n'),
759 _(b'note: ancestors() is part of the revset language\n'),
760 _(b"(learn more about revsets with 'hg help revsets')\n\n"),
760 _(b"(learn more about revsets with 'hg help revsets')\n\n"),
761 )
761 )
762 ui.status((bytes(cmd)), b"\n")
762 ui.status((bytes(cmd)), b"\n")
763
763
764
764
765 def mergetool(ui, repo, *args, **kwargs):
765 def mergetool(ui, repo, *args, **kwargs):
766 cmdoptions = []
766 cmdoptions = []
767 args, opts = parseoptions(ui, cmdoptions, args)
767 args, opts = parseoptions(ui, cmdoptions, args)
768
768
769 cmd = Command(b"resolve")
769 cmd = Command(b"resolve")
770
770
771 if len(args) == 0:
771 if len(args) == 0:
772 cmd[b'--all'] = None
772 cmd[b'--all'] = None
773 cmd.extend(args)
773 cmd.extend(args)
774 ui.status((bytes(cmd)), b"\n")
774 ui.status((bytes(cmd)), b"\n")
775
775
776
776
777 def mv(ui, repo, *args, **kwargs):
777 def mv(ui, repo, *args, **kwargs):
778 cmdoptions = [
778 cmdoptions = [
779 (b'f', b'force', None, b''),
779 (b'f', b'force', None, b''),
780 (b'n', b'dry-run', None, b''),
780 (b'n', b'dry-run', None, b''),
781 ]
781 ]
782 args, opts = parseoptions(ui, cmdoptions, args)
782 args, opts = parseoptions(ui, cmdoptions, args)
783
783
784 cmd = Command(b'mv')
784 cmd = Command(b'mv')
785 cmd.extend(args)
785 cmd.extend(args)
786
786
787 if opts.get(b'force'):
787 if opts.get(b'force'):
788 cmd[b'-f'] = None
788 cmd[b'-f'] = None
789 if opts.get(b'dry_run'):
789 if opts.get(b'dry_run'):
790 cmd[b'-n'] = None
790 cmd[b'-n'] = None
791
791
792 ui.status((bytes(cmd)), b"\n")
792 ui.status((bytes(cmd)), b"\n")
793
793
794
794
795 def pull(ui, repo, *args, **kwargs):
795 def pull(ui, repo, *args, **kwargs):
796 cmdoptions = [
796 cmdoptions = [
797 (b'', b'all', None, b''),
797 (b'', b'all', None, b''),
798 (b'f', b'force', None, b''),
798 (b'f', b'force', None, b''),
799 (b'r', b'rebase', None, b''),
799 (b'r', b'rebase', None, b''),
800 ]
800 ]
801 args, opts = parseoptions(ui, cmdoptions, args)
801 args, opts = parseoptions(ui, cmdoptions, args)
802
802
803 cmd = Command(b'pull')
803 cmd = Command(b'pull')
804 cmd[b'--rebase'] = None
804 cmd[b'--rebase'] = None
805
805
806 if len(args) > 0:
806 if len(args) > 0:
807 cmd.append(args[0])
807 cmd.append(args[0])
808 if len(args) > 1:
808 if len(args) > 1:
809 ui.status(
809 ui.status(
810 _(
810 _(
811 b"note: Mercurial doesn't have refspecs. "
811 b"note: Mercurial doesn't have refspecs. "
812 b"-r can be used to specify which commits you want to "
812 b"-r can be used to specify which commits you want to "
813 b"pull. -B can be used to specify which bookmark you "
813 b"pull. -B can be used to specify which bookmark you "
814 b"want to pull.\n\n"
814 b"want to pull.\n\n"
815 )
815 )
816 )
816 )
817 for v in args[1:]:
817 for v in args[1:]:
818 if v in repo._bookmarks:
818 if v in repo._bookmarks:
819 cmd[b'-B'] = v
819 cmd[b'-B'] = v
820 else:
820 else:
821 cmd[b'-r'] = v
821 cmd[b'-r'] = v
822
822
823 ui.status((bytes(cmd)), b"\n")
823 ui.status((bytes(cmd)), b"\n")
824
824
825
825
826 def push(ui, repo, *args, **kwargs):
826 def push(ui, repo, *args, **kwargs):
827 cmdoptions = [
827 cmdoptions = [
828 (b'', b'all', None, b''),
828 (b'', b'all', None, b''),
829 (b'f', b'force', None, b''),
829 (b'f', b'force', None, b''),
830 ]
830 ]
831 args, opts = parseoptions(ui, cmdoptions, args)
831 args, opts = parseoptions(ui, cmdoptions, args)
832
832
833 cmd = Command(b'push')
833 cmd = Command(b'push')
834
834
835 if len(args) > 0:
835 if len(args) > 0:
836 cmd.append(args[0])
836 cmd.append(args[0])
837 if len(args) > 1:
837 if len(args) > 1:
838 ui.status(
838 ui.status(
839 _(
839 _(
840 b"note: Mercurial doesn't have refspecs. "
840 b"note: Mercurial doesn't have refspecs. "
841 b"-r can be used to specify which commits you want "
841 b"-r can be used to specify which commits you want "
842 b"to push. -B can be used to specify which bookmark "
842 b"to push. -B can be used to specify which bookmark "
843 b"you want to push.\n\n"
843 b"you want to push.\n\n"
844 )
844 )
845 )
845 )
846 for v in args[1:]:
846 for v in args[1:]:
847 if v in repo._bookmarks:
847 if v in repo._bookmarks:
848 cmd[b'-B'] = v
848 cmd[b'-B'] = v
849 else:
849 else:
850 cmd[b'-r'] = v
850 cmd[b'-r'] = v
851
851
852 if opts.get(b'force'):
852 if opts.get(b'force'):
853 cmd[b'-f'] = None
853 cmd[b'-f'] = None
854
854
855 ui.status((bytes(cmd)), b"\n")
855 ui.status((bytes(cmd)), b"\n")
856
856
857
857
858 def rebase(ui, repo, *args, **kwargs):
858 def rebase(ui, repo, *args, **kwargs):
859 cmdoptions = [
859 cmdoptions = [
860 (b'', b'all', None, b''),
860 (b'', b'all', None, b''),
861 (b'i', b'interactive', None, b''),
861 (b'i', b'interactive', None, b''),
862 (b'', b'onto', b'', b''),
862 (b'', b'onto', b'', b''),
863 (b'', b'abort', None, b''),
863 (b'', b'abort', None, b''),
864 (b'', b'continue', None, b''),
864 (b'', b'continue', None, b''),
865 (b'', b'skip', None, b''),
865 (b'', b'skip', None, b''),
866 ]
866 ]
867 args, opts = parseoptions(ui, cmdoptions, args)
867 args, opts = parseoptions(ui, cmdoptions, args)
868
868
869 if opts.get(b'interactive'):
869 if opts.get(b'interactive'):
870 ui.status(
870 ui.status(
871 _(
871 _(
872 b"note: hg histedit does not perform a rebase. "
872 b"note: hg histedit does not perform a rebase. "
873 b"It just edits history.\n\n"
873 b"It just edits history.\n\n"
874 )
874 )
875 )
875 )
876 cmd = Command(b'histedit')
876 cmd = Command(b'histedit')
877 if len(args) > 0:
877 if len(args) > 0:
878 ui.status(
878 ui.status(
879 _(
879 _(
880 b"also note: 'hg histedit' will automatically detect"
880 b"also note: 'hg histedit' will automatically detect"
881 b" your stack, so no second argument is necessary\n\n"
881 b" your stack, so no second argument is necessary\n\n"
882 )
882 )
883 )
883 )
884 ui.status((bytes(cmd)), b"\n")
884 ui.status((bytes(cmd)), b"\n")
885 return
885 return
886
886
887 if opts.get(b'skip'):
887 if opts.get(b'skip'):
888 cmd = Command(b'revert --all -r .')
888 cmd = Command(b'revert --all -r .')
889 ui.status((bytes(cmd)), b"\n")
889 ui.status((bytes(cmd)), b"\n")
890
890
891 cmd = Command(b'rebase')
891 cmd = Command(b'rebase')
892
892
893 if opts.get(b'continue') or opts.get(b'skip'):
893 if opts.get(b'continue') or opts.get(b'skip'):
894 cmd[b'--continue'] = None
894 cmd[b'--continue'] = None
895 if opts.get(b'abort'):
895 if opts.get(b'abort'):
896 cmd[b'--abort'] = None
896 cmd[b'--abort'] = None
897
897
898 if opts.get(b'onto'):
898 if opts.get(b'onto'):
899 ui.status(
899 ui.status(
900 _(
900 _(
901 b"note: if you're trying to lift a commit off one branch, "
901 b"note: if you're trying to lift a commit off one branch, "
902 b"try hg rebase -d <destination commit> -s <commit to be "
902 b"try hg rebase -d <destination commit> -s <commit to be "
903 b"lifted>\n\n"
903 b"lifted>\n\n"
904 )
904 )
905 )
905 )
906 cmd[b'-d'] = convert(opts.get(b'onto'))
906 cmd[b'-d'] = convert(opts.get(b'onto'))
907 if len(args) < 2:
907 if len(args) < 2:
908 raise error.Abort(_(b"expected format: git rebase --onto X Y Z"))
908 raise error.Abort(_(b"expected format: git rebase --onto X Y Z"))
909 cmd[b'-s'] = b"'::%s - ::%s'" % (convert(args[1]), convert(args[0]))
909 cmd[b'-s'] = b"'::%s - ::%s'" % (convert(args[1]), convert(args[0]))
910 else:
910 else:
911 if len(args) == 1:
911 if len(args) == 1:
912 cmd[b'-d'] = convert(args[0])
912 cmd[b'-d'] = convert(args[0])
913 elif len(args) == 2:
913 elif len(args) == 2:
914 cmd[b'-d'] = convert(args[0])
914 cmd[b'-d'] = convert(args[0])
915 cmd[b'-b'] = convert(args[1])
915 cmd[b'-b'] = convert(args[1])
916
916
917 ui.status((bytes(cmd)), b"\n")
917 ui.status((bytes(cmd)), b"\n")
918
918
919
919
920 def reflog(ui, repo, *args, **kwargs):
920 def reflog(ui, repo, *args, **kwargs):
921 cmdoptions = [
921 cmdoptions = [
922 (b'', b'all', None, b''),
922 (b'', b'all', None, b''),
923 ]
923 ]
924 args, opts = parseoptions(ui, cmdoptions, args)
924 args, opts = parseoptions(ui, cmdoptions, args)
925
925
926 cmd = Command(b'journal')
926 cmd = Command(b'journal')
927 if opts.get(b'all'):
927 if opts.get(b'all'):
928 cmd[b'--all'] = None
928 cmd[b'--all'] = None
929 if len(args) > 0:
929 if len(args) > 0:
930 cmd.append(args[0])
930 cmd.append(args[0])
931
931
932 ui.status(bytes(cmd), b"\n\n")
932 ui.status(bytes(cmd), b"\n\n")
933 ui.status(
933 ui.status(
934 _(
934 _(
935 b"note: in hg commits can be deleted from repo but we always"
935 b"note: in hg commits can be deleted from repo but we always"
936 b" have backups\n"
936 b" have backups\n"
937 )
937 )
938 )
938 )
939
939
940
940
941 def reset(ui, repo, *args, **kwargs):
941 def reset(ui, repo, *args, **kwargs):
942 cmdoptions = [
942 cmdoptions = [
943 (b'', b'soft', None, b''),
943 (b'', b'soft', None, b''),
944 (b'', b'hard', None, b''),
944 (b'', b'hard', None, b''),
945 (b'', b'mixed', None, b''),
945 (b'', b'mixed', None, b''),
946 ]
946 ]
947 args, opts = parseoptions(ui, cmdoptions, args)
947 args, opts = parseoptions(ui, cmdoptions, args)
948
948
949 commit = convert(args[0] if len(args) > 0 else b'.')
949 commit = convert(args[0] if len(args) > 0 else b'.')
950 hard = opts.get(b'hard')
950 hard = opts.get(b'hard')
951
951
952 if opts.get(b'mixed'):
952 if opts.get(b'mixed'):
953 ui.status(
953 ui.status(
954 _(
954 _(
955 b'note: --mixed has no meaning since Mercurial has no '
955 b'note: --mixed has no meaning since Mercurial has no '
956 b'staging area\n\n'
956 b'staging area\n\n'
957 )
957 )
958 )
958 )
959 if opts.get(b'soft'):
959 if opts.get(b'soft'):
960 ui.status(
960 ui.status(
961 _(
961 _(
962 b'note: --soft has no meaning since Mercurial has no '
962 b'note: --soft has no meaning since Mercurial has no '
963 b'staging area\n\n'
963 b'staging area\n\n'
964 )
964 )
965 )
965 )
966
966
967 cmd = Command(b'update')
967 cmd = Command(b'update')
968 if hard:
968 if hard:
969 cmd.append(b'--clean')
969 cmd.append(b'--clean')
970
970
971 cmd.append(commit)
971 cmd.append(commit)
972
972
973 ui.status((bytes(cmd)), b"\n")
973 ui.status((bytes(cmd)), b"\n")
974
974
975
975
976 def revert(ui, repo, *args, **kwargs):
976 def revert(ui, repo, *args, **kwargs):
977 cmdoptions = []
977 cmdoptions = []
978 args, opts = parseoptions(ui, cmdoptions, args)
978 args, opts = parseoptions(ui, cmdoptions, args)
979
979
980 if len(args) > 1:
980 if len(args) > 1:
981 ui.status(
981 ui.status(
982 _(
982 _(
983 b"note: hg backout doesn't support multiple commits at "
983 b"note: hg backout doesn't support multiple commits at "
984 b"once\n\n"
984 b"once\n\n"
985 )
985 )
986 )
986 )
987
987
988 cmd = Command(b'backout')
988 cmd = Command(b'backout')
989 if args:
989 if args:
990 cmd.append(args[0])
990 cmd.append(args[0])
991
991
992 ui.status((bytes(cmd)), b"\n")
992 ui.status((bytes(cmd)), b"\n")
993
993
994
994
995 def revparse(ui, repo, *args, **kwargs):
995 def revparse(ui, repo, *args, **kwargs):
996 cmdoptions = [
996 cmdoptions = [
997 (b'', b'show-cdup', None, b''),
997 (b'', b'show-cdup', None, b''),
998 (b'', b'show-toplevel', None, b''),
998 (b'', b'show-toplevel', None, b''),
999 ]
999 ]
1000 args, opts = parseoptions(ui, cmdoptions, args)
1000 args, opts = parseoptions(ui, cmdoptions, args)
1001
1001
1002 if opts.get(b'show_cdup') or opts.get(b'show_toplevel'):
1002 if opts.get(b'show_cdup') or opts.get(b'show_toplevel'):
1003 cmd = Command(b'root')
1003 cmd = Command(b'root')
1004 if opts.get(b'show_cdup'):
1004 if opts.get(b'show_cdup'):
1005 ui.status(_(b"note: hg root prints the root of the repository\n\n"))
1005 ui.status(_(b"note: hg root prints the root of the repository\n\n"))
1006 ui.status((bytes(cmd)), b"\n")
1006 ui.status((bytes(cmd)), b"\n")
1007 else:
1007 else:
1008 ui.status(_(b"note: see hg help revset for how to refer to commits\n"))
1008 ui.status(_(b"note: see hg help revset for how to refer to commits\n"))
1009
1009
1010
1010
1011 def rm(ui, repo, *args, **kwargs):
1011 def rm(ui, repo, *args, **kwargs):
1012 cmdoptions = [
1012 cmdoptions = [
1013 (b'f', b'force', None, b''),
1013 (b'f', b'force', None, b''),
1014 (b'n', b'dry-run', None, b''),
1014 (b'n', b'dry-run', None, b''),
1015 ]
1015 ]
1016 args, opts = parseoptions(ui, cmdoptions, args)
1016 args, opts = parseoptions(ui, cmdoptions, args)
1017
1017
1018 cmd = Command(b'rm')
1018 cmd = Command(b'rm')
1019 cmd.extend(args)
1019 cmd.extend(args)
1020
1020
1021 if opts.get(b'force'):
1021 if opts.get(b'force'):
1022 cmd[b'-f'] = None
1022 cmd[b'-f'] = None
1023 if opts.get(b'dry_run'):
1023 if opts.get(b'dry_run'):
1024 cmd[b'-n'] = None
1024 cmd[b'-n'] = None
1025
1025
1026 ui.status((bytes(cmd)), b"\n")
1026 ui.status((bytes(cmd)), b"\n")
1027
1027
1028
1028
1029 def show(ui, repo, *args, **kwargs):
1029 def show(ui, repo, *args, **kwargs):
1030 cmdoptions = [
1030 cmdoptions = [
1031 (b'', b'name-status', None, b''),
1031 (b'', b'name-status', None, b''),
1032 (b'', b'pretty', b'', b''),
1032 (b'', b'pretty', b'', b''),
1033 (b'U', b'unified', int, b''),
1033 (b'U', b'unified', int, b''),
1034 ]
1034 ]
1035 args, opts = parseoptions(ui, cmdoptions, args)
1035 args, opts = parseoptions(ui, cmdoptions, args)
1036
1036
1037 if opts.get(b'name_status'):
1037 if opts.get(b'name_status'):
1038 if opts.get(b'pretty') == b'format:':
1038 if opts.get(b'pretty') == b'format:':
1039 cmd = Command(b'status')
1039 cmd = Command(b'status')
1040 cmd[b'--change'] = b'.'
1040 cmd[b'--change'] = b'.'
1041 else:
1041 else:
1042 cmd = Command(b'log')
1042 cmd = Command(b'log')
1043 cmd.append(b'--style status')
1043 cmd.append(b'--style status')
1044 cmd.append(b'-r .')
1044 cmd.append(b'-r .')
1045 elif len(args) > 0:
1045 elif len(args) > 0:
1046 if ispath(repo, args[0]):
1046 if ispath(repo, args[0]):
1047 cmd = Command(b'cat')
1047 cmd = Command(b'cat')
1048 else:
1048 else:
1049 cmd = Command(b'export')
1049 cmd = Command(b'export')
1050 cmd.extend(args)
1050 cmd.extend(args)
1051 if opts.get(b'unified'):
1051 if opts.get(b'unified'):
1052 cmd.append(b'--config diff.unified=%d' % (opts[b'unified'],))
1052 cmd.append(b'--config diff.unified=%d' % (opts[b'unified'],))
1053 elif opts.get(b'unified'):
1053 elif opts.get(b'unified'):
1054 cmd = Command(b'export')
1054 cmd = Command(b'export')
1055 cmd.append(b'--config diff.unified=%d' % (opts[b'unified'],))
1055 cmd.append(b'--config diff.unified=%d' % (opts[b'unified'],))
1056 else:
1056 else:
1057 cmd = Command(b'export')
1057 cmd = Command(b'export')
1058
1058
1059 ui.status((bytes(cmd)), b"\n")
1059 ui.status((bytes(cmd)), b"\n")
1060
1060
1061
1061
1062 def stash(ui, repo, *args, **kwargs):
1062 def stash(ui, repo, *args, **kwargs):
1063 cmdoptions = [
1063 cmdoptions = [
1064 (b'p', b'patch', None, b''),
1064 (b'p', b'patch', None, b''),
1065 ]
1065 ]
1066 args, opts = parseoptions(ui, cmdoptions, args)
1066 args, opts = parseoptions(ui, cmdoptions, args)
1067
1067
1068 cmd = Command(b'shelve')
1068 cmd = Command(b'shelve')
1069 action = args[0] if len(args) > 0 else None
1069 action = args[0] if len(args) > 0 else None
1070
1070
1071 if action == b'list':
1071 if action == b'list':
1072 cmd[b'-l'] = None
1072 cmd[b'-l'] = None
1073 if opts.get(b'patch'):
1073 if opts.get(b'patch'):
1074 cmd[b'-p'] = None
1074 cmd[b'-p'] = None
1075 elif action == b'show':
1075 elif action == b'show':
1076 if opts.get(b'patch'):
1076 if opts.get(b'patch'):
1077 cmd[b'-p'] = None
1077 cmd[b'-p'] = None
1078 else:
1078 else:
1079 cmd[b'--stat'] = None
1079 cmd[b'--stat'] = None
1080 if len(args) > 1:
1080 if len(args) > 1:
1081 cmd.append(args[1])
1081 cmd.append(args[1])
1082 elif action == b'clear':
1082 elif action == b'clear':
1083 cmd[b'--cleanup'] = None
1083 cmd[b'--cleanup'] = None
1084 elif action == b'drop':
1084 elif action == b'drop':
1085 cmd[b'-d'] = None
1085 cmd[b'-d'] = None
1086 if len(args) > 1:
1086 if len(args) > 1:
1087 cmd.append(args[1])
1087 cmd.append(args[1])
1088 else:
1088 else:
1089 cmd.append(b'<shelve name>')
1089 cmd.append(b'<shelve name>')
1090 elif action == b'pop' or action == b'apply':
1090 elif action == b'pop' or action == b'apply':
1091 cmd = Command(b'unshelve')
1091 cmd = Command(b'unshelve')
1092 if len(args) > 1:
1092 if len(args) > 1:
1093 cmd.append(args[1])
1093 cmd.append(args[1])
1094 if action == b'apply':
1094 if action == b'apply':
1095 cmd[b'--keep'] = None
1095 cmd[b'--keep'] = None
1096 elif action == b'branch' or action == b'create':
1096 elif action == b'branch' or action == b'create':
1097 ui.status(
1097 ui.status(
1098 _(
1098 _(
1099 b"note: Mercurial doesn't have equivalents to the "
1099 b"note: Mercurial doesn't have equivalents to the "
1100 b"git stash branch or create actions\n\n"
1100 b"git stash branch or create actions\n\n"
1101 )
1101 )
1102 )
1102 )
1103 return
1103 return
1104 else:
1104 else:
1105 if len(args) > 0:
1105 if len(args) > 0:
1106 if args[0] != b'save':
1106 if args[0] != b'save':
1107 cmd[b'--name'] = args[0]
1107 cmd[b'--name'] = args[0]
1108 elif len(args) > 1:
1108 elif len(args) > 1:
1109 cmd[b'--name'] = args[1]
1109 cmd[b'--name'] = args[1]
1110
1110
1111 ui.status((bytes(cmd)), b"\n")
1111 ui.status((bytes(cmd)), b"\n")
1112
1112
1113
1113
1114 def status(ui, repo, *args, **kwargs):
1114 def status(ui, repo, *args, **kwargs):
1115 cmdoptions = [
1115 cmdoptions = [
1116 (b'', b'ignored', None, b''),
1116 (b'', b'ignored', None, b''),
1117 ]
1117 ]
1118 args, opts = parseoptions(ui, cmdoptions, args)
1118 args, opts = parseoptions(ui, cmdoptions, args)
1119
1119
1120 cmd = Command(b'status')
1120 cmd = Command(b'status')
1121 cmd.extend(args)
1121 cmd.extend(args)
1122
1122
1123 if opts.get(b'ignored'):
1123 if opts.get(b'ignored'):
1124 cmd[b'-i'] = None
1124 cmd[b'-i'] = None
1125
1125
1126 ui.status((bytes(cmd)), b"\n")
1126 ui.status((bytes(cmd)), b"\n")
1127
1127
1128
1128
1129 def svn(ui, repo, *args, **kwargs):
1129 def svn(ui, repo, *args, **kwargs):
1130 if not args:
1130 if not args:
1131 raise error.Abort(_(b'missing svn command'))
1131 raise error.Abort(_(b'missing svn command'))
1132 svncmd = args[0]
1132 svncmd = args[0]
1133 if svncmd not in gitsvncommands:
1133 if svncmd not in gitsvncommands:
1134 raise error.Abort(_(b'unknown git svn command "%s"') % svncmd)
1134 raise error.Abort(_(b'unknown git svn command "%s"') % svncmd)
1135
1135
1136 args = args[1:]
1136 args = args[1:]
1137 return gitsvncommands[svncmd](ui, repo, *args, **kwargs)
1137 return gitsvncommands[svncmd](ui, repo, *args, **kwargs)
1138
1138
1139
1139
1140 def svndcommit(ui, repo, *args, **kwargs):
1140 def svndcommit(ui, repo, *args, **kwargs):
1141 cmdoptions = []
1141 cmdoptions = []
1142 args, opts = parseoptions(ui, cmdoptions, args)
1142 args, opts = parseoptions(ui, cmdoptions, args)
1143
1143
1144 cmd = Command(b'push')
1144 cmd = Command(b'push')
1145
1145
1146 ui.status((bytes(cmd)), b"\n")
1146 ui.status((bytes(cmd)), b"\n")
1147
1147
1148
1148
1149 def svnfetch(ui, repo, *args, **kwargs):
1149 def svnfetch(ui, repo, *args, **kwargs):
1150 cmdoptions = []
1150 cmdoptions = []
1151 args, opts = parseoptions(ui, cmdoptions, args)
1151 args, opts = parseoptions(ui, cmdoptions, args)
1152
1152
1153 cmd = Command(b'pull')
1153 cmd = Command(b'pull')
1154 cmd.append(b'default-push')
1154 cmd.append(b'default-push')
1155
1155
1156 ui.status((bytes(cmd)), b"\n")
1156 ui.status((bytes(cmd)), b"\n")
1157
1157
1158
1158
1159 def svnfindrev(ui, repo, *args, **kwargs):
1159 def svnfindrev(ui, repo, *args, **kwargs):
1160 cmdoptions = []
1160 cmdoptions = []
1161 args, opts = parseoptions(ui, cmdoptions, args)
1161 args, opts = parseoptions(ui, cmdoptions, args)
1162
1162
1163 if not args:
1163 if not args:
1164 raise error.Abort(_(b'missing find-rev argument'))
1164 raise error.Abort(_(b'missing find-rev argument'))
1165
1165
1166 cmd = Command(b'log')
1166 cmd = Command(b'log')
1167 cmd[b'-r'] = args[0]
1167 cmd[b'-r'] = args[0]
1168
1168
1169 ui.status((bytes(cmd)), b"\n")
1169 ui.status((bytes(cmd)), b"\n")
1170
1170
1171
1171
1172 def svnrebase(ui, repo, *args, **kwargs):
1172 def svnrebase(ui, repo, *args, **kwargs):
1173 cmdoptions = [
1173 cmdoptions = [
1174 (b'l', b'local', None, b''),
1174 (b'l', b'local', None, b''),
1175 ]
1175 ]
1176 args, opts = parseoptions(ui, cmdoptions, args)
1176 args, opts = parseoptions(ui, cmdoptions, args)
1177
1177
1178 pullcmd = Command(b'pull')
1178 pullcmd = Command(b'pull')
1179 pullcmd.append(b'default-push')
1179 pullcmd.append(b'default-push')
1180 rebasecmd = Command(b'rebase')
1180 rebasecmd = Command(b'rebase')
1181 rebasecmd.append(b'tip')
1181 rebasecmd.append(b'tip')
1182
1182
1183 cmd = pullcmd & rebasecmd
1183 cmd = pullcmd & rebasecmd
1184
1184
1185 ui.status((bytes(cmd)), b"\n")
1185 ui.status((bytes(cmd)), b"\n")
1186
1186
1187
1187
1188 def tag(ui, repo, *args, **kwargs):
1188 def tag(ui, repo, *args, **kwargs):
1189 cmdoptions = [
1189 cmdoptions = [
1190 (b'f', b'force', None, b''),
1190 (b'f', b'force', None, b''),
1191 (b'l', b'list', None, b''),
1191 (b'l', b'list', None, b''),
1192 (b'd', b'delete', None, b''),
1192 (b'd', b'delete', None, b''),
1193 ]
1193 ]
1194 args, opts = parseoptions(ui, cmdoptions, args)
1194 args, opts = parseoptions(ui, cmdoptions, args)
1195
1195
1196 if opts.get(b'list'):
1196 if opts.get(b'list'):
1197 cmd = Command(b'tags')
1197 cmd = Command(b'tags')
1198 else:
1198 else:
1199 cmd = Command(b'tag')
1199 cmd = Command(b'tag')
1200
1200
1201 if not args:
1201 if not args:
1202 raise error.Abort(_(b'missing tag argument'))
1202 raise error.Abort(_(b'missing tag argument'))
1203
1203
1204 cmd.append(args[0])
1204 cmd.append(args[0])
1205 if len(args) > 1:
1205 if len(args) > 1:
1206 cmd[b'-r'] = args[1]
1206 cmd[b'-r'] = args[1]
1207
1207
1208 if opts.get(b'delete'):
1208 if opts.get(b'delete'):
1209 cmd[b'--remove'] = None
1209 cmd[b'--remove'] = None
1210
1210
1211 if opts.get(b'force'):
1211 if opts.get(b'force'):
1212 cmd[b'-f'] = None
1212 cmd[b'-f'] = None
1213
1213
1214 ui.status((bytes(cmd)), b"\n")
1214 ui.status((bytes(cmd)), b"\n")
1215
1215
1216
1216
1217 gitcommands = {
1217 gitcommands = {
1218 b'add': add,
1218 b'add': add,
1219 b'am': am,
1219 b'am': am,
1220 b'apply': apply,
1220 b'apply': apply,
1221 b'bisect': bisect,
1221 b'bisect': bisect,
1222 b'blame': blame,
1222 b'blame': blame,
1223 b'branch': branch,
1223 b'branch': branch,
1224 b'checkout': checkout,
1224 b'checkout': checkout,
1225 b'cherry-pick': cherrypick,
1225 b'cherry-pick': cherrypick,
1226 b'clean': clean,
1226 b'clean': clean,
1227 b'clone': clone,
1227 b'clone': clone,
1228 b'commit': commit,
1228 b'commit': commit,
1229 b'diff': diff,
1229 b'diff': diff,
1230 b'difftool': difftool,
1230 b'difftool': difftool,
1231 b'fetch': fetch,
1231 b'fetch': fetch,
1232 b'grep': grep,
1232 b'grep': grep,
1233 b'init': init,
1233 b'init': init,
1234 b'log': log,
1234 b'log': log,
1235 b'ls-files': lsfiles,
1235 b'ls-files': lsfiles,
1236 b'merge': merge,
1236 b'merge': merge,
1237 b'merge-base': mergebase,
1237 b'merge-base': mergebase,
1238 b'mergetool': mergetool,
1238 b'mergetool': mergetool,
1239 b'mv': mv,
1239 b'mv': mv,
1240 b'pull': pull,
1240 b'pull': pull,
1241 b'push': push,
1241 b'push': push,
1242 b'rebase': rebase,
1242 b'rebase': rebase,
1243 b'reflog': reflog,
1243 b'reflog': reflog,
1244 b'reset': reset,
1244 b'reset': reset,
1245 b'revert': revert,
1245 b'revert': revert,
1246 b'rev-parse': revparse,
1246 b'rev-parse': revparse,
1247 b'rm': rm,
1247 b'rm': rm,
1248 b'show': show,
1248 b'show': show,
1249 b'stash': stash,
1249 b'stash': stash,
1250 b'status': status,
1250 b'status': status,
1251 b'svn': svn,
1251 b'svn': svn,
1252 b'tag': tag,
1252 b'tag': tag,
1253 b'whatchanged': deprecated,
1253 b'whatchanged': deprecated,
1254 }
1254 }
1255
1255
1256 gitsvncommands = {
1256 gitsvncommands = {
1257 b'dcommit': svndcommit,
1257 b'dcommit': svndcommit,
1258 b'fetch': svnfetch,
1258 b'fetch': svnfetch,
1259 b'find-rev': svnfindrev,
1259 b'find-rev': svnfindrev,
1260 b'rebase': svnrebase,
1260 b'rebase': svnrebase,
1261 }
1261 }
@@ -1,2650 +1,2650 b''
1 # histedit.py - interactive history editing for mercurial
1 # histedit.py - interactive history editing for mercurial
2 #
2 #
3 # Copyright 2009 Augie Fackler <raf@durin42.com>
3 # Copyright 2009 Augie Fackler <raf@durin42.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 """interactive history editing
7 """interactive history editing
8
8
9 With this extension installed, Mercurial gains one new command: histedit. Usage
9 With this extension installed, Mercurial gains one new command: histedit. Usage
10 is as follows, assuming the following history::
10 is as follows, assuming the following history::
11
11
12 @ 3[tip] 7c2fd3b9020c 2009-04-27 18:04 -0500 durin42
12 @ 3[tip] 7c2fd3b9020c 2009-04-27 18:04 -0500 durin42
13 | Add delta
13 | Add delta
14 |
14 |
15 o 2 030b686bedc4 2009-04-27 18:04 -0500 durin42
15 o 2 030b686bedc4 2009-04-27 18:04 -0500 durin42
16 | Add gamma
16 | Add gamma
17 |
17 |
18 o 1 c561b4e977df 2009-04-27 18:04 -0500 durin42
18 o 1 c561b4e977df 2009-04-27 18:04 -0500 durin42
19 | Add beta
19 | Add beta
20 |
20 |
21 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
21 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
22 Add alpha
22 Add alpha
23
23
24 If you were to run ``hg histedit c561b4e977df``, you would see the following
24 If you were to run ``hg histedit c561b4e977df``, you would see the following
25 file open in your editor::
25 file open in your editor::
26
26
27 pick c561b4e977df Add beta
27 pick c561b4e977df Add beta
28 pick 030b686bedc4 Add gamma
28 pick 030b686bedc4 Add gamma
29 pick 7c2fd3b9020c Add delta
29 pick 7c2fd3b9020c Add delta
30
30
31 # Edit history between c561b4e977df and 7c2fd3b9020c
31 # Edit history between c561b4e977df and 7c2fd3b9020c
32 #
32 #
33 # Commits are listed from least to most recent
33 # Commits are listed from least to most recent
34 #
34 #
35 # Commands:
35 # Commands:
36 # p, pick = use commit
36 # p, pick = use commit
37 # e, edit = use commit, but stop for amending
37 # e, edit = use commit, but stop for amending
38 # f, fold = use commit, but combine it with the one above
38 # f, fold = use commit, but combine it with the one above
39 # r, roll = like fold, but discard this commit's description and date
39 # r, roll = like fold, but discard this commit's description and date
40 # d, drop = remove commit from history
40 # d, drop = remove commit from history
41 # m, mess = edit commit message without changing commit content
41 # m, mess = edit commit message without changing commit content
42 # b, base = checkout changeset and apply further changesets from there
42 # b, base = checkout changeset and apply further changesets from there
43 #
43 #
44
44
45 In this file, lines beginning with ``#`` are ignored. You must specify a rule
45 In this file, lines beginning with ``#`` are ignored. You must specify a rule
46 for each revision in your history. For example, if you had meant to add gamma
46 for each revision in your history. For example, if you had meant to add gamma
47 before beta, and then wanted to add delta in the same revision as beta, you
47 before beta, and then wanted to add delta in the same revision as beta, you
48 would reorganize the file to look like this::
48 would reorganize the file to look like this::
49
49
50 pick 030b686bedc4 Add gamma
50 pick 030b686bedc4 Add gamma
51 pick c561b4e977df Add beta
51 pick c561b4e977df Add beta
52 fold 7c2fd3b9020c Add delta
52 fold 7c2fd3b9020c Add delta
53
53
54 # Edit history between c561b4e977df and 7c2fd3b9020c
54 # Edit history between c561b4e977df and 7c2fd3b9020c
55 #
55 #
56 # Commits are listed from least to most recent
56 # Commits are listed from least to most recent
57 #
57 #
58 # Commands:
58 # Commands:
59 # p, pick = use commit
59 # p, pick = use commit
60 # e, edit = use commit, but stop for amending
60 # e, edit = use commit, but stop for amending
61 # f, fold = use commit, but combine it with the one above
61 # f, fold = use commit, but combine it with the one above
62 # r, roll = like fold, but discard this commit's description and date
62 # r, roll = like fold, but discard this commit's description and date
63 # d, drop = remove commit from history
63 # d, drop = remove commit from history
64 # m, mess = edit commit message without changing commit content
64 # m, mess = edit commit message without changing commit content
65 # b, base = checkout changeset and apply further changesets from there
65 # b, base = checkout changeset and apply further changesets from there
66 #
66 #
67
67
68 At which point you close the editor and ``histedit`` starts working. When you
68 At which point you close the editor and ``histedit`` starts working. When you
69 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
69 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
70 those revisions together, offering you a chance to clean up the commit message::
70 those revisions together, offering you a chance to clean up the commit message::
71
71
72 Add beta
72 Add beta
73 ***
73 ***
74 Add delta
74 Add delta
75
75
76 Edit the commit message to your liking, then close the editor. The date used
76 Edit the commit message to your liking, then close the editor. The date used
77 for the commit will be the later of the two commits' dates. For this example,
77 for the commit will be the later of the two commits' dates. For this example,
78 let's assume that the commit message was changed to ``Add beta and delta.``
78 let's assume that the commit message was changed to ``Add beta and delta.``
79 After histedit has run and had a chance to remove any old or temporary
79 After histedit has run and had a chance to remove any old or temporary
80 revisions it needed, the history looks like this::
80 revisions it needed, the history looks like this::
81
81
82 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
82 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
83 | Add beta and delta.
83 | Add beta and delta.
84 |
84 |
85 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
85 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
86 | Add gamma
86 | Add gamma
87 |
87 |
88 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
88 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
89 Add alpha
89 Add alpha
90
90
91 Note that ``histedit`` does *not* remove any revisions (even its own temporary
91 Note that ``histedit`` does *not* remove any revisions (even its own temporary
92 ones) until after it has completed all the editing operations, so it will
92 ones) until after it has completed all the editing operations, so it will
93 probably perform several strip operations when it's done. For the above example,
93 probably perform several strip operations when it's done. For the above example,
94 it had to run strip twice. Strip can be slow depending on a variety of factors,
94 it had to run strip twice. Strip can be slow depending on a variety of factors,
95 so you might need to be a little patient. You can choose to keep the original
95 so you might need to be a little patient. You can choose to keep the original
96 revisions by passing the ``--keep`` flag.
96 revisions by passing the ``--keep`` flag.
97
97
98 The ``edit`` operation will drop you back to a command prompt,
98 The ``edit`` operation will drop you back to a command prompt,
99 allowing you to edit files freely, or even use ``hg record`` to commit
99 allowing you to edit files freely, or even use ``hg record`` to commit
100 some changes as a separate commit. When you're done, any remaining
100 some changes as a separate commit. When you're done, any remaining
101 uncommitted changes will be committed as well. When done, run ``hg
101 uncommitted changes will be committed as well. When done, run ``hg
102 histedit --continue`` to finish this step. If there are uncommitted
102 histedit --continue`` to finish this step. If there are uncommitted
103 changes, you'll be prompted for a new commit message, but the default
103 changes, you'll be prompted for a new commit message, but the default
104 commit message will be the original message for the ``edit`` ed
104 commit message will be the original message for the ``edit`` ed
105 revision, and the date of the original commit will be preserved.
105 revision, and the date of the original commit will be preserved.
106
106
107 The ``message`` operation will give you a chance to revise a commit
107 The ``message`` operation will give you a chance to revise a commit
108 message without changing the contents. It's a shortcut for doing
108 message without changing the contents. It's a shortcut for doing
109 ``edit`` immediately followed by `hg histedit --continue``.
109 ``edit`` immediately followed by `hg histedit --continue``.
110
110
111 If ``histedit`` encounters a conflict when moving a revision (while
111 If ``histedit`` encounters a conflict when moving a revision (while
112 handling ``pick`` or ``fold``), it'll stop in a similar manner to
112 handling ``pick`` or ``fold``), it'll stop in a similar manner to
113 ``edit`` with the difference that it won't prompt you for a commit
113 ``edit`` with the difference that it won't prompt you for a commit
114 message when done. If you decide at this point that you don't like how
114 message when done. If you decide at this point that you don't like how
115 much work it will be to rearrange history, or that you made a mistake,
115 much work it will be to rearrange history, or that you made a mistake,
116 you can use ``hg histedit --abort`` to abandon the new changes you
116 you can use ``hg histedit --abort`` to abandon the new changes you
117 have made and return to the state before you attempted to edit your
117 have made and return to the state before you attempted to edit your
118 history.
118 history.
119
119
120 If we clone the histedit-ed example repository above and add four more
120 If we clone the histedit-ed example repository above and add four more
121 changes, such that we have the following history::
121 changes, such that we have the following history::
122
122
123 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
123 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
124 | Add theta
124 | Add theta
125 |
125 |
126 o 5 140988835471 2009-04-27 18:04 -0500 stefan
126 o 5 140988835471 2009-04-27 18:04 -0500 stefan
127 | Add eta
127 | Add eta
128 |
128 |
129 o 4 122930637314 2009-04-27 18:04 -0500 stefan
129 o 4 122930637314 2009-04-27 18:04 -0500 stefan
130 | Add zeta
130 | Add zeta
131 |
131 |
132 o 3 836302820282 2009-04-27 18:04 -0500 stefan
132 o 3 836302820282 2009-04-27 18:04 -0500 stefan
133 | Add epsilon
133 | Add epsilon
134 |
134 |
135 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
135 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
136 | Add beta and delta.
136 | Add beta and delta.
137 |
137 |
138 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
138 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
139 | Add gamma
139 | Add gamma
140 |
140 |
141 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
141 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
142 Add alpha
142 Add alpha
143
143
144 If you run ``hg histedit --outgoing`` on the clone then it is the same
144 If you run ``hg histedit --outgoing`` on the clone then it is the same
145 as running ``hg histedit 836302820282``. If you need plan to push to a
145 as running ``hg histedit 836302820282``. If you need plan to push to a
146 repository that Mercurial does not detect to be related to the source
146 repository that Mercurial does not detect to be related to the source
147 repo, you can add a ``--force`` option.
147 repo, you can add a ``--force`` option.
148
148
149 Config
149 Config
150 ------
150 ------
151
151
152 Histedit rule lines are truncated to 80 characters by default. You
152 Histedit rule lines are truncated to 80 characters by default. You
153 can customize this behavior by setting a different length in your
153 can customize this behavior by setting a different length in your
154 configuration file::
154 configuration file::
155
155
156 [histedit]
156 [histedit]
157 linelen = 120 # truncate rule lines at 120 characters
157 linelen = 120 # truncate rule lines at 120 characters
158
158
159 The summary of a change can be customized as well::
159 The summary of a change can be customized as well::
160
160
161 [histedit]
161 [histedit]
162 summary-template = '{rev} {bookmarks} {desc|firstline}'
162 summary-template = '{rev} {bookmarks} {desc|firstline}'
163
163
164 The customized summary should be kept short enough that rule lines
164 The customized summary should be kept short enough that rule lines
165 will fit in the configured line length. See above if that requires
165 will fit in the configured line length. See above if that requires
166 customization.
166 customization.
167
167
168 ``hg histedit`` attempts to automatically choose an appropriate base
168 ``hg histedit`` attempts to automatically choose an appropriate base
169 revision to use. To change which base revision is used, define a
169 revision to use. To change which base revision is used, define a
170 revset in your configuration file::
170 revset in your configuration file::
171
171
172 [histedit]
172 [histedit]
173 defaultrev = only(.) & draft()
173 defaultrev = only(.) & draft()
174
174
175 By default each edited revision needs to be present in histedit commands.
175 By default each edited revision needs to be present in histedit commands.
176 To remove revision you need to use ``drop`` operation. You can configure
176 To remove revision you need to use ``drop`` operation. You can configure
177 the drop to be implicit for missing commits by adding::
177 the drop to be implicit for missing commits by adding::
178
178
179 [histedit]
179 [histedit]
180 dropmissing = True
180 dropmissing = True
181
181
182 By default, histedit will close the transaction after each action. For
182 By default, histedit will close the transaction after each action. For
183 performance purposes, you can configure histedit to use a single transaction
183 performance purposes, you can configure histedit to use a single transaction
184 across the entire histedit. WARNING: This setting introduces a significant risk
184 across the entire histedit. WARNING: This setting introduces a significant risk
185 of losing the work you've done in a histedit if the histedit aborts
185 of losing the work you've done in a histedit if the histedit aborts
186 unexpectedly::
186 unexpectedly::
187
187
188 [histedit]
188 [histedit]
189 singletransaction = True
189 singletransaction = True
190
190
191 """
191 """
192
192
193 from __future__ import absolute_import
193 from __future__ import absolute_import
194
194
195 # chistedit dependencies that are not available everywhere
195 # chistedit dependencies that are not available everywhere
196 try:
196 try:
197 import fcntl
197 import fcntl
198 import termios
198 import termios
199 except ImportError:
199 except ImportError:
200 fcntl = None
200 fcntl = None
201 termios = None
201 termios = None
202
202
203 import functools
203 import functools
204 import locale
204 import locale
205 import os
205 import os
206 import struct
206 import struct
207
207
208 from mercurial.i18n import _
208 from mercurial.i18n import _
209 from mercurial.pycompat import (
209 from mercurial.pycompat import (
210 getattr,
210 getattr,
211 open,
211 open,
212 )
212 )
213 from mercurial import (
213 from mercurial import (
214 bundle2,
214 bundle2,
215 cmdutil,
215 cmdutil,
216 context,
216 context,
217 copies,
217 copies,
218 destutil,
218 destutil,
219 discovery,
219 discovery,
220 encoding,
220 encoding,
221 error,
221 error,
222 exchange,
222 exchange,
223 extensions,
223 extensions,
224 hg,
224 hg,
225 logcmdutil,
225 logcmdutil,
226 merge as mergemod,
226 merge as mergemod,
227 mergeutil,
227 mergeutil,
228 node,
228 node,
229 obsolete,
229 obsolete,
230 pycompat,
230 pycompat,
231 registrar,
231 registrar,
232 repair,
232 repair,
233 scmutil,
233 scmutil,
234 state as statemod,
234 state as statemod,
235 util,
235 util,
236 )
236 )
237 from mercurial.utils import (
237 from mercurial.utils import (
238 dateutil,
238 dateutil,
239 stringutil,
239 stringutil,
240 )
240 )
241
241
242 pickle = util.pickle
242 pickle = util.pickle
243 cmdtable = {}
243 cmdtable = {}
244 command = registrar.command(cmdtable)
244 command = registrar.command(cmdtable)
245
245
246 configtable = {}
246 configtable = {}
247 configitem = registrar.configitem(configtable)
247 configitem = registrar.configitem(configtable)
248 configitem(
248 configitem(
249 b'experimental', b'histedit.autoverb', default=False,
249 b'experimental', b'histedit.autoverb', default=False,
250 )
250 )
251 configitem(
251 configitem(
252 b'histedit', b'defaultrev', default=None,
252 b'histedit', b'defaultrev', default=None,
253 )
253 )
254 configitem(
254 configitem(
255 b'histedit', b'dropmissing', default=False,
255 b'histedit', b'dropmissing', default=False,
256 )
256 )
257 configitem(
257 configitem(
258 b'histedit', b'linelen', default=80,
258 b'histedit', b'linelen', default=80,
259 )
259 )
260 configitem(
260 configitem(
261 b'histedit', b'singletransaction', default=False,
261 b'histedit', b'singletransaction', default=False,
262 )
262 )
263 configitem(
263 configitem(
264 b'ui', b'interface.histedit', default=None,
264 b'ui', b'interface.histedit', default=None,
265 )
265 )
266 configitem(b'histedit', b'summary-template', default=b'{rev} {desc|firstline}')
266 configitem(b'histedit', b'summary-template', default=b'{rev} {desc|firstline}')
267
267
268 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
268 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
269 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
269 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
270 # be specifying the version(s) of Mercurial they are tested with, or
270 # be specifying the version(s) of Mercurial they are tested with, or
271 # leave the attribute unspecified.
271 # leave the attribute unspecified.
272 testedwith = b'ships-with-hg-core'
272 testedwith = b'ships-with-hg-core'
273
273
274 actiontable = {}
274 actiontable = {}
275 primaryactions = set()
275 primaryactions = set()
276 secondaryactions = set()
276 secondaryactions = set()
277 tertiaryactions = set()
277 tertiaryactions = set()
278 internalactions = set()
278 internalactions = set()
279
279
280
280
281 def geteditcomment(ui, first, last):
281 def geteditcomment(ui, first, last):
282 """ construct the editor comment
282 """ construct the editor comment
283 The comment includes::
283 The comment includes::
284 - an intro
284 - an intro
285 - sorted primary commands
285 - sorted primary commands
286 - sorted short commands
286 - sorted short commands
287 - sorted long commands
287 - sorted long commands
288 - additional hints
288 - additional hints
289
289
290 Commands are only included once.
290 Commands are only included once.
291 """
291 """
292 intro = _(
292 intro = _(
293 """Edit history between %s and %s
293 """Edit history between %s and %s
294
294
295 Commits are listed from least to most recent
295 Commits are listed from least to most recent
296
296
297 You can reorder changesets by reordering the lines
297 You can reorder changesets by reordering the lines
298
298
299 Commands:
299 Commands:
300 """
300 """
301 )
301 )
302 actions = []
302 actions = []
303
303
304 def addverb(v):
304 def addverb(v):
305 a = actiontable[v]
305 a = actiontable[v]
306 lines = a.message.split(b"\n")
306 lines = a.message.split(b"\n")
307 if len(a.verbs):
307 if len(a.verbs):
308 v = b', '.join(sorted(a.verbs, key=lambda v: len(v)))
308 v = b', '.join(sorted(a.verbs, key=lambda v: len(v)))
309 actions.append(b" %s = %s" % (v, lines[0]))
309 actions.append(b" %s = %s" % (v, lines[0]))
310 actions.extend([b' %s' for l in lines[1:]])
310 actions.extend([b' %s' for l in lines[1:]])
311
311
312 for v in (
312 for v in (
313 sorted(primaryactions)
313 sorted(primaryactions)
314 + sorted(secondaryactions)
314 + sorted(secondaryactions)
315 + sorted(tertiaryactions)
315 + sorted(tertiaryactions)
316 ):
316 ):
317 addverb(v)
317 addverb(v)
318 actions.append(b'')
318 actions.append(b'')
319
319
320 hints = []
320 hints = []
321 if ui.configbool(b'histedit', b'dropmissing'):
321 if ui.configbool(b'histedit', b'dropmissing'):
322 hints.append(
322 hints.append(
323 b"Deleting a changeset from the list "
323 b"Deleting a changeset from the list "
324 b"will DISCARD it from the edited history!"
324 b"will DISCARD it from the edited history!"
325 )
325 )
326
326
327 lines = (intro % (first, last)).split(b'\n') + actions + hints
327 lines = (intro % (first, last)).split(b'\n') + actions + hints
328
328
329 return b''.join([b'# %s\n' % l if l else b'#\n' for l in lines])
329 return b''.join([b'# %s\n' % l if l else b'#\n' for l in lines])
330
330
331
331
332 class histeditstate(object):
332 class histeditstate(object):
333 def __init__(self, repo):
333 def __init__(self, repo):
334 self.repo = repo
334 self.repo = repo
335 self.actions = None
335 self.actions = None
336 self.keep = None
336 self.keep = None
337 self.topmost = None
337 self.topmost = None
338 self.parentctxnode = None
338 self.parentctxnode = None
339 self.lock = None
339 self.lock = None
340 self.wlock = None
340 self.wlock = None
341 self.backupfile = None
341 self.backupfile = None
342 self.stateobj = statemod.cmdstate(repo, b'histedit-state')
342 self.stateobj = statemod.cmdstate(repo, b'histedit-state')
343 self.replacements = []
343 self.replacements = []
344
344
345 def read(self):
345 def read(self):
346 """Load histedit state from disk and set fields appropriately."""
346 """Load histedit state from disk and set fields appropriately."""
347 if not self.stateobj.exists():
347 if not self.stateobj.exists():
348 cmdutil.wrongtooltocontinue(self.repo, _(b'histedit'))
348 cmdutil.wrongtooltocontinue(self.repo, _(b'histedit'))
349
349
350 data = self._read()
350 data = self._read()
351
351
352 self.parentctxnode = data[b'parentctxnode']
352 self.parentctxnode = data[b'parentctxnode']
353 actions = parserules(data[b'rules'], self)
353 actions = parserules(data[b'rules'], self)
354 self.actions = actions
354 self.actions = actions
355 self.keep = data[b'keep']
355 self.keep = data[b'keep']
356 self.topmost = data[b'topmost']
356 self.topmost = data[b'topmost']
357 self.replacements = data[b'replacements']
357 self.replacements = data[b'replacements']
358 self.backupfile = data[b'backupfile']
358 self.backupfile = data[b'backupfile']
359
359
360 def _read(self):
360 def _read(self):
361 fp = self.repo.vfs.read(b'histedit-state')
361 fp = self.repo.vfs.read(b'histedit-state')
362 if fp.startswith(b'v1\n'):
362 if fp.startswith(b'v1\n'):
363 data = self._load()
363 data = self._load()
364 parentctxnode, rules, keep, topmost, replacements, backupfile = data
364 parentctxnode, rules, keep, topmost, replacements, backupfile = data
365 else:
365 else:
366 data = pickle.loads(fp)
366 data = pickle.loads(fp)
367 parentctxnode, rules, keep, topmost, replacements = data
367 parentctxnode, rules, keep, topmost, replacements = data
368 backupfile = None
368 backupfile = None
369 rules = b"\n".join([b"%s %s" % (verb, rest) for [verb, rest] in rules])
369 rules = b"\n".join([b"%s %s" % (verb, rest) for [verb, rest] in rules])
370
370
371 return {
371 return {
372 b'parentctxnode': parentctxnode,
372 b'parentctxnode': parentctxnode,
373 b"rules": rules,
373 b"rules": rules,
374 b"keep": keep,
374 b"keep": keep,
375 b"topmost": topmost,
375 b"topmost": topmost,
376 b"replacements": replacements,
376 b"replacements": replacements,
377 b"backupfile": backupfile,
377 b"backupfile": backupfile,
378 }
378 }
379
379
380 def write(self, tr=None):
380 def write(self, tr=None):
381 if tr:
381 if tr:
382 tr.addfilegenerator(
382 tr.addfilegenerator(
383 b'histedit-state',
383 b'histedit-state',
384 (b'histedit-state',),
384 (b'histedit-state',),
385 self._write,
385 self._write,
386 location=b'plain',
386 location=b'plain',
387 )
387 )
388 else:
388 else:
389 with self.repo.vfs(b"histedit-state", b"w") as f:
389 with self.repo.vfs(b"histedit-state", b"w") as f:
390 self._write(f)
390 self._write(f)
391
391
392 def _write(self, fp):
392 def _write(self, fp):
393 fp.write(b'v1\n')
393 fp.write(b'v1\n')
394 fp.write(b'%s\n' % node.hex(self.parentctxnode))
394 fp.write(b'%s\n' % node.hex(self.parentctxnode))
395 fp.write(b'%s\n' % node.hex(self.topmost))
395 fp.write(b'%s\n' % node.hex(self.topmost))
396 fp.write(b'%s\n' % (b'True' if self.keep else b'False'))
396 fp.write(b'%s\n' % (b'True' if self.keep else b'False'))
397 fp.write(b'%d\n' % len(self.actions))
397 fp.write(b'%d\n' % len(self.actions))
398 for action in self.actions:
398 for action in self.actions:
399 fp.write(b'%s\n' % action.tostate())
399 fp.write(b'%s\n' % action.tostate())
400 fp.write(b'%d\n' % len(self.replacements))
400 fp.write(b'%d\n' % len(self.replacements))
401 for replacement in self.replacements:
401 for replacement in self.replacements:
402 fp.write(
402 fp.write(
403 b'%s%s\n'
403 b'%s%s\n'
404 % (
404 % (
405 node.hex(replacement[0]),
405 node.hex(replacement[0]),
406 b''.join(node.hex(r) for r in replacement[1]),
406 b''.join(node.hex(r) for r in replacement[1]),
407 )
407 )
408 )
408 )
409 backupfile = self.backupfile
409 backupfile = self.backupfile
410 if not backupfile:
410 if not backupfile:
411 backupfile = b''
411 backupfile = b''
412 fp.write(b'%s\n' % backupfile)
412 fp.write(b'%s\n' % backupfile)
413
413
414 def _load(self):
414 def _load(self):
415 fp = self.repo.vfs(b'histedit-state', b'r')
415 fp = self.repo.vfs(b'histedit-state', b'r')
416 lines = [l[:-1] for l in fp.readlines()]
416 lines = [l[:-1] for l in fp.readlines()]
417
417
418 index = 0
418 index = 0
419 lines[index] # version number
419 lines[index] # version number
420 index += 1
420 index += 1
421
421
422 parentctxnode = node.bin(lines[index])
422 parentctxnode = node.bin(lines[index])
423 index += 1
423 index += 1
424
424
425 topmost = node.bin(lines[index])
425 topmost = node.bin(lines[index])
426 index += 1
426 index += 1
427
427
428 keep = lines[index] == b'True'
428 keep = lines[index] == b'True'
429 index += 1
429 index += 1
430
430
431 # Rules
431 # Rules
432 rules = []
432 rules = []
433 rulelen = int(lines[index])
433 rulelen = int(lines[index])
434 index += 1
434 index += 1
435 for i in pycompat.xrange(rulelen):
435 for i in pycompat.xrange(rulelen):
436 ruleaction = lines[index]
436 ruleaction = lines[index]
437 index += 1
437 index += 1
438 rule = lines[index]
438 rule = lines[index]
439 index += 1
439 index += 1
440 rules.append((ruleaction, rule))
440 rules.append((ruleaction, rule))
441
441
442 # Replacements
442 # Replacements
443 replacements = []
443 replacements = []
444 replacementlen = int(lines[index])
444 replacementlen = int(lines[index])
445 index += 1
445 index += 1
446 for i in pycompat.xrange(replacementlen):
446 for i in pycompat.xrange(replacementlen):
447 replacement = lines[index]
447 replacement = lines[index]
448 original = node.bin(replacement[:40])
448 original = node.bin(replacement[:40])
449 succ = [
449 succ = [
450 node.bin(replacement[i : i + 40])
450 node.bin(replacement[i : i + 40])
451 for i in range(40, len(replacement), 40)
451 for i in range(40, len(replacement), 40)
452 ]
452 ]
453 replacements.append((original, succ))
453 replacements.append((original, succ))
454 index += 1
454 index += 1
455
455
456 backupfile = lines[index]
456 backupfile = lines[index]
457 index += 1
457 index += 1
458
458
459 fp.close()
459 fp.close()
460
460
461 return parentctxnode, rules, keep, topmost, replacements, backupfile
461 return parentctxnode, rules, keep, topmost, replacements, backupfile
462
462
463 def clear(self):
463 def clear(self):
464 if self.inprogress():
464 if self.inprogress():
465 self.repo.vfs.unlink(b'histedit-state')
465 self.repo.vfs.unlink(b'histedit-state')
466
466
467 def inprogress(self):
467 def inprogress(self):
468 return self.repo.vfs.exists(b'histedit-state')
468 return self.repo.vfs.exists(b'histedit-state')
469
469
470
470
471 class histeditaction(object):
471 class histeditaction(object):
472 def __init__(self, state, node):
472 def __init__(self, state, node):
473 self.state = state
473 self.state = state
474 self.repo = state.repo
474 self.repo = state.repo
475 self.node = node
475 self.node = node
476
476
477 @classmethod
477 @classmethod
478 def fromrule(cls, state, rule):
478 def fromrule(cls, state, rule):
479 """Parses the given rule, returning an instance of the histeditaction.
479 """Parses the given rule, returning an instance of the histeditaction.
480 """
480 """
481 ruleid = rule.strip().split(b' ', 1)[0]
481 ruleid = rule.strip().split(b' ', 1)[0]
482 # ruleid can be anything from rev numbers, hashes, "bookmarks" etc
482 # ruleid can be anything from rev numbers, hashes, "bookmarks" etc
483 # Check for validation of rule ids and get the rulehash
483 # Check for validation of rule ids and get the rulehash
484 try:
484 try:
485 rev = node.bin(ruleid)
485 rev = node.bin(ruleid)
486 except TypeError:
486 except TypeError:
487 try:
487 try:
488 _ctx = scmutil.revsingle(state.repo, ruleid)
488 _ctx = scmutil.revsingle(state.repo, ruleid)
489 rulehash = _ctx.hex()
489 rulehash = _ctx.hex()
490 rev = node.bin(rulehash)
490 rev = node.bin(rulehash)
491 except error.RepoLookupError:
491 except error.RepoLookupError:
492 raise error.ParseError(_(b"invalid changeset %s") % ruleid)
492 raise error.ParseError(_(b"invalid changeset %s") % ruleid)
493 return cls(state, rev)
493 return cls(state, rev)
494
494
495 def verify(self, prev, expected, seen):
495 def verify(self, prev, expected, seen):
496 """ Verifies semantic correctness of the rule"""
496 """ Verifies semantic correctness of the rule"""
497 repo = self.repo
497 repo = self.repo
498 ha = node.hex(self.node)
498 ha = node.hex(self.node)
499 self.node = scmutil.resolvehexnodeidprefix(repo, ha)
499 self.node = scmutil.resolvehexnodeidprefix(repo, ha)
500 if self.node is None:
500 if self.node is None:
501 raise error.ParseError(_(b'unknown changeset %s listed') % ha[:12])
501 raise error.ParseError(_(b'unknown changeset %s listed') % ha[:12])
502 self._verifynodeconstraints(prev, expected, seen)
502 self._verifynodeconstraints(prev, expected, seen)
503
503
504 def _verifynodeconstraints(self, prev, expected, seen):
504 def _verifynodeconstraints(self, prev, expected, seen):
505 # by default command need a node in the edited list
505 # by default command need a node in the edited list
506 if self.node not in expected:
506 if self.node not in expected:
507 raise error.ParseError(
507 raise error.ParseError(
508 _(b'%s "%s" changeset was not a candidate')
508 _(b'%s "%s" changeset was not a candidate')
509 % (self.verb, node.short(self.node)),
509 % (self.verb, node.short(self.node)),
510 hint=_(b'only use listed changesets'),
510 hint=_(b'only use listed changesets'),
511 )
511 )
512 # and only one command per node
512 # and only one command per node
513 if self.node in seen:
513 if self.node in seen:
514 raise error.ParseError(
514 raise error.ParseError(
515 _(b'duplicated command for changeset %s')
515 _(b'duplicated command for changeset %s')
516 % node.short(self.node)
516 % node.short(self.node)
517 )
517 )
518
518
519 def torule(self):
519 def torule(self):
520 """build a histedit rule line for an action
520 """build a histedit rule line for an action
521
521
522 by default lines are in the form:
522 by default lines are in the form:
523 <hash> <rev> <summary>
523 <hash> <rev> <summary>
524 """
524 """
525 ctx = self.repo[self.node]
525 ctx = self.repo[self.node]
526 ui = self.repo.ui
526 ui = self.repo.ui
527 summary = (
527 summary = (
528 cmdutil.rendertemplate(
528 cmdutil.rendertemplate(
529 ctx, ui.config(b'histedit', b'summary-template')
529 ctx, ui.config(b'histedit', b'summary-template')
530 )
530 )
531 or b''
531 or b''
532 )
532 )
533 summary = summary.splitlines()[0]
533 summary = summary.splitlines()[0]
534 line = b'%s %s %s' % (self.verb, ctx, summary)
534 line = b'%s %s %s' % (self.verb, ctx, summary)
535 # trim to 75 columns by default so it's not stupidly wide in my editor
535 # trim to 75 columns by default so it's not stupidly wide in my editor
536 # (the 5 more are left for verb)
536 # (the 5 more are left for verb)
537 maxlen = self.repo.ui.configint(b'histedit', b'linelen')
537 maxlen = self.repo.ui.configint(b'histedit', b'linelen')
538 maxlen = max(maxlen, 22) # avoid truncating hash
538 maxlen = max(maxlen, 22) # avoid truncating hash
539 return stringutil.ellipsis(line, maxlen)
539 return stringutil.ellipsis(line, maxlen)
540
540
541 def tostate(self):
541 def tostate(self):
542 """Print an action in format used by histedit state files
542 """Print an action in format used by histedit state files
543 (the first line is a verb, the remainder is the second)
543 (the first line is a verb, the remainder is the second)
544 """
544 """
545 return b"%s\n%s" % (self.verb, node.hex(self.node))
545 return b"%s\n%s" % (self.verb, node.hex(self.node))
546
546
547 def run(self):
547 def run(self):
548 """Runs the action. The default behavior is simply apply the action's
548 """Runs the action. The default behavior is simply apply the action's
549 rulectx onto the current parentctx."""
549 rulectx onto the current parentctx."""
550 self.applychange()
550 self.applychange()
551 self.continuedirty()
551 self.continuedirty()
552 return self.continueclean()
552 return self.continueclean()
553
553
554 def applychange(self):
554 def applychange(self):
555 """Applies the changes from this action's rulectx onto the current
555 """Applies the changes from this action's rulectx onto the current
556 parentctx, but does not commit them."""
556 parentctx, but does not commit them."""
557 repo = self.repo
557 repo = self.repo
558 rulectx = repo[self.node]
558 rulectx = repo[self.node]
559 repo.ui.pushbuffer(error=True, labeled=True)
559 repo.ui.pushbuffer(error=True, labeled=True)
560 hg.update(repo, self.state.parentctxnode, quietempty=True)
560 hg.update(repo, self.state.parentctxnode, quietempty=True)
561 repo.ui.popbuffer()
561 repo.ui.popbuffer()
562 stats = applychanges(repo.ui, repo, rulectx, {})
562 stats = applychanges(repo.ui, repo, rulectx, {})
563 repo.dirstate.setbranch(rulectx.branch())
563 repo.dirstate.setbranch(rulectx.branch())
564 if stats.unresolvedcount:
564 if stats.unresolvedcount:
565 raise error.InterventionRequired(
565 raise error.InterventionRequired(
566 _(b'Fix up the change (%s %s)')
566 _(b'Fix up the change (%s %s)')
567 % (self.verb, node.short(self.node)),
567 % (self.verb, node.short(self.node)),
568 hint=_(b'hg histedit --continue to resume'),
568 hint=_(b'hg histedit --continue to resume'),
569 )
569 )
570
570
571 def continuedirty(self):
571 def continuedirty(self):
572 """Continues the action when changes have been applied to the working
572 """Continues the action when changes have been applied to the working
573 copy. The default behavior is to commit the dirty changes."""
573 copy. The default behavior is to commit the dirty changes."""
574 repo = self.repo
574 repo = self.repo
575 rulectx = repo[self.node]
575 rulectx = repo[self.node]
576
576
577 editor = self.commiteditor()
577 editor = self.commiteditor()
578 commit = commitfuncfor(repo, rulectx)
578 commit = commitfuncfor(repo, rulectx)
579 if repo.ui.configbool(b'rewrite', b'update-timestamp'):
579 if repo.ui.configbool(b'rewrite', b'update-timestamp'):
580 date = dateutil.makedate()
580 date = dateutil.makedate()
581 else:
581 else:
582 date = rulectx.date()
582 date = rulectx.date()
583 commit(
583 commit(
584 text=rulectx.description(),
584 text=rulectx.description(),
585 user=rulectx.user(),
585 user=rulectx.user(),
586 date=date,
586 date=date,
587 extra=rulectx.extra(),
587 extra=rulectx.extra(),
588 editor=editor,
588 editor=editor,
589 )
589 )
590
590
591 def commiteditor(self):
591 def commiteditor(self):
592 """The editor to be used to edit the commit message."""
592 """The editor to be used to edit the commit message."""
593 return False
593 return False
594
594
595 def continueclean(self):
595 def continueclean(self):
596 """Continues the action when the working copy is clean. The default
596 """Continues the action when the working copy is clean. The default
597 behavior is to accept the current commit as the new version of the
597 behavior is to accept the current commit as the new version of the
598 rulectx."""
598 rulectx."""
599 ctx = self.repo[b'.']
599 ctx = self.repo[b'.']
600 if ctx.node() == self.state.parentctxnode:
600 if ctx.node() == self.state.parentctxnode:
601 self.repo.ui.warn(
601 self.repo.ui.warn(
602 _(b'%s: skipping changeset (no changes)\n')
602 _(b'%s: skipping changeset (no changes)\n')
603 % node.short(self.node)
603 % node.short(self.node)
604 )
604 )
605 return ctx, [(self.node, tuple())]
605 return ctx, [(self.node, tuple())]
606 if ctx.node() == self.node:
606 if ctx.node() == self.node:
607 # Nothing changed
607 # Nothing changed
608 return ctx, []
608 return ctx, []
609 return ctx, [(self.node, (ctx.node(),))]
609 return ctx, [(self.node, (ctx.node(),))]
610
610
611
611
612 def commitfuncfor(repo, src):
612 def commitfuncfor(repo, src):
613 """Build a commit function for the replacement of <src>
613 """Build a commit function for the replacement of <src>
614
614
615 This function ensure we apply the same treatment to all changesets.
615 This function ensure we apply the same treatment to all changesets.
616
616
617 - Add a 'histedit_source' entry in extra.
617 - Add a 'histedit_source' entry in extra.
618
618
619 Note that fold has its own separated logic because its handling is a bit
619 Note that fold has its own separated logic because its handling is a bit
620 different and not easily factored out of the fold method.
620 different and not easily factored out of the fold method.
621 """
621 """
622 phasemin = src.phase()
622 phasemin = src.phase()
623
623
624 def commitfunc(**kwargs):
624 def commitfunc(**kwargs):
625 overrides = {(b'phases', b'new-commit'): phasemin}
625 overrides = {(b'phases', b'new-commit'): phasemin}
626 with repo.ui.configoverride(overrides, b'histedit'):
626 with repo.ui.configoverride(overrides, b'histedit'):
627 extra = kwargs.get('extra', {}).copy()
627 extra = kwargs.get('extra', {}).copy()
628 extra[b'histedit_source'] = src.hex()
628 extra[b'histedit_source'] = src.hex()
629 kwargs['extra'] = extra
629 kwargs['extra'] = extra
630 return repo.commit(**kwargs)
630 return repo.commit(**kwargs)
631
631
632 return commitfunc
632 return commitfunc
633
633
634
634
635 def applychanges(ui, repo, ctx, opts):
635 def applychanges(ui, repo, ctx, opts):
636 """Merge changeset from ctx (only) in the current working directory"""
636 """Merge changeset from ctx (only) in the current working directory"""
637 wcpar = repo.dirstate.p1()
637 wcpar = repo.dirstate.p1()
638 if ctx.p1().node() == wcpar:
638 if ctx.p1().node() == wcpar:
639 # edits are "in place" we do not need to make any merge,
639 # edits are "in place" we do not need to make any merge,
640 # just applies changes on parent for editing
640 # just applies changes on parent for editing
641 ui.pushbuffer()
641 ui.pushbuffer()
642 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
642 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
643 stats = mergemod.updateresult(0, 0, 0, 0)
643 stats = mergemod.updateresult(0, 0, 0, 0)
644 ui.popbuffer()
644 ui.popbuffer()
645 else:
645 else:
646 try:
646 try:
647 # ui.forcemerge is an internal variable, do not document
647 # ui.forcemerge is an internal variable, do not document
648 repo.ui.setconfig(
648 repo.ui.setconfig(
649 b'ui', b'forcemerge', opts.get(b'tool', b''), b'histedit'
649 b'ui', b'forcemerge', opts.get(b'tool', b''), b'histedit'
650 )
650 )
651 stats = mergemod.graft(repo, ctx, ctx.p1(), [b'local', b'histedit'])
651 stats = mergemod.graft(repo, ctx, ctx.p1(), [b'local', b'histedit'])
652 finally:
652 finally:
653 repo.ui.setconfig(b'ui', b'forcemerge', b'', b'histedit')
653 repo.ui.setconfig(b'ui', b'forcemerge', b'', b'histedit')
654 return stats
654 return stats
655
655
656
656
657 def collapse(repo, firstctx, lastctx, commitopts, skipprompt=False):
657 def collapse(repo, firstctx, lastctx, commitopts, skipprompt=False):
658 """collapse the set of revisions from first to last as new one.
658 """collapse the set of revisions from first to last as new one.
659
659
660 Expected commit options are:
660 Expected commit options are:
661 - message
661 - message
662 - date
662 - date
663 - username
663 - username
664 Commit message is edited in all cases.
664 Commit message is edited in all cases.
665
665
666 This function works in memory."""
666 This function works in memory."""
667 ctxs = list(repo.set(b'%d::%d', firstctx.rev(), lastctx.rev()))
667 ctxs = list(repo.set(b'%d::%d', firstctx.rev(), lastctx.rev()))
668 if not ctxs:
668 if not ctxs:
669 return None
669 return None
670 for c in ctxs:
670 for c in ctxs:
671 if not c.mutable():
671 if not c.mutable():
672 raise error.ParseError(
672 raise error.ParseError(
673 _(b"cannot fold into public change %s") % node.short(c.node())
673 _(b"cannot fold into public change %s") % node.short(c.node())
674 )
674 )
675 base = firstctx.p1()
675 base = firstctx.p1()
676
676
677 # commit a new version of the old changeset, including the update
677 # commit a new version of the old changeset, including the update
678 # collect all files which might be affected
678 # collect all files which might be affected
679 files = set()
679 files = set()
680 for ctx in ctxs:
680 for ctx in ctxs:
681 files.update(ctx.files())
681 files.update(ctx.files())
682
682
683 # Recompute copies (avoid recording a -> b -> a)
683 # Recompute copies (avoid recording a -> b -> a)
684 copied = copies.pathcopies(base, lastctx)
684 copied = copies.pathcopies(base, lastctx)
685
685
686 # prune files which were reverted by the updates
686 # prune files which were reverted by the updates
687 files = [f for f in files if not cmdutil.samefile(f, lastctx, base)]
687 files = [f for f in files if not cmdutil.samefile(f, lastctx, base)]
688 # commit version of these files as defined by head
688 # commit version of these files as defined by head
689 headmf = lastctx.manifest()
689 headmf = lastctx.manifest()
690
690
691 def filectxfn(repo, ctx, path):
691 def filectxfn(repo, ctx, path):
692 if path in headmf:
692 if path in headmf:
693 fctx = lastctx[path]
693 fctx = lastctx[path]
694 flags = fctx.flags()
694 flags = fctx.flags()
695 mctx = context.memfilectx(
695 mctx = context.memfilectx(
696 repo,
696 repo,
697 ctx,
697 ctx,
698 fctx.path(),
698 fctx.path(),
699 fctx.data(),
699 fctx.data(),
700 islink=b'l' in flags,
700 islink=b'l' in flags,
701 isexec=b'x' in flags,
701 isexec=b'x' in flags,
702 copysource=copied.get(path),
702 copysource=copied.get(path),
703 )
703 )
704 return mctx
704 return mctx
705 return None
705 return None
706
706
707 if commitopts.get(b'message'):
707 if commitopts.get(b'message'):
708 message = commitopts[b'message']
708 message = commitopts[b'message']
709 else:
709 else:
710 message = firstctx.description()
710 message = firstctx.description()
711 user = commitopts.get(b'user')
711 user = commitopts.get(b'user')
712 date = commitopts.get(b'date')
712 date = commitopts.get(b'date')
713 extra = commitopts.get(b'extra')
713 extra = commitopts.get(b'extra')
714
714
715 parents = (firstctx.p1().node(), firstctx.p2().node())
715 parents = (firstctx.p1().node(), firstctx.p2().node())
716 editor = None
716 editor = None
717 if not skipprompt:
717 if not skipprompt:
718 editor = cmdutil.getcommiteditor(edit=True, editform=b'histedit.fold')
718 editor = cmdutil.getcommiteditor(edit=True, editform=b'histedit.fold')
719 new = context.memctx(
719 new = context.memctx(
720 repo,
720 repo,
721 parents=parents,
721 parents=parents,
722 text=message,
722 text=message,
723 files=files,
723 files=files,
724 filectxfn=filectxfn,
724 filectxfn=filectxfn,
725 user=user,
725 user=user,
726 date=date,
726 date=date,
727 extra=extra,
727 extra=extra,
728 editor=editor,
728 editor=editor,
729 )
729 )
730 return repo.commitctx(new)
730 return repo.commitctx(new)
731
731
732
732
733 def _isdirtywc(repo):
733 def _isdirtywc(repo):
734 return repo[None].dirty(missing=True)
734 return repo[None].dirty(missing=True)
735
735
736
736
737 def abortdirty():
737 def abortdirty():
738 raise error.Abort(
738 raise error.Abort(
739 _(b'working copy has pending changes'),
739 _(b'working copy has pending changes'),
740 hint=_(
740 hint=_(
741 b'amend, commit, or revert them and run histedit '
741 b'amend, commit, or revert them and run histedit '
742 b'--continue, or abort with histedit --abort'
742 b'--continue, or abort with histedit --abort'
743 ),
743 ),
744 )
744 )
745
745
746
746
747 def action(verbs, message, priority=False, internal=False):
747 def action(verbs, message, priority=False, internal=False):
748 def wrap(cls):
748 def wrap(cls):
749 assert not priority or not internal
749 assert not priority or not internal
750 verb = verbs[0]
750 verb = verbs[0]
751 if priority:
751 if priority:
752 primaryactions.add(verb)
752 primaryactions.add(verb)
753 elif internal:
753 elif internal:
754 internalactions.add(verb)
754 internalactions.add(verb)
755 elif len(verbs) > 1:
755 elif len(verbs) > 1:
756 secondaryactions.add(verb)
756 secondaryactions.add(verb)
757 else:
757 else:
758 tertiaryactions.add(verb)
758 tertiaryactions.add(verb)
759
759
760 cls.verb = verb
760 cls.verb = verb
761 cls.verbs = verbs
761 cls.verbs = verbs
762 cls.message = message
762 cls.message = message
763 for verb in verbs:
763 for verb in verbs:
764 actiontable[verb] = cls
764 actiontable[verb] = cls
765 return cls
765 return cls
766
766
767 return wrap
767 return wrap
768
768
769
769
770 @action([b'pick', b'p'], _(b'use commit'), priority=True)
770 @action([b'pick', b'p'], _(b'use commit'), priority=True)
771 class pick(histeditaction):
771 class pick(histeditaction):
772 def run(self):
772 def run(self):
773 rulectx = self.repo[self.node]
773 rulectx = self.repo[self.node]
774 if rulectx.p1().node() == self.state.parentctxnode:
774 if rulectx.p1().node() == self.state.parentctxnode:
775 self.repo.ui.debug(b'node %s unchanged\n' % node.short(self.node))
775 self.repo.ui.debug(b'node %s unchanged\n' % node.short(self.node))
776 return rulectx, []
776 return rulectx, []
777
777
778 return super(pick, self).run()
778 return super(pick, self).run()
779
779
780
780
781 @action([b'edit', b'e'], _(b'use commit, but stop for amending'), priority=True)
781 @action([b'edit', b'e'], _(b'use commit, but stop for amending'), priority=True)
782 class edit(histeditaction):
782 class edit(histeditaction):
783 def run(self):
783 def run(self):
784 repo = self.repo
784 repo = self.repo
785 rulectx = repo[self.node]
785 rulectx = repo[self.node]
786 hg.update(repo, self.state.parentctxnode, quietempty=True)
786 hg.update(repo, self.state.parentctxnode, quietempty=True)
787 applychanges(repo.ui, repo, rulectx, {})
787 applychanges(repo.ui, repo, rulectx, {})
788 raise error.InterventionRequired(
788 raise error.InterventionRequired(
789 _(b'Editing (%s), you may commit or record as needed now.')
789 _(b'Editing (%s), you may commit or record as needed now.')
790 % node.short(self.node),
790 % node.short(self.node),
791 hint=_(b'hg histedit --continue to resume'),
791 hint=_(b'hg histedit --continue to resume'),
792 )
792 )
793
793
794 def commiteditor(self):
794 def commiteditor(self):
795 return cmdutil.getcommiteditor(edit=True, editform=b'histedit.edit')
795 return cmdutil.getcommiteditor(edit=True, editform=b'histedit.edit')
796
796
797
797
798 @action([b'fold', b'f'], _(b'use commit, but combine it with the one above'))
798 @action([b'fold', b'f'], _(b'use commit, but combine it with the one above'))
799 class fold(histeditaction):
799 class fold(histeditaction):
800 def verify(self, prev, expected, seen):
800 def verify(self, prev, expected, seen):
801 """ Verifies semantic correctness of the fold rule"""
801 """ Verifies semantic correctness of the fold rule"""
802 super(fold, self).verify(prev, expected, seen)
802 super(fold, self).verify(prev, expected, seen)
803 repo = self.repo
803 repo = self.repo
804 if not prev:
804 if not prev:
805 c = repo[self.node].p1()
805 c = repo[self.node].p1()
806 elif not prev.verb in (b'pick', b'base'):
806 elif not prev.verb in (b'pick', b'base'):
807 return
807 return
808 else:
808 else:
809 c = repo[prev.node]
809 c = repo[prev.node]
810 if not c.mutable():
810 if not c.mutable():
811 raise error.ParseError(
811 raise error.ParseError(
812 _(b"cannot fold into public change %s") % node.short(c.node())
812 _(b"cannot fold into public change %s") % node.short(c.node())
813 )
813 )
814
814
815 def continuedirty(self):
815 def continuedirty(self):
816 repo = self.repo
816 repo = self.repo
817 rulectx = repo[self.node]
817 rulectx = repo[self.node]
818
818
819 commit = commitfuncfor(repo, rulectx)
819 commit = commitfuncfor(repo, rulectx)
820 commit(
820 commit(
821 text=b'fold-temp-revision %s' % node.short(self.node),
821 text=b'fold-temp-revision %s' % node.short(self.node),
822 user=rulectx.user(),
822 user=rulectx.user(),
823 date=rulectx.date(),
823 date=rulectx.date(),
824 extra=rulectx.extra(),
824 extra=rulectx.extra(),
825 )
825 )
826
826
827 def continueclean(self):
827 def continueclean(self):
828 repo = self.repo
828 repo = self.repo
829 ctx = repo[b'.']
829 ctx = repo[b'.']
830 rulectx = repo[self.node]
830 rulectx = repo[self.node]
831 parentctxnode = self.state.parentctxnode
831 parentctxnode = self.state.parentctxnode
832 if ctx.node() == parentctxnode:
832 if ctx.node() == parentctxnode:
833 repo.ui.warn(_(b'%s: empty changeset\n') % node.short(self.node))
833 repo.ui.warn(_(b'%s: empty changeset\n') % node.short(self.node))
834 return ctx, [(self.node, (parentctxnode,))]
834 return ctx, [(self.node, (parentctxnode,))]
835
835
836 parentctx = repo[parentctxnode]
836 parentctx = repo[parentctxnode]
837 newcommits = set(
837 newcommits = set(
838 c.node()
838 c.node()
839 for c in repo.set(b'(%d::. - %d)', parentctx.rev(), parentctx.rev())
839 for c in repo.set(b'(%d::. - %d)', parentctx.rev(), parentctx.rev())
840 )
840 )
841 if not newcommits:
841 if not newcommits:
842 repo.ui.warn(
842 repo.ui.warn(
843 _(
843 _(
844 b'%s: cannot fold - working copy is not a '
844 b'%s: cannot fold - working copy is not a '
845 b'descendant of previous commit %s\n'
845 b'descendant of previous commit %s\n'
846 )
846 )
847 % (node.short(self.node), node.short(parentctxnode))
847 % (node.short(self.node), node.short(parentctxnode))
848 )
848 )
849 return ctx, [(self.node, (ctx.node(),))]
849 return ctx, [(self.node, (ctx.node(),))]
850
850
851 middlecommits = newcommits.copy()
851 middlecommits = newcommits.copy()
852 middlecommits.discard(ctx.node())
852 middlecommits.discard(ctx.node())
853
853
854 return self.finishfold(
854 return self.finishfold(
855 repo.ui, repo, parentctx, rulectx, ctx.node(), middlecommits
855 repo.ui, repo, parentctx, rulectx, ctx.node(), middlecommits
856 )
856 )
857
857
858 def skipprompt(self):
858 def skipprompt(self):
859 """Returns true if the rule should skip the message editor.
859 """Returns true if the rule should skip the message editor.
860
860
861 For example, 'fold' wants to show an editor, but 'rollup'
861 For example, 'fold' wants to show an editor, but 'rollup'
862 doesn't want to.
862 doesn't want to.
863 """
863 """
864 return False
864 return False
865
865
866 def mergedescs(self):
866 def mergedescs(self):
867 """Returns true if the rule should merge messages of multiple changes.
867 """Returns true if the rule should merge messages of multiple changes.
868
868
869 This exists mainly so that 'rollup' rules can be a subclass of
869 This exists mainly so that 'rollup' rules can be a subclass of
870 'fold'.
870 'fold'.
871 """
871 """
872 return True
872 return True
873
873
874 def firstdate(self):
874 def firstdate(self):
875 """Returns true if the rule should preserve the date of the first
875 """Returns true if the rule should preserve the date of the first
876 change.
876 change.
877
877
878 This exists mainly so that 'rollup' rules can be a subclass of
878 This exists mainly so that 'rollup' rules can be a subclass of
879 'fold'.
879 'fold'.
880 """
880 """
881 return False
881 return False
882
882
883 def finishfold(self, ui, repo, ctx, oldctx, newnode, internalchanges):
883 def finishfold(self, ui, repo, ctx, oldctx, newnode, internalchanges):
884 parent = ctx.p1().node()
884 parent = ctx.p1().node()
885 hg.updaterepo(repo, parent, overwrite=False)
885 hg.updaterepo(repo, parent, overwrite=False)
886 ### prepare new commit data
886 ### prepare new commit data
887 commitopts = {}
887 commitopts = {}
888 commitopts[b'user'] = ctx.user()
888 commitopts[b'user'] = ctx.user()
889 # commit message
889 # commit message
890 if not self.mergedescs():
890 if not self.mergedescs():
891 newmessage = ctx.description()
891 newmessage = ctx.description()
892 else:
892 else:
893 newmessage = (
893 newmessage = (
894 b'\n***\n'.join(
894 b'\n***\n'.join(
895 [ctx.description()]
895 [ctx.description()]
896 + [repo[r].description() for r in internalchanges]
896 + [repo[r].description() for r in internalchanges]
897 + [oldctx.description()]
897 + [oldctx.description()]
898 )
898 )
899 + b'\n'
899 + b'\n'
900 )
900 )
901 commitopts[b'message'] = newmessage
901 commitopts[b'message'] = newmessage
902 # date
902 # date
903 if self.firstdate():
903 if self.firstdate():
904 commitopts[b'date'] = ctx.date()
904 commitopts[b'date'] = ctx.date()
905 else:
905 else:
906 commitopts[b'date'] = max(ctx.date(), oldctx.date())
906 commitopts[b'date'] = max(ctx.date(), oldctx.date())
907 # if date is to be updated to current
907 # if date is to be updated to current
908 if ui.configbool(b'rewrite', b'update-timestamp'):
908 if ui.configbool(b'rewrite', b'update-timestamp'):
909 commitopts[b'date'] = dateutil.makedate()
909 commitopts[b'date'] = dateutil.makedate()
910
910
911 extra = ctx.extra().copy()
911 extra = ctx.extra().copy()
912 # histedit_source
912 # histedit_source
913 # note: ctx is likely a temporary commit but that the best we can do
913 # note: ctx is likely a temporary commit but that the best we can do
914 # here. This is sufficient to solve issue3681 anyway.
914 # here. This is sufficient to solve issue3681 anyway.
915 extra[b'histedit_source'] = b'%s,%s' % (ctx.hex(), oldctx.hex())
915 extra[b'histedit_source'] = b'%s,%s' % (ctx.hex(), oldctx.hex())
916 commitopts[b'extra'] = extra
916 commitopts[b'extra'] = extra
917 phasemin = max(ctx.phase(), oldctx.phase())
917 phasemin = max(ctx.phase(), oldctx.phase())
918 overrides = {(b'phases', b'new-commit'): phasemin}
918 overrides = {(b'phases', b'new-commit'): phasemin}
919 with repo.ui.configoverride(overrides, b'histedit'):
919 with repo.ui.configoverride(overrides, b'histedit'):
920 n = collapse(
920 n = collapse(
921 repo,
921 repo,
922 ctx,
922 ctx,
923 repo[newnode],
923 repo[newnode],
924 commitopts,
924 commitopts,
925 skipprompt=self.skipprompt(),
925 skipprompt=self.skipprompt(),
926 )
926 )
927 if n is None:
927 if n is None:
928 return ctx, []
928 return ctx, []
929 hg.updaterepo(repo, n, overwrite=False)
929 hg.updaterepo(repo, n, overwrite=False)
930 replacements = [
930 replacements = [
931 (oldctx.node(), (newnode,)),
931 (oldctx.node(), (newnode,)),
932 (ctx.node(), (n,)),
932 (ctx.node(), (n,)),
933 (newnode, (n,)),
933 (newnode, (n,)),
934 ]
934 ]
935 for ich in internalchanges:
935 for ich in internalchanges:
936 replacements.append((ich, (n,)))
936 replacements.append((ich, (n,)))
937 return repo[n], replacements
937 return repo[n], replacements
938
938
939
939
940 @action(
940 @action(
941 [b'base', b'b'],
941 [b'base', b'b'],
942 _(b'checkout changeset and apply further changesets from there'),
942 _(b'checkout changeset and apply further changesets from there'),
943 )
943 )
944 class base(histeditaction):
944 class base(histeditaction):
945 def run(self):
945 def run(self):
946 if self.repo[b'.'].node() != self.node:
946 if self.repo[b'.'].node() != self.node:
947 mergemod.update(self.repo, self.node, branchmerge=False, force=True)
947 mergemod.update(self.repo, self.node, branchmerge=False, force=True)
948 return self.continueclean()
948 return self.continueclean()
949
949
950 def continuedirty(self):
950 def continuedirty(self):
951 abortdirty()
951 abortdirty()
952
952
953 def continueclean(self):
953 def continueclean(self):
954 basectx = self.repo[b'.']
954 basectx = self.repo[b'.']
955 return basectx, []
955 return basectx, []
956
956
957 def _verifynodeconstraints(self, prev, expected, seen):
957 def _verifynodeconstraints(self, prev, expected, seen):
958 # base can only be use with a node not in the edited set
958 # base can only be use with a node not in the edited set
959 if self.node in expected:
959 if self.node in expected:
960 msg = _(b'%s "%s" changeset was an edited list candidate')
960 msg = _(b'%s "%s" changeset was an edited list candidate')
961 raise error.ParseError(
961 raise error.ParseError(
962 msg % (self.verb, node.short(self.node)),
962 msg % (self.verb, node.short(self.node)),
963 hint=_(b'base must only use unlisted changesets'),
963 hint=_(b'base must only use unlisted changesets'),
964 )
964 )
965
965
966
966
967 @action(
967 @action(
968 [b'_multifold'],
968 [b'_multifold'],
969 _(
969 _(
970 """fold subclass used for when multiple folds happen in a row
970 """fold subclass used for when multiple folds happen in a row
971
971
972 We only want to fire the editor for the folded message once when
972 We only want to fire the editor for the folded message once when
973 (say) four changes are folded down into a single change. This is
973 (say) four changes are folded down into a single change. This is
974 similar to rollup, but we should preserve both messages so that
974 similar to rollup, but we should preserve both messages so that
975 when the last fold operation runs we can show the user all the
975 when the last fold operation runs we can show the user all the
976 commit messages in their editor.
976 commit messages in their editor.
977 """
977 """
978 ),
978 ),
979 internal=True,
979 internal=True,
980 )
980 )
981 class _multifold(fold):
981 class _multifold(fold):
982 def skipprompt(self):
982 def skipprompt(self):
983 return True
983 return True
984
984
985
985
986 @action(
986 @action(
987 [b"roll", b"r"],
987 [b"roll", b"r"],
988 _(b"like fold, but discard this commit's description and date"),
988 _(b"like fold, but discard this commit's description and date"),
989 )
989 )
990 class rollup(fold):
990 class rollup(fold):
991 def mergedescs(self):
991 def mergedescs(self):
992 return False
992 return False
993
993
994 def skipprompt(self):
994 def skipprompt(self):
995 return True
995 return True
996
996
997 def firstdate(self):
997 def firstdate(self):
998 return True
998 return True
999
999
1000
1000
1001 @action([b"drop", b"d"], _(b'remove commit from history'))
1001 @action([b"drop", b"d"], _(b'remove commit from history'))
1002 class drop(histeditaction):
1002 class drop(histeditaction):
1003 def run(self):
1003 def run(self):
1004 parentctx = self.repo[self.state.parentctxnode]
1004 parentctx = self.repo[self.state.parentctxnode]
1005 return parentctx, [(self.node, tuple())]
1005 return parentctx, [(self.node, tuple())]
1006
1006
1007
1007
1008 @action(
1008 @action(
1009 [b"mess", b"m"],
1009 [b"mess", b"m"],
1010 _(b'edit commit message without changing commit content'),
1010 _(b'edit commit message without changing commit content'),
1011 priority=True,
1011 priority=True,
1012 )
1012 )
1013 class message(histeditaction):
1013 class message(histeditaction):
1014 def commiteditor(self):
1014 def commiteditor(self):
1015 return cmdutil.getcommiteditor(edit=True, editform=b'histedit.mess')
1015 return cmdutil.getcommiteditor(edit=True, editform=b'histedit.mess')
1016
1016
1017
1017
1018 def findoutgoing(ui, repo, remote=None, force=False, opts=None):
1018 def findoutgoing(ui, repo, remote=None, force=False, opts=None):
1019 """utility function to find the first outgoing changeset
1019 """utility function to find the first outgoing changeset
1020
1020
1021 Used by initialization code"""
1021 Used by initialization code"""
1022 if opts is None:
1022 if opts is None:
1023 opts = {}
1023 opts = {}
1024 dest = ui.expandpath(remote or b'default-push', remote or b'default')
1024 dest = ui.expandpath(remote or b'default-push', remote or b'default')
1025 dest, branches = hg.parseurl(dest, None)[:2]
1025 dest, branches = hg.parseurl(dest, None)[:2]
1026 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
1026 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
1027
1027
1028 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
1028 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
1029 other = hg.peer(repo, opts, dest)
1029 other = hg.peer(repo, opts, dest)
1030
1030
1031 if revs:
1031 if revs:
1032 revs = [repo.lookup(rev) for rev in revs]
1032 revs = [repo.lookup(rev) for rev in revs]
1033
1033
1034 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
1034 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
1035 if not outgoing.missing:
1035 if not outgoing.missing:
1036 raise error.Abort(_(b'no outgoing ancestors'))
1036 raise error.Abort(_(b'no outgoing ancestors'))
1037 roots = list(repo.revs(b"roots(%ln)", outgoing.missing))
1037 roots = list(repo.revs(b"roots(%ln)", outgoing.missing))
1038 if len(roots) > 1:
1038 if len(roots) > 1:
1039 msg = _(b'there are ambiguous outgoing revisions')
1039 msg = _(b'there are ambiguous outgoing revisions')
1040 hint = _(b"see 'hg help histedit' for more detail")
1040 hint = _(b"see 'hg help histedit' for more detail")
1041 raise error.Abort(msg, hint=hint)
1041 raise error.Abort(msg, hint=hint)
1042 return repo[roots[0]].node()
1042 return repo[roots[0]].node()
1043
1043
1044
1044
1045 # Curses Support
1045 # Curses Support
1046 try:
1046 try:
1047 import curses
1047 import curses
1048 except ImportError:
1048 except ImportError:
1049 curses = None
1049 curses = None
1050
1050
1051 KEY_LIST = [b'pick', b'edit', b'fold', b'drop', b'mess', b'roll']
1051 KEY_LIST = [b'pick', b'edit', b'fold', b'drop', b'mess', b'roll']
1052 ACTION_LABELS = {
1052 ACTION_LABELS = {
1053 b'fold': b'^fold',
1053 b'fold': b'^fold',
1054 b'roll': b'^roll',
1054 b'roll': b'^roll',
1055 }
1055 }
1056
1056
1057 COLOR_HELP, COLOR_SELECTED, COLOR_OK, COLOR_WARN, COLOR_CURRENT = 1, 2, 3, 4, 5
1057 COLOR_HELP, COLOR_SELECTED, COLOR_OK, COLOR_WARN, COLOR_CURRENT = 1, 2, 3, 4, 5
1058 COLOR_DIFF_ADD_LINE, COLOR_DIFF_DEL_LINE, COLOR_DIFF_OFFSET = 6, 7, 8
1058 COLOR_DIFF_ADD_LINE, COLOR_DIFF_DEL_LINE, COLOR_DIFF_OFFSET = 6, 7, 8
1059 COLOR_ROLL, COLOR_ROLL_CURRENT, COLOR_ROLL_SELECTED = 9, 10, 11
1059 COLOR_ROLL, COLOR_ROLL_CURRENT, COLOR_ROLL_SELECTED = 9, 10, 11
1060
1060
1061 E_QUIT, E_HISTEDIT = 1, 2
1061 E_QUIT, E_HISTEDIT = 1, 2
1062 E_PAGEDOWN, E_PAGEUP, E_LINEUP, E_LINEDOWN, E_RESIZE = 3, 4, 5, 6, 7
1062 E_PAGEDOWN, E_PAGEUP, E_LINEUP, E_LINEDOWN, E_RESIZE = 3, 4, 5, 6, 7
1063 MODE_INIT, MODE_PATCH, MODE_RULES, MODE_HELP = 0, 1, 2, 3
1063 MODE_INIT, MODE_PATCH, MODE_RULES, MODE_HELP = 0, 1, 2, 3
1064
1064
1065 KEYTABLE = {
1065 KEYTABLE = {
1066 b'global': {
1066 b'global': {
1067 b'h': b'next-action',
1067 b'h': b'next-action',
1068 b'KEY_RIGHT': b'next-action',
1068 b'KEY_RIGHT': b'next-action',
1069 b'l': b'prev-action',
1069 b'l': b'prev-action',
1070 b'KEY_LEFT': b'prev-action',
1070 b'KEY_LEFT': b'prev-action',
1071 b'q': b'quit',
1071 b'q': b'quit',
1072 b'c': b'histedit',
1072 b'c': b'histedit',
1073 b'C': b'histedit',
1073 b'C': b'histedit',
1074 b'v': b'showpatch',
1074 b'v': b'showpatch',
1075 b'?': b'help',
1075 b'?': b'help',
1076 },
1076 },
1077 MODE_RULES: {
1077 MODE_RULES: {
1078 b'd': b'action-drop',
1078 b'd': b'action-drop',
1079 b'e': b'action-edit',
1079 b'e': b'action-edit',
1080 b'f': b'action-fold',
1080 b'f': b'action-fold',
1081 b'm': b'action-mess',
1081 b'm': b'action-mess',
1082 b'p': b'action-pick',
1082 b'p': b'action-pick',
1083 b'r': b'action-roll',
1083 b'r': b'action-roll',
1084 b' ': b'select',
1084 b' ': b'select',
1085 b'j': b'down',
1085 b'j': b'down',
1086 b'k': b'up',
1086 b'k': b'up',
1087 b'KEY_DOWN': b'down',
1087 b'KEY_DOWN': b'down',
1088 b'KEY_UP': b'up',
1088 b'KEY_UP': b'up',
1089 b'J': b'move-down',
1089 b'J': b'move-down',
1090 b'K': b'move-up',
1090 b'K': b'move-up',
1091 b'KEY_NPAGE': b'move-down',
1091 b'KEY_NPAGE': b'move-down',
1092 b'KEY_PPAGE': b'move-up',
1092 b'KEY_PPAGE': b'move-up',
1093 b'0': b'goto', # Used for 0..9
1093 b'0': b'goto', # Used for 0..9
1094 },
1094 },
1095 MODE_PATCH: {
1095 MODE_PATCH: {
1096 b' ': b'page-down',
1096 b' ': b'page-down',
1097 b'KEY_NPAGE': b'page-down',
1097 b'KEY_NPAGE': b'page-down',
1098 b'KEY_PPAGE': b'page-up',
1098 b'KEY_PPAGE': b'page-up',
1099 b'j': b'line-down',
1099 b'j': b'line-down',
1100 b'k': b'line-up',
1100 b'k': b'line-up',
1101 b'KEY_DOWN': b'line-down',
1101 b'KEY_DOWN': b'line-down',
1102 b'KEY_UP': b'line-up',
1102 b'KEY_UP': b'line-up',
1103 b'J': b'down',
1103 b'J': b'down',
1104 b'K': b'up',
1104 b'K': b'up',
1105 },
1105 },
1106 MODE_HELP: {},
1106 MODE_HELP: {},
1107 }
1107 }
1108
1108
1109
1109
1110 def screen_size():
1110 def screen_size():
1111 return struct.unpack(b'hh', fcntl.ioctl(1, termios.TIOCGWINSZ, b' '))
1111 return struct.unpack(b'hh', fcntl.ioctl(1, termios.TIOCGWINSZ, b' '))
1112
1112
1113
1113
1114 class histeditrule(object):
1114 class histeditrule(object):
1115 def __init__(self, ctx, pos, action=b'pick'):
1115 def __init__(self, ctx, pos, action=b'pick'):
1116 self.ctx = ctx
1116 self.ctx = ctx
1117 self.action = action
1117 self.action = action
1118 self.origpos = pos
1118 self.origpos = pos
1119 self.pos = pos
1119 self.pos = pos
1120 self.conflicts = []
1120 self.conflicts = []
1121
1121
1122 def __bytes__(self):
1122 def __bytes__(self):
1123 # Example display of several histeditrules:
1123 # Example display of several histeditrules:
1124 #
1124 #
1125 # #10 pick 316392:06a16c25c053 add option to skip tests
1125 # #10 pick 316392:06a16c25c053 add option to skip tests
1126 # #11 ^roll 316393:71313c964cc5 <RED>oops a fixup commit</RED>
1126 # #11 ^roll 316393:71313c964cc5 <RED>oops a fixup commit</RED>
1127 # #12 pick 316394:ab31f3973b0d include mfbt for mozilla-config.h
1127 # #12 pick 316394:ab31f3973b0d include mfbt for mozilla-config.h
1128 # #13 ^fold 316395:14ce5803f4c3 fix warnings
1128 # #13 ^fold 316395:14ce5803f4c3 fix warnings
1129 #
1129 #
1130 # The carets point to the changeset being folded into ("roll this
1130 # The carets point to the changeset being folded into ("roll this
1131 # changeset into the changeset above").
1131 # changeset into the changeset above").
1132 return b'%s%s' % (self.prefix, self.desc)
1132 return b'%s%s' % (self.prefix, self.desc)
1133
1133
1134 __str__ = encoding.strmethod(__bytes__)
1134 __str__ = encoding.strmethod(__bytes__)
1135
1135
1136 @property
1136 @property
1137 def prefix(self):
1137 def prefix(self):
1138 # Some actions ('fold' and 'roll') combine a patch with a
1138 # Some actions ('fold' and 'roll') combine a patch with a
1139 # previous one. Add a marker showing which patch they apply
1139 # previous one. Add a marker showing which patch they apply
1140 # to.
1140 # to.
1141 action = ACTION_LABELS.get(self.action, self.action)
1141 action = ACTION_LABELS.get(self.action, self.action)
1142
1142
1143 h = self.ctx.hex()[0:12]
1143 h = self.ctx.hex()[0:12]
1144 r = self.ctx.rev()
1144 r = self.ctx.rev()
1145
1145
1146 return b"#%s %s %d:%s " % (
1146 return b"#%s %s %d:%s " % (
1147 (b'%d' % self.origpos).ljust(2),
1147 (b'%d' % self.origpos).ljust(2),
1148 action.ljust(6),
1148 action.ljust(6),
1149 r,
1149 r,
1150 h,
1150 h,
1151 )
1151 )
1152
1152
1153 @property
1153 @property
1154 def desc(self):
1154 def desc(self):
1155 # This is split off from the prefix property so that we can
1155 # This is split off from the prefix property so that we can
1156 # separately make the description for 'roll' red (since it
1156 # separately make the description for 'roll' red (since it
1157 # will get discarded).
1157 # will get discarded).
1158 return self.ctx.description().splitlines()[0].strip()
1158 return self.ctx.description().splitlines()[0].strip()
1159
1159
1160 def checkconflicts(self, other):
1160 def checkconflicts(self, other):
1161 if other.pos > self.pos and other.origpos <= self.origpos:
1161 if other.pos > self.pos and other.origpos <= self.origpos:
1162 if set(other.ctx.files()) & set(self.ctx.files()) != set():
1162 if set(other.ctx.files()) & set(self.ctx.files()) != set():
1163 self.conflicts.append(other)
1163 self.conflicts.append(other)
1164 return self.conflicts
1164 return self.conflicts
1165
1165
1166 if other in self.conflicts:
1166 if other in self.conflicts:
1167 self.conflicts.remove(other)
1167 self.conflicts.remove(other)
1168 return self.conflicts
1168 return self.conflicts
1169
1169
1170
1170
1171 # ============ EVENTS ===============
1171 # ============ EVENTS ===============
1172 def movecursor(state, oldpos, newpos):
1172 def movecursor(state, oldpos, newpos):
1173 '''Change the rule/changeset that the cursor is pointing to, regardless of
1173 '''Change the rule/changeset that the cursor is pointing to, regardless of
1174 current mode (you can switch between patches from the view patch window).'''
1174 current mode (you can switch between patches from the view patch window).'''
1175 state[b'pos'] = newpos
1175 state[b'pos'] = newpos
1176
1176
1177 mode, _ = state[b'mode']
1177 mode, _ = state[b'mode']
1178 if mode == MODE_RULES:
1178 if mode == MODE_RULES:
1179 # Scroll through the list by updating the view for MODE_RULES, so that
1179 # Scroll through the list by updating the view for MODE_RULES, so that
1180 # even if we are not currently viewing the rules, switching back will
1180 # even if we are not currently viewing the rules, switching back will
1181 # result in the cursor's rule being visible.
1181 # result in the cursor's rule being visible.
1182 modestate = state[b'modes'][MODE_RULES]
1182 modestate = state[b'modes'][MODE_RULES]
1183 if newpos < modestate[b'line_offset']:
1183 if newpos < modestate[b'line_offset']:
1184 modestate[b'line_offset'] = newpos
1184 modestate[b'line_offset'] = newpos
1185 elif newpos > modestate[b'line_offset'] + state[b'page_height'] - 1:
1185 elif newpos > modestate[b'line_offset'] + state[b'page_height'] - 1:
1186 modestate[b'line_offset'] = newpos - state[b'page_height'] + 1
1186 modestate[b'line_offset'] = newpos - state[b'page_height'] + 1
1187
1187
1188 # Reset the patch view region to the top of the new patch.
1188 # Reset the patch view region to the top of the new patch.
1189 state[b'modes'][MODE_PATCH][b'line_offset'] = 0
1189 state[b'modes'][MODE_PATCH][b'line_offset'] = 0
1190
1190
1191
1191
1192 def changemode(state, mode):
1192 def changemode(state, mode):
1193 curmode, _ = state[b'mode']
1193 curmode, _ = state[b'mode']
1194 state[b'mode'] = (mode, curmode)
1194 state[b'mode'] = (mode, curmode)
1195 if mode == MODE_PATCH:
1195 if mode == MODE_PATCH:
1196 state[b'modes'][MODE_PATCH][b'patchcontents'] = patchcontents(state)
1196 state[b'modes'][MODE_PATCH][b'patchcontents'] = patchcontents(state)
1197
1197
1198
1198
1199 def makeselection(state, pos):
1199 def makeselection(state, pos):
1200 state[b'selected'] = pos
1200 state[b'selected'] = pos
1201
1201
1202
1202
1203 def swap(state, oldpos, newpos):
1203 def swap(state, oldpos, newpos):
1204 """Swap two positions and calculate necessary conflicts in
1204 """Swap two positions and calculate necessary conflicts in
1205 O(|newpos-oldpos|) time"""
1205 O(|newpos-oldpos|) time"""
1206
1206
1207 rules = state[b'rules']
1207 rules = state[b'rules']
1208 assert 0 <= oldpos < len(rules) and 0 <= newpos < len(rules)
1208 assert 0 <= oldpos < len(rules) and 0 <= newpos < len(rules)
1209
1209
1210 rules[oldpos], rules[newpos] = rules[newpos], rules[oldpos]
1210 rules[oldpos], rules[newpos] = rules[newpos], rules[oldpos]
1211
1211
1212 # TODO: swap should not know about histeditrule's internals
1212 # TODO: swap should not know about histeditrule's internals
1213 rules[newpos].pos = newpos
1213 rules[newpos].pos = newpos
1214 rules[oldpos].pos = oldpos
1214 rules[oldpos].pos = oldpos
1215
1215
1216 start = min(oldpos, newpos)
1216 start = min(oldpos, newpos)
1217 end = max(oldpos, newpos)
1217 end = max(oldpos, newpos)
1218 for r in pycompat.xrange(start, end + 1):
1218 for r in pycompat.xrange(start, end + 1):
1219 rules[newpos].checkconflicts(rules[r])
1219 rules[newpos].checkconflicts(rules[r])
1220 rules[oldpos].checkconflicts(rules[r])
1220 rules[oldpos].checkconflicts(rules[r])
1221
1221
1222 if state[b'selected']:
1222 if state[b'selected']:
1223 makeselection(state, newpos)
1223 makeselection(state, newpos)
1224
1224
1225
1225
1226 def changeaction(state, pos, action):
1226 def changeaction(state, pos, action):
1227 """Change the action state on the given position to the new action"""
1227 """Change the action state on the given position to the new action"""
1228 rules = state[b'rules']
1228 rules = state[b'rules']
1229 assert 0 <= pos < len(rules)
1229 assert 0 <= pos < len(rules)
1230 rules[pos].action = action
1230 rules[pos].action = action
1231
1231
1232
1232
1233 def cycleaction(state, pos, next=False):
1233 def cycleaction(state, pos, next=False):
1234 """Changes the action state the next or the previous action from
1234 """Changes the action state the next or the previous action from
1235 the action list"""
1235 the action list"""
1236 rules = state[b'rules']
1236 rules = state[b'rules']
1237 assert 0 <= pos < len(rules)
1237 assert 0 <= pos < len(rules)
1238 current = rules[pos].action
1238 current = rules[pos].action
1239
1239
1240 assert current in KEY_LIST
1240 assert current in KEY_LIST
1241
1241
1242 index = KEY_LIST.index(current)
1242 index = KEY_LIST.index(current)
1243 if next:
1243 if next:
1244 index += 1
1244 index += 1
1245 else:
1245 else:
1246 index -= 1
1246 index -= 1
1247 changeaction(state, pos, KEY_LIST[index % len(KEY_LIST)])
1247 changeaction(state, pos, KEY_LIST[index % len(KEY_LIST)])
1248
1248
1249
1249
1250 def changeview(state, delta, unit):
1250 def changeview(state, delta, unit):
1251 '''Change the region of whatever is being viewed (a patch or the list of
1251 '''Change the region of whatever is being viewed (a patch or the list of
1252 changesets). 'delta' is an amount (+/- 1) and 'unit' is 'page' or 'line'.'''
1252 changesets). 'delta' is an amount (+/- 1) and 'unit' is 'page' or 'line'.'''
1253 mode, _ = state[b'mode']
1253 mode, _ = state[b'mode']
1254 if mode != MODE_PATCH:
1254 if mode != MODE_PATCH:
1255 return
1255 return
1256 mode_state = state[b'modes'][mode]
1256 mode_state = state[b'modes'][mode]
1257 num_lines = len(mode_state[b'patchcontents'])
1257 num_lines = len(mode_state[b'patchcontents'])
1258 page_height = state[b'page_height']
1258 page_height = state[b'page_height']
1259 unit = page_height if unit == b'page' else 1
1259 unit = page_height if unit == b'page' else 1
1260 num_pages = 1 + (num_lines - 1) / page_height
1260 num_pages = 1 + (num_lines - 1) / page_height
1261 max_offset = (num_pages - 1) * page_height
1261 max_offset = (num_pages - 1) * page_height
1262 newline = mode_state[b'line_offset'] + delta * unit
1262 newline = mode_state[b'line_offset'] + delta * unit
1263 mode_state[b'line_offset'] = max(0, min(max_offset, newline))
1263 mode_state[b'line_offset'] = max(0, min(max_offset, newline))
1264
1264
1265
1265
1266 def event(state, ch):
1266 def event(state, ch):
1267 """Change state based on the current character input
1267 """Change state based on the current character input
1268
1268
1269 This takes the current state and based on the current character input from
1269 This takes the current state and based on the current character input from
1270 the user we change the state.
1270 the user we change the state.
1271 """
1271 """
1272 selected = state[b'selected']
1272 selected = state[b'selected']
1273 oldpos = state[b'pos']
1273 oldpos = state[b'pos']
1274 rules = state[b'rules']
1274 rules = state[b'rules']
1275
1275
1276 if ch in (curses.KEY_RESIZE, b"KEY_RESIZE"):
1276 if ch in (curses.KEY_RESIZE, b"KEY_RESIZE"):
1277 return E_RESIZE
1277 return E_RESIZE
1278
1278
1279 lookup_ch = ch
1279 lookup_ch = ch
1280 if ch is not None and b'0' <= ch <= b'9':
1280 if ch is not None and b'0' <= ch <= b'9':
1281 lookup_ch = b'0'
1281 lookup_ch = b'0'
1282
1282
1283 curmode, prevmode = state[b'mode']
1283 curmode, prevmode = state[b'mode']
1284 action = KEYTABLE[curmode].get(
1284 action = KEYTABLE[curmode].get(
1285 lookup_ch, KEYTABLE[b'global'].get(lookup_ch)
1285 lookup_ch, KEYTABLE[b'global'].get(lookup_ch)
1286 )
1286 )
1287 if action is None:
1287 if action is None:
1288 return
1288 return
1289 if action in (b'down', b'move-down'):
1289 if action in (b'down', b'move-down'):
1290 newpos = min(oldpos + 1, len(rules) - 1)
1290 newpos = min(oldpos + 1, len(rules) - 1)
1291 movecursor(state, oldpos, newpos)
1291 movecursor(state, oldpos, newpos)
1292 if selected is not None or action == b'move-down':
1292 if selected is not None or action == b'move-down':
1293 swap(state, oldpos, newpos)
1293 swap(state, oldpos, newpos)
1294 elif action in (b'up', b'move-up'):
1294 elif action in (b'up', b'move-up'):
1295 newpos = max(0, oldpos - 1)
1295 newpos = max(0, oldpos - 1)
1296 movecursor(state, oldpos, newpos)
1296 movecursor(state, oldpos, newpos)
1297 if selected is not None or action == b'move-up':
1297 if selected is not None or action == b'move-up':
1298 swap(state, oldpos, newpos)
1298 swap(state, oldpos, newpos)
1299 elif action == b'next-action':
1299 elif action == b'next-action':
1300 cycleaction(state, oldpos, next=True)
1300 cycleaction(state, oldpos, next=True)
1301 elif action == b'prev-action':
1301 elif action == b'prev-action':
1302 cycleaction(state, oldpos, next=False)
1302 cycleaction(state, oldpos, next=False)
1303 elif action == b'select':
1303 elif action == b'select':
1304 selected = oldpos if selected is None else None
1304 selected = oldpos if selected is None else None
1305 makeselection(state, selected)
1305 makeselection(state, selected)
1306 elif action == b'goto' and int(ch) < len(rules) and len(rules) <= 10:
1306 elif action == b'goto' and int(ch) < len(rules) and len(rules) <= 10:
1307 newrule = next((r for r in rules if r.origpos == int(ch)))
1307 newrule = next((r for r in rules if r.origpos == int(ch)))
1308 movecursor(state, oldpos, newrule.pos)
1308 movecursor(state, oldpos, newrule.pos)
1309 if selected is not None:
1309 if selected is not None:
1310 swap(state, oldpos, newrule.pos)
1310 swap(state, oldpos, newrule.pos)
1311 elif action.startswith(b'action-'):
1311 elif action.startswith(b'action-'):
1312 changeaction(state, oldpos, action[7:])
1312 changeaction(state, oldpos, action[7:])
1313 elif action == b'showpatch':
1313 elif action == b'showpatch':
1314 changemode(state, MODE_PATCH if curmode != MODE_PATCH else prevmode)
1314 changemode(state, MODE_PATCH if curmode != MODE_PATCH else prevmode)
1315 elif action == b'help':
1315 elif action == b'help':
1316 changemode(state, MODE_HELP if curmode != MODE_HELP else prevmode)
1316 changemode(state, MODE_HELP if curmode != MODE_HELP else prevmode)
1317 elif action == b'quit':
1317 elif action == b'quit':
1318 return E_QUIT
1318 return E_QUIT
1319 elif action == b'histedit':
1319 elif action == b'histedit':
1320 return E_HISTEDIT
1320 return E_HISTEDIT
1321 elif action == b'page-down':
1321 elif action == b'page-down':
1322 return E_PAGEDOWN
1322 return E_PAGEDOWN
1323 elif action == b'page-up':
1323 elif action == b'page-up':
1324 return E_PAGEUP
1324 return E_PAGEUP
1325 elif action == b'line-down':
1325 elif action == b'line-down':
1326 return E_LINEDOWN
1326 return E_LINEDOWN
1327 elif action == b'line-up':
1327 elif action == b'line-up':
1328 return E_LINEUP
1328 return E_LINEUP
1329
1329
1330
1330
1331 def makecommands(rules):
1331 def makecommands(rules):
1332 """Returns a list of commands consumable by histedit --commands based on
1332 """Returns a list of commands consumable by histedit --commands based on
1333 our list of rules"""
1333 our list of rules"""
1334 commands = []
1334 commands = []
1335 for rules in rules:
1335 for rules in rules:
1336 commands.append(b'%s %s\n' % (rules.action, rules.ctx))
1336 commands.append(b'%s %s\n' % (rules.action, rules.ctx))
1337 return commands
1337 return commands
1338
1338
1339
1339
1340 def addln(win, y, x, line, color=None):
1340 def addln(win, y, x, line, color=None):
1341 """Add a line to the given window left padding but 100% filled with
1341 """Add a line to the given window left padding but 100% filled with
1342 whitespace characters, so that the color appears on the whole line"""
1342 whitespace characters, so that the color appears on the whole line"""
1343 maxy, maxx = win.getmaxyx()
1343 maxy, maxx = win.getmaxyx()
1344 length = maxx - 1 - x
1344 length = maxx - 1 - x
1345 line = bytes(line).ljust(length)[:length]
1345 line = bytes(line).ljust(length)[:length]
1346 if y < 0:
1346 if y < 0:
1347 y = maxy + y
1347 y = maxy + y
1348 if x < 0:
1348 if x < 0:
1349 x = maxx + x
1349 x = maxx + x
1350 if color:
1350 if color:
1351 win.addstr(y, x, line, color)
1351 win.addstr(y, x, line, color)
1352 else:
1352 else:
1353 win.addstr(y, x, line)
1353 win.addstr(y, x, line)
1354
1354
1355
1355
1356 def _trunc_head(line, n):
1356 def _trunc_head(line, n):
1357 if len(line) <= n:
1357 if len(line) <= n:
1358 return line
1358 return line
1359 return b'> ' + line[-(n - 2) :]
1359 return b'> ' + line[-(n - 2) :]
1360
1360
1361
1361
1362 def _trunc_tail(line, n):
1362 def _trunc_tail(line, n):
1363 if len(line) <= n:
1363 if len(line) <= n:
1364 return line
1364 return line
1365 return line[: n - 2] + b' >'
1365 return line[: n - 2] + b' >'
1366
1366
1367
1367
1368 def patchcontents(state):
1368 def patchcontents(state):
1369 repo = state[b'repo']
1369 repo = state[b'repo']
1370 rule = state[b'rules'][state[b'pos']]
1370 rule = state[b'rules'][state[b'pos']]
1371 displayer = logcmdutil.changesetdisplayer(
1371 displayer = logcmdutil.changesetdisplayer(
1372 repo.ui, repo, {b"patch": True, b"template": b"status"}, buffered=True
1372 repo.ui, repo, {b"patch": True, b"template": b"status"}, buffered=True
1373 )
1373 )
1374 overrides = {(b'ui', b'verbose'): True}
1374 overrides = {(b'ui', b'verbose'): True}
1375 with repo.ui.configoverride(overrides, source=b'histedit'):
1375 with repo.ui.configoverride(overrides, source=b'histedit'):
1376 displayer.show(rule.ctx)
1376 displayer.show(rule.ctx)
1377 displayer.close()
1377 displayer.close()
1378 return displayer.hunk[rule.ctx.rev()].splitlines()
1378 return displayer.hunk[rule.ctx.rev()].splitlines()
1379
1379
1380
1380
1381 def _chisteditmain(repo, rules, stdscr):
1381 def _chisteditmain(repo, rules, stdscr):
1382 try:
1382 try:
1383 curses.use_default_colors()
1383 curses.use_default_colors()
1384 except curses.error:
1384 except curses.error:
1385 pass
1385 pass
1386
1386
1387 # initialize color pattern
1387 # initialize color pattern
1388 curses.init_pair(COLOR_HELP, curses.COLOR_WHITE, curses.COLOR_BLUE)
1388 curses.init_pair(COLOR_HELP, curses.COLOR_WHITE, curses.COLOR_BLUE)
1389 curses.init_pair(COLOR_SELECTED, curses.COLOR_BLACK, curses.COLOR_WHITE)
1389 curses.init_pair(COLOR_SELECTED, curses.COLOR_BLACK, curses.COLOR_WHITE)
1390 curses.init_pair(COLOR_WARN, curses.COLOR_BLACK, curses.COLOR_YELLOW)
1390 curses.init_pair(COLOR_WARN, curses.COLOR_BLACK, curses.COLOR_YELLOW)
1391 curses.init_pair(COLOR_OK, curses.COLOR_BLACK, curses.COLOR_GREEN)
1391 curses.init_pair(COLOR_OK, curses.COLOR_BLACK, curses.COLOR_GREEN)
1392 curses.init_pair(COLOR_CURRENT, curses.COLOR_WHITE, curses.COLOR_MAGENTA)
1392 curses.init_pair(COLOR_CURRENT, curses.COLOR_WHITE, curses.COLOR_MAGENTA)
1393 curses.init_pair(COLOR_DIFF_ADD_LINE, curses.COLOR_GREEN, -1)
1393 curses.init_pair(COLOR_DIFF_ADD_LINE, curses.COLOR_GREEN, -1)
1394 curses.init_pair(COLOR_DIFF_DEL_LINE, curses.COLOR_RED, -1)
1394 curses.init_pair(COLOR_DIFF_DEL_LINE, curses.COLOR_RED, -1)
1395 curses.init_pair(COLOR_DIFF_OFFSET, curses.COLOR_MAGENTA, -1)
1395 curses.init_pair(COLOR_DIFF_OFFSET, curses.COLOR_MAGENTA, -1)
1396 curses.init_pair(COLOR_ROLL, curses.COLOR_RED, -1)
1396 curses.init_pair(COLOR_ROLL, curses.COLOR_RED, -1)
1397 curses.init_pair(
1397 curses.init_pair(
1398 COLOR_ROLL_CURRENT, curses.COLOR_BLACK, curses.COLOR_MAGENTA
1398 COLOR_ROLL_CURRENT, curses.COLOR_BLACK, curses.COLOR_MAGENTA
1399 )
1399 )
1400 curses.init_pair(COLOR_ROLL_SELECTED, curses.COLOR_RED, curses.COLOR_WHITE)
1400 curses.init_pair(COLOR_ROLL_SELECTED, curses.COLOR_RED, curses.COLOR_WHITE)
1401
1401
1402 # don't display the cursor
1402 # don't display the cursor
1403 try:
1403 try:
1404 curses.curs_set(0)
1404 curses.curs_set(0)
1405 except curses.error:
1405 except curses.error:
1406 pass
1406 pass
1407
1407
1408 def rendercommit(win, state):
1408 def rendercommit(win, state):
1409 """Renders the commit window that shows the log of the current selected
1409 """Renders the commit window that shows the log of the current selected
1410 commit"""
1410 commit"""
1411 pos = state[b'pos']
1411 pos = state[b'pos']
1412 rules = state[b'rules']
1412 rules = state[b'rules']
1413 rule = rules[pos]
1413 rule = rules[pos]
1414
1414
1415 ctx = rule.ctx
1415 ctx = rule.ctx
1416 win.box()
1416 win.box()
1417
1417
1418 maxy, maxx = win.getmaxyx()
1418 maxy, maxx = win.getmaxyx()
1419 length = maxx - 3
1419 length = maxx - 3
1420
1420
1421 line = b"changeset: %d:%s" % (ctx.rev(), ctx.hex()[:12])
1421 line = b"changeset: %d:%s" % (ctx.rev(), ctx.hex()[:12])
1422 win.addstr(1, 1, line[:length])
1422 win.addstr(1, 1, line[:length])
1423
1423
1424 line = b"user: %s" % ctx.user()
1424 line = b"user: %s" % ctx.user()
1425 win.addstr(2, 1, line[:length])
1425 win.addstr(2, 1, line[:length])
1426
1426
1427 bms = repo.nodebookmarks(ctx.node())
1427 bms = repo.nodebookmarks(ctx.node())
1428 line = b"bookmark: %s" % b' '.join(bms)
1428 line = b"bookmark: %s" % b' '.join(bms)
1429 win.addstr(3, 1, line[:length])
1429 win.addstr(3, 1, line[:length])
1430
1430
1431 line = b"summary: %s" % (ctx.description().splitlines()[0])
1431 line = b"summary: %s" % (ctx.description().splitlines()[0])
1432 win.addstr(4, 1, line[:length])
1432 win.addstr(4, 1, line[:length])
1433
1433
1434 line = b"files: "
1434 line = b"files: "
1435 win.addstr(5, 1, line)
1435 win.addstr(5, 1, line)
1436 fnx = 1 + len(line)
1436 fnx = 1 + len(line)
1437 fnmaxx = length - fnx + 1
1437 fnmaxx = length - fnx + 1
1438 y = 5
1438 y = 5
1439 fnmaxn = maxy - (1 + y) - 1
1439 fnmaxn = maxy - (1 + y) - 1
1440 files = ctx.files()
1440 files = ctx.files()
1441 for i, line1 in enumerate(files):
1441 for i, line1 in enumerate(files):
1442 if len(files) > fnmaxn and i == fnmaxn - 1:
1442 if len(files) > fnmaxn and i == fnmaxn - 1:
1443 win.addstr(y, fnx, _trunc_tail(b','.join(files[i:]), fnmaxx))
1443 win.addstr(y, fnx, _trunc_tail(b','.join(files[i:]), fnmaxx))
1444 y = y + 1
1444 y = y + 1
1445 break
1445 break
1446 win.addstr(y, fnx, _trunc_head(line1, fnmaxx))
1446 win.addstr(y, fnx, _trunc_head(line1, fnmaxx))
1447 y = y + 1
1447 y = y + 1
1448
1448
1449 conflicts = rule.conflicts
1449 conflicts = rule.conflicts
1450 if len(conflicts) > 0:
1450 if len(conflicts) > 0:
1451 conflictstr = b','.join(map(lambda r: r.ctx.hex()[:12], conflicts))
1451 conflictstr = b','.join(map(lambda r: r.ctx.hex()[:12], conflicts))
1452 conflictstr = b"changed files overlap with %s" % conflictstr
1452 conflictstr = b"changed files overlap with %s" % conflictstr
1453 else:
1453 else:
1454 conflictstr = b'no overlap'
1454 conflictstr = b'no overlap'
1455
1455
1456 win.addstr(y, 1, conflictstr[:length])
1456 win.addstr(y, 1, conflictstr[:length])
1457 win.noutrefresh()
1457 win.noutrefresh()
1458
1458
1459 def helplines(mode):
1459 def helplines(mode):
1460 if mode == MODE_PATCH:
1460 if mode == MODE_PATCH:
1461 help = b"""\
1461 help = b"""\
1462 ?: help, k/up: line up, j/down: line down, v: stop viewing patch
1462 ?: help, k/up: line up, j/down: line down, v: stop viewing patch
1463 pgup: prev page, space/pgdn: next page, c: commit, q: abort
1463 pgup: prev page, space/pgdn: next page, c: commit, q: abort
1464 """
1464 """
1465 else:
1465 else:
1466 help = b"""\
1466 help = b"""\
1467 ?: help, k/up: move up, j/down: move down, space: select, v: view patch
1467 ?: help, k/up: move up, j/down: move down, space: select, v: view patch
1468 d: drop, e: edit, f: fold, m: mess, p: pick, r: roll
1468 d: drop, e: edit, f: fold, m: mess, p: pick, r: roll
1469 pgup/K: move patch up, pgdn/J: move patch down, c: commit, q: abort
1469 pgup/K: move patch up, pgdn/J: move patch down, c: commit, q: abort
1470 """
1470 """
1471 return help.splitlines()
1471 return help.splitlines()
1472
1472
1473 def renderhelp(win, state):
1473 def renderhelp(win, state):
1474 maxy, maxx = win.getmaxyx()
1474 maxy, maxx = win.getmaxyx()
1475 mode, _ = state[b'mode']
1475 mode, _ = state[b'mode']
1476 for y, line in enumerate(helplines(mode)):
1476 for y, line in enumerate(helplines(mode)):
1477 if y >= maxy:
1477 if y >= maxy:
1478 break
1478 break
1479 addln(win, y, 0, line, curses.color_pair(COLOR_HELP))
1479 addln(win, y, 0, line, curses.color_pair(COLOR_HELP))
1480 win.noutrefresh()
1480 win.noutrefresh()
1481
1481
1482 def renderrules(rulesscr, state):
1482 def renderrules(rulesscr, state):
1483 rules = state[b'rules']
1483 rules = state[b'rules']
1484 pos = state[b'pos']
1484 pos = state[b'pos']
1485 selected = state[b'selected']
1485 selected = state[b'selected']
1486 start = state[b'modes'][MODE_RULES][b'line_offset']
1486 start = state[b'modes'][MODE_RULES][b'line_offset']
1487
1487
1488 conflicts = [r.ctx for r in rules if r.conflicts]
1488 conflicts = [r.ctx for r in rules if r.conflicts]
1489 if len(conflicts) > 0:
1489 if len(conflicts) > 0:
1490 line = b"potential conflict in %s" % b','.join(
1490 line = b"potential conflict in %s" % b','.join(
1491 map(pycompat.bytestr, conflicts)
1491 map(pycompat.bytestr, conflicts)
1492 )
1492 )
1493 addln(rulesscr, -1, 0, line, curses.color_pair(COLOR_WARN))
1493 addln(rulesscr, -1, 0, line, curses.color_pair(COLOR_WARN))
1494
1494
1495 for y, rule in enumerate(rules[start:]):
1495 for y, rule in enumerate(rules[start:]):
1496 if y >= state[b'page_height']:
1496 if y >= state[b'page_height']:
1497 break
1497 break
1498 if len(rule.conflicts) > 0:
1498 if len(rule.conflicts) > 0:
1499 rulesscr.addstr(y, 0, b" ", curses.color_pair(COLOR_WARN))
1499 rulesscr.addstr(y, 0, b" ", curses.color_pair(COLOR_WARN))
1500 else:
1500 else:
1501 rulesscr.addstr(y, 0, b" ", curses.COLOR_BLACK)
1501 rulesscr.addstr(y, 0, b" ", curses.COLOR_BLACK)
1502
1502
1503 if y + start == selected:
1503 if y + start == selected:
1504 rollcolor = COLOR_ROLL_SELECTED
1504 rollcolor = COLOR_ROLL_SELECTED
1505 addln(rulesscr, y, 2, rule, curses.color_pair(COLOR_SELECTED))
1505 addln(rulesscr, y, 2, rule, curses.color_pair(COLOR_SELECTED))
1506 elif y + start == pos:
1506 elif y + start == pos:
1507 rollcolor = COLOR_ROLL_CURRENT
1507 rollcolor = COLOR_ROLL_CURRENT
1508 addln(
1508 addln(
1509 rulesscr,
1509 rulesscr,
1510 y,
1510 y,
1511 2,
1511 2,
1512 rule,
1512 rule,
1513 curses.color_pair(COLOR_CURRENT) | curses.A_BOLD,
1513 curses.color_pair(COLOR_CURRENT) | curses.A_BOLD,
1514 )
1514 )
1515 else:
1515 else:
1516 rollcolor = COLOR_ROLL
1516 rollcolor = COLOR_ROLL
1517 addln(rulesscr, y, 2, rule)
1517 addln(rulesscr, y, 2, rule)
1518
1518
1519 if rule.action == b'roll':
1519 if rule.action == b'roll':
1520 rulesscr.addstr(
1520 rulesscr.addstr(
1521 y,
1521 y,
1522 2 + len(rule.prefix),
1522 2 + len(rule.prefix),
1523 rule.desc,
1523 rule.desc,
1524 curses.color_pair(rollcolor),
1524 curses.color_pair(rollcolor),
1525 )
1525 )
1526
1526
1527 rulesscr.noutrefresh()
1527 rulesscr.noutrefresh()
1528
1528
1529 def renderstring(win, state, output, diffcolors=False):
1529 def renderstring(win, state, output, diffcolors=False):
1530 maxy, maxx = win.getmaxyx()
1530 maxy, maxx = win.getmaxyx()
1531 length = min(maxy - 1, len(output))
1531 length = min(maxy - 1, len(output))
1532 for y in range(0, length):
1532 for y in range(0, length):
1533 line = output[y]
1533 line = output[y]
1534 if diffcolors:
1534 if diffcolors:
1535 if line and line[0] == b'+':
1535 if line and line[0] == b'+':
1536 win.addstr(
1536 win.addstr(
1537 y, 0, line, curses.color_pair(COLOR_DIFF_ADD_LINE)
1537 y, 0, line, curses.color_pair(COLOR_DIFF_ADD_LINE)
1538 )
1538 )
1539 elif line and line[0] == b'-':
1539 elif line and line[0] == b'-':
1540 win.addstr(
1540 win.addstr(
1541 y, 0, line, curses.color_pair(COLOR_DIFF_DEL_LINE)
1541 y, 0, line, curses.color_pair(COLOR_DIFF_DEL_LINE)
1542 )
1542 )
1543 elif line.startswith(b'@@ '):
1543 elif line.startswith(b'@@ '):
1544 win.addstr(y, 0, line, curses.color_pair(COLOR_DIFF_OFFSET))
1544 win.addstr(y, 0, line, curses.color_pair(COLOR_DIFF_OFFSET))
1545 else:
1545 else:
1546 win.addstr(y, 0, line)
1546 win.addstr(y, 0, line)
1547 else:
1547 else:
1548 win.addstr(y, 0, line)
1548 win.addstr(y, 0, line)
1549 win.noutrefresh()
1549 win.noutrefresh()
1550
1550
1551 def renderpatch(win, state):
1551 def renderpatch(win, state):
1552 start = state[b'modes'][MODE_PATCH][b'line_offset']
1552 start = state[b'modes'][MODE_PATCH][b'line_offset']
1553 content = state[b'modes'][MODE_PATCH][b'patchcontents']
1553 content = state[b'modes'][MODE_PATCH][b'patchcontents']
1554 renderstring(win, state, content[start:], diffcolors=True)
1554 renderstring(win, state, content[start:], diffcolors=True)
1555
1555
1556 def layout(mode):
1556 def layout(mode):
1557 maxy, maxx = stdscr.getmaxyx()
1557 maxy, maxx = stdscr.getmaxyx()
1558 helplen = len(helplines(mode))
1558 helplen = len(helplines(mode))
1559 return {
1559 return {
1560 b'commit': (12, maxx),
1560 b'commit': (12, maxx),
1561 b'help': (helplen, maxx),
1561 b'help': (helplen, maxx),
1562 b'main': (maxy - helplen - 12, maxx),
1562 b'main': (maxy - helplen - 12, maxx),
1563 }
1563 }
1564
1564
1565 def drawvertwin(size, y, x):
1565 def drawvertwin(size, y, x):
1566 win = curses.newwin(size[0], size[1], y, x)
1566 win = curses.newwin(size[0], size[1], y, x)
1567 y += size[0]
1567 y += size[0]
1568 return win, y, x
1568 return win, y, x
1569
1569
1570 state = {
1570 state = {
1571 b'pos': 0,
1571 b'pos': 0,
1572 b'rules': rules,
1572 b'rules': rules,
1573 b'selected': None,
1573 b'selected': None,
1574 b'mode': (MODE_INIT, MODE_INIT),
1574 b'mode': (MODE_INIT, MODE_INIT),
1575 b'page_height': None,
1575 b'page_height': None,
1576 b'modes': {
1576 b'modes': {
1577 MODE_RULES: {b'line_offset': 0,},
1577 MODE_RULES: {b'line_offset': 0,},
1578 MODE_PATCH: {b'line_offset': 0,},
1578 MODE_PATCH: {b'line_offset': 0,},
1579 },
1579 },
1580 b'repo': repo,
1580 b'repo': repo,
1581 }
1581 }
1582
1582
1583 # eventloop
1583 # eventloop
1584 ch = None
1584 ch = None
1585 stdscr.clear()
1585 stdscr.clear()
1586 stdscr.refresh()
1586 stdscr.refresh()
1587 while True:
1587 while True:
1588 try:
1588 try:
1589 oldmode, _ = state[b'mode']
1589 oldmode, _ = state[b'mode']
1590 if oldmode == MODE_INIT:
1590 if oldmode == MODE_INIT:
1591 changemode(state, MODE_RULES)
1591 changemode(state, MODE_RULES)
1592 e = event(state, ch)
1592 e = event(state, ch)
1593
1593
1594 if e == E_QUIT:
1594 if e == E_QUIT:
1595 return False
1595 return False
1596 if e == E_HISTEDIT:
1596 if e == E_HISTEDIT:
1597 return state[b'rules']
1597 return state[b'rules']
1598 else:
1598 else:
1599 if e == E_RESIZE:
1599 if e == E_RESIZE:
1600 size = screen_size()
1600 size = screen_size()
1601 if size != stdscr.getmaxyx():
1601 if size != stdscr.getmaxyx():
1602 curses.resizeterm(*size)
1602 curses.resizeterm(*size)
1603
1603
1604 curmode, _ = state[b'mode']
1604 curmode, _ = state[b'mode']
1605 sizes = layout(curmode)
1605 sizes = layout(curmode)
1606 if curmode != oldmode:
1606 if curmode != oldmode:
1607 state[b'page_height'] = sizes[b'main'][0]
1607 state[b'page_height'] = sizes[b'main'][0]
1608 # Adjust the view to fit the current screen size.
1608 # Adjust the view to fit the current screen size.
1609 movecursor(state, state[b'pos'], state[b'pos'])
1609 movecursor(state, state[b'pos'], state[b'pos'])
1610
1610
1611 # Pack the windows against the top, each pane spread across the
1611 # Pack the windows against the top, each pane spread across the
1612 # full width of the screen.
1612 # full width of the screen.
1613 y, x = (0, 0)
1613 y, x = (0, 0)
1614 helpwin, y, x = drawvertwin(sizes[b'help'], y, x)
1614 helpwin, y, x = drawvertwin(sizes[b'help'], y, x)
1615 mainwin, y, x = drawvertwin(sizes[b'main'], y, x)
1615 mainwin, y, x = drawvertwin(sizes[b'main'], y, x)
1616 commitwin, y, x = drawvertwin(sizes[b'commit'], y, x)
1616 commitwin, y, x = drawvertwin(sizes[b'commit'], y, x)
1617
1617
1618 if e in (E_PAGEDOWN, E_PAGEUP, E_LINEDOWN, E_LINEUP):
1618 if e in (E_PAGEDOWN, E_PAGEUP, E_LINEDOWN, E_LINEUP):
1619 if e == E_PAGEDOWN:
1619 if e == E_PAGEDOWN:
1620 changeview(state, +1, b'page')
1620 changeview(state, +1, b'page')
1621 elif e == E_PAGEUP:
1621 elif e == E_PAGEUP:
1622 changeview(state, -1, b'page')
1622 changeview(state, -1, b'page')
1623 elif e == E_LINEDOWN:
1623 elif e == E_LINEDOWN:
1624 changeview(state, +1, b'line')
1624 changeview(state, +1, b'line')
1625 elif e == E_LINEUP:
1625 elif e == E_LINEUP:
1626 changeview(state, -1, b'line')
1626 changeview(state, -1, b'line')
1627
1627
1628 # start rendering
1628 # start rendering
1629 commitwin.erase()
1629 commitwin.erase()
1630 helpwin.erase()
1630 helpwin.erase()
1631 mainwin.erase()
1631 mainwin.erase()
1632 if curmode == MODE_PATCH:
1632 if curmode == MODE_PATCH:
1633 renderpatch(mainwin, state)
1633 renderpatch(mainwin, state)
1634 elif curmode == MODE_HELP:
1634 elif curmode == MODE_HELP:
1635 renderstring(mainwin, state, __doc__.strip().splitlines())
1635 renderstring(mainwin, state, __doc__.strip().splitlines())
1636 else:
1636 else:
1637 renderrules(mainwin, state)
1637 renderrules(mainwin, state)
1638 rendercommit(commitwin, state)
1638 rendercommit(commitwin, state)
1639 renderhelp(helpwin, state)
1639 renderhelp(helpwin, state)
1640 curses.doupdate()
1640 curses.doupdate()
1641 # done rendering
1641 # done rendering
1642 ch = encoding.strtolocal(stdscr.getkey())
1642 ch = encoding.strtolocal(stdscr.getkey())
1643 except curses.error:
1643 except curses.error:
1644 pass
1644 pass
1645
1645
1646
1646
1647 def _chistedit(ui, repo, *freeargs, **opts):
1647 def _chistedit(ui, repo, *freeargs, **opts):
1648 """interactively edit changeset history via a curses interface
1648 """interactively edit changeset history via a curses interface
1649
1649
1650 Provides a ncurses interface to histedit. Press ? in chistedit mode
1650 Provides a ncurses interface to histedit. Press ? in chistedit mode
1651 to see an extensive help. Requires python-curses to be installed."""
1651 to see an extensive help. Requires python-curses to be installed."""
1652
1652
1653 if curses is None:
1653 if curses is None:
1654 raise error.Abort(_(b"Python curses library required"))
1654 raise error.Abort(_(b"Python curses library required"))
1655
1655
1656 # disable color
1656 # disable color
1657 ui._colormode = None
1657 ui._colormode = None
1658
1658
1659 try:
1659 try:
1660 keep = opts.get(b'keep')
1660 keep = opts.get(b'keep')
1661 revs = opts.get(b'rev', [])[:]
1661 revs = opts.get(b'rev', [])[:]
1662 cmdutil.checkunfinished(repo)
1662 cmdutil.checkunfinished(repo)
1663 cmdutil.bailifchanged(repo)
1663 cmdutil.bailifchanged(repo)
1664
1664
1665 if os.path.exists(os.path.join(repo.path, b'histedit-state')):
1665 if os.path.exists(os.path.join(repo.path, b'histedit-state')):
1666 raise error.Abort(
1666 raise error.Abort(
1667 _(
1667 _(
1668 b'history edit already in progress, try '
1668 b'history edit already in progress, try '
1669 b'--continue or --abort'
1669 b'--continue or --abort'
1670 )
1670 )
1671 )
1671 )
1672 revs.extend(freeargs)
1672 revs.extend(freeargs)
1673 if not revs:
1673 if not revs:
1674 defaultrev = destutil.desthistedit(ui, repo)
1674 defaultrev = destutil.desthistedit(ui, repo)
1675 if defaultrev is not None:
1675 if defaultrev is not None:
1676 revs.append(defaultrev)
1676 revs.append(defaultrev)
1677 if len(revs) != 1:
1677 if len(revs) != 1:
1678 raise error.Abort(
1678 raise error.Abort(
1679 _(b'histedit requires exactly one ancestor revision')
1679 _(b'histedit requires exactly one ancestor revision')
1680 )
1680 )
1681
1681
1682 rr = list(repo.set(b'roots(%ld)', scmutil.revrange(repo, revs)))
1682 rr = list(repo.set(b'roots(%ld)', scmutil.revrange(repo, revs)))
1683 if len(rr) != 1:
1683 if len(rr) != 1:
1684 raise error.Abort(
1684 raise error.Abort(
1685 _(
1685 _(
1686 b'The specified revisions must have '
1686 b'The specified revisions must have '
1687 b'exactly one common root'
1687 b'exactly one common root'
1688 )
1688 )
1689 )
1689 )
1690 root = rr[0].node()
1690 root = rr[0].node()
1691
1691
1692 topmost = repo.dirstate.p1()
1692 topmost = repo.dirstate.p1()
1693 revs = between(repo, root, topmost, keep)
1693 revs = between(repo, root, topmost, keep)
1694 if not revs:
1694 if not revs:
1695 raise error.Abort(
1695 raise error.Abort(
1696 _(b'%s is not an ancestor of working directory')
1696 _(b'%s is not an ancestor of working directory')
1697 % node.short(root)
1697 % node.short(root)
1698 )
1698 )
1699
1699
1700 ctxs = []
1700 ctxs = []
1701 for i, r in enumerate(revs):
1701 for i, r in enumerate(revs):
1702 ctxs.append(histeditrule(repo[r], i))
1702 ctxs.append(histeditrule(repo[r], i))
1703 # Curses requires setting the locale or it will default to the C
1703 # Curses requires setting the locale or it will default to the C
1704 # locale. This sets the locale to the user's default system
1704 # locale. This sets the locale to the user's default system
1705 # locale.
1705 # locale.
1706 locale.setlocale(locale.LC_ALL, '')
1706 locale.setlocale(locale.LC_ALL, '')
1707 rc = curses.wrapper(functools.partial(_chisteditmain, repo, ctxs))
1707 rc = curses.wrapper(functools.partial(_chisteditmain, repo, ctxs))
1708 curses.echo()
1708 curses.echo()
1709 curses.endwin()
1709 curses.endwin()
1710 if rc is False:
1710 if rc is False:
1711 ui.write(_(b"histedit aborted\n"))
1711 ui.write(_(b"histedit aborted\n"))
1712 return 0
1712 return 0
1713 if type(rc) is list:
1713 if type(rc) is list:
1714 ui.status(_(b"performing changes\n"))
1714 ui.status(_(b"performing changes\n"))
1715 rules = makecommands(rc)
1715 rules = makecommands(rc)
1716 with repo.vfs(b'chistedit', b'w+') as fp:
1716 with repo.vfs(b'chistedit', b'w+') as fp:
1717 for r in rules:
1717 for r in rules:
1718 fp.write(r)
1718 fp.write(r)
1719 opts['commands'] = fp.name
1719 opts['commands'] = fp.name
1720 return _texthistedit(ui, repo, *freeargs, **opts)
1720 return _texthistedit(ui, repo, *freeargs, **opts)
1721 except KeyboardInterrupt:
1721 except KeyboardInterrupt:
1722 pass
1722 pass
1723 return -1
1723 return -1
1724
1724
1725
1725
1726 @command(
1726 @command(
1727 b'histedit',
1727 b'histedit',
1728 [
1728 [
1729 (
1729 (
1730 b'',
1730 b'',
1731 b'commands',
1731 b'commands',
1732 b'',
1732 b'',
1733 _(b'read history edits from the specified file'),
1733 _(b'read history edits from the specified file'),
1734 _(b'FILE'),
1734 _(b'FILE'),
1735 ),
1735 ),
1736 (b'c', b'continue', False, _(b'continue an edit already in progress')),
1736 (b'c', b'continue', False, _(b'continue an edit already in progress')),
1737 (b'', b'edit-plan', False, _(b'edit remaining actions list')),
1737 (b'', b'edit-plan', False, _(b'edit remaining actions list')),
1738 (
1738 (
1739 b'k',
1739 b'k',
1740 b'keep',
1740 b'keep',
1741 False,
1741 False,
1742 _(b"don't strip old nodes after edit is complete"),
1742 _(b"don't strip old nodes after edit is complete"),
1743 ),
1743 ),
1744 (b'', b'abort', False, _(b'abort an edit in progress')),
1744 (b'', b'abort', False, _(b'abort an edit in progress')),
1745 (b'o', b'outgoing', False, _(b'changesets not found in destination')),
1745 (b'o', b'outgoing', False, _(b'changesets not found in destination')),
1746 (
1746 (
1747 b'f',
1747 b'f',
1748 b'force',
1748 b'force',
1749 False,
1749 False,
1750 _(b'force outgoing even for unrelated repositories'),
1750 _(b'force outgoing even for unrelated repositories'),
1751 ),
1751 ),
1752 (b'r', b'rev', [], _(b'first revision to be edited'), _(b'REV')),
1752 (b'r', b'rev', [], _(b'first revision to be edited'), _(b'REV')),
1753 ]
1753 ]
1754 + cmdutil.formatteropts,
1754 + cmdutil.formatteropts,
1755 _(b"[OPTIONS] ([ANCESTOR] | --outgoing [URL])"),
1755 _(b"[OPTIONS] ([ANCESTOR] | --outgoing [URL])"),
1756 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
1756 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
1757 )
1757 )
1758 def histedit(ui, repo, *freeargs, **opts):
1758 def histedit(ui, repo, *freeargs, **opts):
1759 """interactively edit changeset history
1759 """interactively edit changeset history
1760
1760
1761 This command lets you edit a linear series of changesets (up to
1761 This command lets you edit a linear series of changesets (up to
1762 and including the working directory, which should be clean).
1762 and including the working directory, which should be clean).
1763 You can:
1763 You can:
1764
1764
1765 - `pick` to [re]order a changeset
1765 - `pick` to [re]order a changeset
1766
1766
1767 - `drop` to omit changeset
1767 - `drop` to omit changeset
1768
1768
1769 - `mess` to reword the changeset commit message
1769 - `mess` to reword the changeset commit message
1770
1770
1771 - `fold` to combine it with the preceding changeset (using the later date)
1771 - `fold` to combine it with the preceding changeset (using the later date)
1772
1772
1773 - `roll` like fold, but discarding this commit's description and date
1773 - `roll` like fold, but discarding this commit's description and date
1774
1774
1775 - `edit` to edit this changeset (preserving date)
1775 - `edit` to edit this changeset (preserving date)
1776
1776
1777 - `base` to checkout changeset and apply further changesets from there
1777 - `base` to checkout changeset and apply further changesets from there
1778
1778
1779 There are a number of ways to select the root changeset:
1779 There are a number of ways to select the root changeset:
1780
1780
1781 - Specify ANCESTOR directly
1781 - Specify ANCESTOR directly
1782
1782
1783 - Use --outgoing -- it will be the first linear changeset not
1783 - Use --outgoing -- it will be the first linear changeset not
1784 included in destination. (See :hg:`help config.paths.default-push`)
1784 included in destination. (See :hg:`help config.paths.default-push`)
1785
1785
1786 - Otherwise, the value from the "histedit.defaultrev" config option
1786 - Otherwise, the value from the "histedit.defaultrev" config option
1787 is used as a revset to select the base revision when ANCESTOR is not
1787 is used as a revset to select the base revision when ANCESTOR is not
1788 specified. The first revision returned by the revset is used. By
1788 specified. The first revision returned by the revset is used. By
1789 default, this selects the editable history that is unique to the
1789 default, this selects the editable history that is unique to the
1790 ancestry of the working directory.
1790 ancestry of the working directory.
1791
1791
1792 .. container:: verbose
1792 .. container:: verbose
1793
1793
1794 If you use --outgoing, this command will abort if there are ambiguous
1794 If you use --outgoing, this command will abort if there are ambiguous
1795 outgoing revisions. For example, if there are multiple branches
1795 outgoing revisions. For example, if there are multiple branches
1796 containing outgoing revisions.
1796 containing outgoing revisions.
1797
1797
1798 Use "min(outgoing() and ::.)" or similar revset specification
1798 Use "min(outgoing() and ::.)" or similar revset specification
1799 instead of --outgoing to specify edit target revision exactly in
1799 instead of --outgoing to specify edit target revision exactly in
1800 such ambiguous situation. See :hg:`help revsets` for detail about
1800 such ambiguous situation. See :hg:`help revsets` for detail about
1801 selecting revisions.
1801 selecting revisions.
1802
1802
1803 .. container:: verbose
1803 .. container:: verbose
1804
1804
1805 Examples:
1805 Examples:
1806
1806
1807 - A number of changes have been made.
1807 - A number of changes have been made.
1808 Revision 3 is no longer needed.
1808 Revision 3 is no longer needed.
1809
1809
1810 Start history editing from revision 3::
1810 Start history editing from revision 3::
1811
1811
1812 hg histedit -r 3
1812 hg histedit -r 3
1813
1813
1814 An editor opens, containing the list of revisions,
1814 An editor opens, containing the list of revisions,
1815 with specific actions specified::
1815 with specific actions specified::
1816
1816
1817 pick 5339bf82f0ca 3 Zworgle the foobar
1817 pick 5339bf82f0ca 3 Zworgle the foobar
1818 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1818 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1819 pick 0a9639fcda9d 5 Morgify the cromulancy
1819 pick 0a9639fcda9d 5 Morgify the cromulancy
1820
1820
1821 Additional information about the possible actions
1821 Additional information about the possible actions
1822 to take appears below the list of revisions.
1822 to take appears below the list of revisions.
1823
1823
1824 To remove revision 3 from the history,
1824 To remove revision 3 from the history,
1825 its action (at the beginning of the relevant line)
1825 its action (at the beginning of the relevant line)
1826 is changed to 'drop'::
1826 is changed to 'drop'::
1827
1827
1828 drop 5339bf82f0ca 3 Zworgle the foobar
1828 drop 5339bf82f0ca 3 Zworgle the foobar
1829 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1829 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1830 pick 0a9639fcda9d 5 Morgify the cromulancy
1830 pick 0a9639fcda9d 5 Morgify the cromulancy
1831
1831
1832 - A number of changes have been made.
1832 - A number of changes have been made.
1833 Revision 2 and 4 need to be swapped.
1833 Revision 2 and 4 need to be swapped.
1834
1834
1835 Start history editing from revision 2::
1835 Start history editing from revision 2::
1836
1836
1837 hg histedit -r 2
1837 hg histedit -r 2
1838
1838
1839 An editor opens, containing the list of revisions,
1839 An editor opens, containing the list of revisions,
1840 with specific actions specified::
1840 with specific actions specified::
1841
1841
1842 pick 252a1af424ad 2 Blorb a morgwazzle
1842 pick 252a1af424ad 2 Blorb a morgwazzle
1843 pick 5339bf82f0ca 3 Zworgle the foobar
1843 pick 5339bf82f0ca 3 Zworgle the foobar
1844 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1844 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1845
1845
1846 To swap revision 2 and 4, its lines are swapped
1846 To swap revision 2 and 4, its lines are swapped
1847 in the editor::
1847 in the editor::
1848
1848
1849 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1849 pick 8ef592ce7cc4 4 Bedazzle the zerlog
1850 pick 5339bf82f0ca 3 Zworgle the foobar
1850 pick 5339bf82f0ca 3 Zworgle the foobar
1851 pick 252a1af424ad 2 Blorb a morgwazzle
1851 pick 252a1af424ad 2 Blorb a morgwazzle
1852
1852
1853 Returns 0 on success, 1 if user intervention is required (not only
1853 Returns 0 on success, 1 if user intervention is required (not only
1854 for intentional "edit" command, but also for resolving unexpected
1854 for intentional "edit" command, but also for resolving unexpected
1855 conflicts).
1855 conflicts).
1856 """
1856 """
1857 # kludge: _chistedit only works for starting an edit, not aborting
1857 # kludge: _chistedit only works for starting an edit, not aborting
1858 # or continuing, so fall back to regular _texthistedit for those
1858 # or continuing, so fall back to regular _texthistedit for those
1859 # operations.
1859 # operations.
1860 if (
1860 if (
1861 ui.interface(b'histedit') == b'curses'
1861 ui.interface(b'histedit') == b'curses'
1862 and _getgoal(pycompat.byteskwargs(opts)) == goalnew
1862 and _getgoal(pycompat.byteskwargs(opts)) == goalnew
1863 ):
1863 ):
1864 return _chistedit(ui, repo, *freeargs, **opts)
1864 return _chistedit(ui, repo, *freeargs, **opts)
1865 return _texthistedit(ui, repo, *freeargs, **opts)
1865 return _texthistedit(ui, repo, *freeargs, **opts)
1866
1866
1867
1867
1868 def _texthistedit(ui, repo, *freeargs, **opts):
1868 def _texthistedit(ui, repo, *freeargs, **opts):
1869 state = histeditstate(repo)
1869 state = histeditstate(repo)
1870 with repo.wlock() as wlock, repo.lock() as lock:
1870 with repo.wlock() as wlock, repo.lock() as lock:
1871 state.wlock = wlock
1871 state.wlock = wlock
1872 state.lock = lock
1872 state.lock = lock
1873 _histedit(ui, repo, state, *freeargs, **opts)
1873 _histedit(ui, repo, state, *freeargs, **opts)
1874
1874
1875
1875
1876 goalcontinue = b'continue'
1876 goalcontinue = b'continue'
1877 goalabort = b'abort'
1877 goalabort = b'abort'
1878 goaleditplan = b'edit-plan'
1878 goaleditplan = b'edit-plan'
1879 goalnew = b'new'
1879 goalnew = b'new'
1880
1880
1881
1881
1882 def _getgoal(opts):
1882 def _getgoal(opts):
1883 if opts.get(b'continue'):
1883 if opts.get(b'continue'):
1884 return goalcontinue
1884 return goalcontinue
1885 if opts.get(b'abort'):
1885 if opts.get(b'abort'):
1886 return goalabort
1886 return goalabort
1887 if opts.get(b'edit_plan'):
1887 if opts.get(b'edit_plan'):
1888 return goaleditplan
1888 return goaleditplan
1889 return goalnew
1889 return goalnew
1890
1890
1891
1891
1892 def _readfile(ui, path):
1892 def _readfile(ui, path):
1893 if path == b'-':
1893 if path == b'-':
1894 with ui.timeblockedsection(b'histedit'):
1894 with ui.timeblockedsection(b'histedit'):
1895 return ui.fin.read()
1895 return ui.fin.read()
1896 else:
1896 else:
1897 with open(path, b'rb') as f:
1897 with open(path, b'rb') as f:
1898 return f.read()
1898 return f.read()
1899
1899
1900
1900
1901 def _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs):
1901 def _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs):
1902 # TODO only abort if we try to histedit mq patches, not just
1902 # TODO only abort if we try to histedit mq patches, not just
1903 # blanket if mq patches are applied somewhere
1903 # blanket if mq patches are applied somewhere
1904 mq = getattr(repo, 'mq', None)
1904 mq = getattr(repo, 'mq', None)
1905 if mq and mq.applied:
1905 if mq and mq.applied:
1906 raise error.Abort(_(b'source has mq patches applied'))
1906 raise error.Abort(_(b'source has mq patches applied'))
1907
1907
1908 # basic argument incompatibility processing
1908 # basic argument incompatibility processing
1909 outg = opts.get(b'outgoing')
1909 outg = opts.get(b'outgoing')
1910 editplan = opts.get(b'edit_plan')
1910 editplan = opts.get(b'edit_plan')
1911 abort = opts.get(b'abort')
1911 abort = opts.get(b'abort')
1912 force = opts.get(b'force')
1912 force = opts.get(b'force')
1913 if force and not outg:
1913 if force and not outg:
1914 raise error.Abort(_(b'--force only allowed with --outgoing'))
1914 raise error.Abort(_(b'--force only allowed with --outgoing'))
1915 if goal == b'continue':
1915 if goal == b'continue':
1916 if any((outg, abort, revs, freeargs, rules, editplan)):
1916 if any((outg, abort, revs, freeargs, rules, editplan)):
1917 raise error.Abort(_(b'no arguments allowed with --continue'))
1917 raise error.Abort(_(b'no arguments allowed with --continue'))
1918 elif goal == b'abort':
1918 elif goal == b'abort':
1919 if any((outg, revs, freeargs, rules, editplan)):
1919 if any((outg, revs, freeargs, rules, editplan)):
1920 raise error.Abort(_(b'no arguments allowed with --abort'))
1920 raise error.Abort(_(b'no arguments allowed with --abort'))
1921 elif goal == b'edit-plan':
1921 elif goal == b'edit-plan':
1922 if any((outg, revs, freeargs)):
1922 if any((outg, revs, freeargs)):
1923 raise error.Abort(
1923 raise error.Abort(
1924 _(b'only --commands argument allowed with --edit-plan')
1924 _(b'only --commands argument allowed with --edit-plan')
1925 )
1925 )
1926 else:
1926 else:
1927 if state.inprogress():
1927 if state.inprogress():
1928 raise error.Abort(
1928 raise error.Abort(
1929 _(
1929 _(
1930 b'history edit already in progress, try '
1930 b'history edit already in progress, try '
1931 b'--continue or --abort'
1931 b'--continue or --abort'
1932 )
1932 )
1933 )
1933 )
1934 if outg:
1934 if outg:
1935 if revs:
1935 if revs:
1936 raise error.Abort(_(b'no revisions allowed with --outgoing'))
1936 raise error.Abort(_(b'no revisions allowed with --outgoing'))
1937 if len(freeargs) > 1:
1937 if len(freeargs) > 1:
1938 raise error.Abort(
1938 raise error.Abort(
1939 _(b'only one repo argument allowed with --outgoing')
1939 _(b'only one repo argument allowed with --outgoing')
1940 )
1940 )
1941 else:
1941 else:
1942 revs.extend(freeargs)
1942 revs.extend(freeargs)
1943 if len(revs) == 0:
1943 if len(revs) == 0:
1944 defaultrev = destutil.desthistedit(ui, repo)
1944 defaultrev = destutil.desthistedit(ui, repo)
1945 if defaultrev is not None:
1945 if defaultrev is not None:
1946 revs.append(defaultrev)
1946 revs.append(defaultrev)
1947
1947
1948 if len(revs) != 1:
1948 if len(revs) != 1:
1949 raise error.Abort(
1949 raise error.Abort(
1950 _(b'histedit requires exactly one ancestor revision')
1950 _(b'histedit requires exactly one ancestor revision')
1951 )
1951 )
1952
1952
1953
1953
1954 def _histedit(ui, repo, state, *freeargs, **opts):
1954 def _histedit(ui, repo, state, *freeargs, **opts):
1955 opts = pycompat.byteskwargs(opts)
1955 opts = pycompat.byteskwargs(opts)
1956 fm = ui.formatter(b'histedit', opts)
1956 fm = ui.formatter(b'histedit', opts)
1957 fm.startitem()
1957 fm.startitem()
1958 goal = _getgoal(opts)
1958 goal = _getgoal(opts)
1959 revs = opts.get(b'rev', [])
1959 revs = opts.get(b'rev', [])
1960 nobackup = not ui.configbool(b'rewrite', b'backup-bundle')
1960 nobackup = not ui.configbool(b'rewrite', b'backup-bundle')
1961 rules = opts.get(b'commands', b'')
1961 rules = opts.get(b'commands', b'')
1962 state.keep = opts.get(b'keep', False)
1962 state.keep = opts.get(b'keep', False)
1963
1963
1964 _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs)
1964 _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs)
1965
1965
1966 hastags = False
1966 hastags = False
1967 if revs:
1967 if revs:
1968 revs = scmutil.revrange(repo, revs)
1968 revs = scmutil.revrange(repo, revs)
1969 ctxs = [repo[rev] for rev in revs]
1969 ctxs = [repo[rev] for rev in revs]
1970 for ctx in ctxs:
1970 for ctx in ctxs:
1971 tags = [tag for tag in ctx.tags() if tag != b'tip']
1971 tags = [tag for tag in ctx.tags() if tag != b'tip']
1972 if not hastags:
1972 if not hastags:
1973 hastags = len(tags)
1973 hastags = len(tags)
1974 if hastags:
1974 if hastags:
1975 if ui.promptchoice(
1975 if ui.promptchoice(
1976 _(
1976 _(
1977 b'warning: tags associated with the given'
1977 b'warning: tags associated with the given'
1978 b' changeset will be lost after histedit.\n'
1978 b' changeset will be lost after histedit.\n'
1979 b'do you want to continue (yN)? $$ &Yes $$ &No'
1979 b'do you want to continue (yN)? $$ &Yes $$ &No'
1980 ),
1980 ),
1981 default=1,
1981 default=1,
1982 ):
1982 ):
1983 raise error.Abort(_(b'histedit cancelled\n'))
1983 raise error.Abort(_(b'histedit cancelled\n'))
1984 # rebuild state
1984 # rebuild state
1985 if goal == goalcontinue:
1985 if goal == goalcontinue:
1986 state.read()
1986 state.read()
1987 state = bootstrapcontinue(ui, state, opts)
1987 state = bootstrapcontinue(ui, state, opts)
1988 elif goal == goaleditplan:
1988 elif goal == goaleditplan:
1989 _edithisteditplan(ui, repo, state, rules)
1989 _edithisteditplan(ui, repo, state, rules)
1990 return
1990 return
1991 elif goal == goalabort:
1991 elif goal == goalabort:
1992 _aborthistedit(ui, repo, state, nobackup=nobackup)
1992 _aborthistedit(ui, repo, state, nobackup=nobackup)
1993 return
1993 return
1994 else:
1994 else:
1995 # goal == goalnew
1995 # goal == goalnew
1996 _newhistedit(ui, repo, state, revs, freeargs, opts)
1996 _newhistedit(ui, repo, state, revs, freeargs, opts)
1997
1997
1998 _continuehistedit(ui, repo, state)
1998 _continuehistedit(ui, repo, state)
1999 _finishhistedit(ui, repo, state, fm)
1999 _finishhistedit(ui, repo, state, fm)
2000 fm.end()
2000 fm.end()
2001
2001
2002
2002
2003 def _continuehistedit(ui, repo, state):
2003 def _continuehistedit(ui, repo, state):
2004 """This function runs after either:
2004 """This function runs after either:
2005 - bootstrapcontinue (if the goal is 'continue')
2005 - bootstrapcontinue (if the goal is 'continue')
2006 - _newhistedit (if the goal is 'new')
2006 - _newhistedit (if the goal is 'new')
2007 """
2007 """
2008 # preprocess rules so that we can hide inner folds from the user
2008 # preprocess rules so that we can hide inner folds from the user
2009 # and only show one editor
2009 # and only show one editor
2010 actions = state.actions[:]
2010 actions = state.actions[:]
2011 for idx, (action, nextact) in enumerate(zip(actions, actions[1:] + [None])):
2011 for idx, (action, nextact) in enumerate(zip(actions, actions[1:] + [None])):
2012 if action.verb == b'fold' and nextact and nextact.verb == b'fold':
2012 if action.verb == b'fold' and nextact and nextact.verb == b'fold':
2013 state.actions[idx].__class__ = _multifold
2013 state.actions[idx].__class__ = _multifold
2014
2014
2015 # Force an initial state file write, so the user can run --abort/continue
2015 # Force an initial state file write, so the user can run --abort/continue
2016 # even if there's an exception before the first transaction serialize.
2016 # even if there's an exception before the first transaction serialize.
2017 state.write()
2017 state.write()
2018
2018
2019 tr = None
2019 tr = None
2020 # Don't use singletransaction by default since it rolls the entire
2020 # Don't use singletransaction by default since it rolls the entire
2021 # transaction back if an unexpected exception happens (like a
2021 # transaction back if an unexpected exception happens (like a
2022 # pretxncommit hook throws, or the user aborts the commit msg editor).
2022 # pretxncommit hook throws, or the user aborts the commit msg editor).
2023 if ui.configbool(b"histedit", b"singletransaction"):
2023 if ui.configbool(b"histedit", b"singletransaction"):
2024 # Don't use a 'with' for the transaction, since actions may close
2024 # Don't use a 'with' for the transaction, since actions may close
2025 # and reopen a transaction. For example, if the action executes an
2025 # and reopen a transaction. For example, if the action executes an
2026 # external process it may choose to commit the transaction first.
2026 # external process it may choose to commit the transaction first.
2027 tr = repo.transaction(b'histedit')
2027 tr = repo.transaction(b'histedit')
2028 progress = ui.makeprogress(
2028 progress = ui.makeprogress(
2029 _(b"editing"), unit=_(b'changes'), total=len(state.actions)
2029 _(b"editing"), unit=_(b'changes'), total=len(state.actions)
2030 )
2030 )
2031 with progress, util.acceptintervention(tr):
2031 with progress, util.acceptintervention(tr):
2032 while state.actions:
2032 while state.actions:
2033 state.write(tr=tr)
2033 state.write(tr=tr)
2034 actobj = state.actions[0]
2034 actobj = state.actions[0]
2035 progress.increment(item=actobj.torule())
2035 progress.increment(item=actobj.torule())
2036 ui.debug(
2036 ui.debug(
2037 b'histedit: processing %s %s\n' % (actobj.verb, actobj.torule())
2037 b'histedit: processing %s %s\n' % (actobj.verb, actobj.torule())
2038 )
2038 )
2039 parentctx, replacement_ = actobj.run()
2039 parentctx, replacement_ = actobj.run()
2040 state.parentctxnode = parentctx.node()
2040 state.parentctxnode = parentctx.node()
2041 state.replacements.extend(replacement_)
2041 state.replacements.extend(replacement_)
2042 state.actions.pop(0)
2042 state.actions.pop(0)
2043
2043
2044 state.write()
2044 state.write()
2045
2045
2046
2046
2047 def _finishhistedit(ui, repo, state, fm):
2047 def _finishhistedit(ui, repo, state, fm):
2048 """This action runs when histedit is finishing its session"""
2048 """This action runs when histedit is finishing its session"""
2049 hg.updaterepo(repo, state.parentctxnode, overwrite=False)
2049 hg.updaterepo(repo, state.parentctxnode, overwrite=False)
2050
2050
2051 mapping, tmpnodes, created, ntm = processreplacement(state)
2051 mapping, tmpnodes, created, ntm = processreplacement(state)
2052 if mapping:
2052 if mapping:
2053 for prec, succs in pycompat.iteritems(mapping):
2053 for prec, succs in pycompat.iteritems(mapping):
2054 if not succs:
2054 if not succs:
2055 ui.debug(b'histedit: %s is dropped\n' % node.short(prec))
2055 ui.debug(b'histedit: %s is dropped\n' % node.short(prec))
2056 else:
2056 else:
2057 ui.debug(
2057 ui.debug(
2058 b'histedit: %s is replaced by %s\n'
2058 b'histedit: %s is replaced by %s\n'
2059 % (node.short(prec), node.short(succs[0]))
2059 % (node.short(prec), node.short(succs[0]))
2060 )
2060 )
2061 if len(succs) > 1:
2061 if len(succs) > 1:
2062 m = b'histedit: %s'
2062 m = b'histedit: %s'
2063 for n in succs[1:]:
2063 for n in succs[1:]:
2064 ui.debug(m % node.short(n))
2064 ui.debug(m % node.short(n))
2065
2065
2066 if not state.keep:
2066 if not state.keep:
2067 if mapping:
2067 if mapping:
2068 movetopmostbookmarks(repo, state.topmost, ntm)
2068 movetopmostbookmarks(repo, state.topmost, ntm)
2069 # TODO update mq state
2069 # TODO update mq state
2070 else:
2070 else:
2071 mapping = {}
2071 mapping = {}
2072
2072
2073 for n in tmpnodes:
2073 for n in tmpnodes:
2074 if n in repo:
2074 if n in repo:
2075 mapping[n] = ()
2075 mapping[n] = ()
2076
2076
2077 # remove entries about unknown nodes
2077 # remove entries about unknown nodes
2078 has_node = repo.unfiltered().changelog.index.has_node
2078 has_node = repo.unfiltered().changelog.index.has_node
2079 mapping = {
2079 mapping = {
2080 k: v
2080 k: v
2081 for k, v in mapping.items()
2081 for k, v in mapping.items()
2082 if has_node(k) and all(has_node(n) for n in v)
2082 if has_node(k) and all(has_node(n) for n in v)
2083 }
2083 }
2084 scmutil.cleanupnodes(repo, mapping, b'histedit')
2084 scmutil.cleanupnodes(repo, mapping, b'histedit')
2085 hf = fm.hexfunc
2085 hf = fm.hexfunc
2086 fl = fm.formatlist
2086 fl = fm.formatlist
2087 fd = fm.formatdict
2087 fd = fm.formatdict
2088 nodechanges = fd(
2088 nodechanges = fd(
2089 {
2089 {
2090 hf(oldn): fl([hf(n) for n in newn], name=b'node')
2090 hf(oldn): fl([hf(n) for n in newn], name=b'node')
2091 for oldn, newn in pycompat.iteritems(mapping)
2091 for oldn, newn in pycompat.iteritems(mapping)
2092 },
2092 },
2093 key=b"oldnode",
2093 key=b"oldnode",
2094 value=b"newnodes",
2094 value=b"newnodes",
2095 )
2095 )
2096 fm.data(nodechanges=nodechanges)
2096 fm.data(nodechanges=nodechanges)
2097
2097
2098 state.clear()
2098 state.clear()
2099 if os.path.exists(repo.sjoin(b'undo')):
2099 if os.path.exists(repo.sjoin(b'undo')):
2100 os.unlink(repo.sjoin(b'undo'))
2100 os.unlink(repo.sjoin(b'undo'))
2101 if repo.vfs.exists(b'histedit-last-edit.txt'):
2101 if repo.vfs.exists(b'histedit-last-edit.txt'):
2102 repo.vfs.unlink(b'histedit-last-edit.txt')
2102 repo.vfs.unlink(b'histedit-last-edit.txt')
2103
2103
2104
2104
2105 def _aborthistedit(ui, repo, state, nobackup=False):
2105 def _aborthistedit(ui, repo, state, nobackup=False):
2106 try:
2106 try:
2107 state.read()
2107 state.read()
2108 __, leafs, tmpnodes, __ = processreplacement(state)
2108 __, leafs, tmpnodes, __ = processreplacement(state)
2109 ui.debug(b'restore wc to old parent %s\n' % node.short(state.topmost))
2109 ui.debug(b'restore wc to old parent %s\n' % node.short(state.topmost))
2110
2110
2111 # Recover our old commits if necessary
2111 # Recover our old commits if necessary
2112 if not state.topmost in repo and state.backupfile:
2112 if not state.topmost in repo and state.backupfile:
2113 backupfile = repo.vfs.join(state.backupfile)
2113 backupfile = repo.vfs.join(state.backupfile)
2114 f = hg.openpath(ui, backupfile)
2114 f = hg.openpath(ui, backupfile)
2115 gen = exchange.readbundle(ui, f, backupfile)
2115 gen = exchange.readbundle(ui, f, backupfile)
2116 with repo.transaction(b'histedit.abort') as tr:
2116 with repo.transaction(b'histedit.abort') as tr:
2117 bundle2.applybundle(
2117 bundle2.applybundle(
2118 repo,
2118 repo,
2119 gen,
2119 gen,
2120 tr,
2120 tr,
2121 source=b'histedit',
2121 source=b'histedit',
2122 url=b'bundle:' + backupfile,
2122 url=b'bundle:' + backupfile,
2123 )
2123 )
2124
2124
2125 os.remove(backupfile)
2125 os.remove(backupfile)
2126
2126
2127 # check whether we should update away
2127 # check whether we should update away
2128 if repo.unfiltered().revs(
2128 if repo.unfiltered().revs(
2129 b'parents() and (%n or %ln::)',
2129 b'parents() and (%n or %ln::)',
2130 state.parentctxnode,
2130 state.parentctxnode,
2131 leafs | tmpnodes,
2131 leafs | tmpnodes,
2132 ):
2132 ):
2133 hg.clean(repo, state.topmost, show_stats=True, quietempty=True)
2133 hg.clean(repo, state.topmost, show_stats=True, quietempty=True)
2134 cleanupnode(ui, repo, tmpnodes, nobackup=nobackup)
2134 cleanupnode(ui, repo, tmpnodes, nobackup=nobackup)
2135 cleanupnode(ui, repo, leafs, nobackup=nobackup)
2135 cleanupnode(ui, repo, leafs, nobackup=nobackup)
2136 except Exception:
2136 except Exception:
2137 if state.inprogress():
2137 if state.inprogress():
2138 ui.warn(
2138 ui.warn(
2139 _(
2139 _(
2140 b'warning: encountered an exception during histedit '
2140 b'warning: encountered an exception during histedit '
2141 b'--abort; the repository may not have been completely '
2141 b'--abort; the repository may not have been completely '
2142 b'cleaned up\n'
2142 b'cleaned up\n'
2143 )
2143 )
2144 )
2144 )
2145 raise
2145 raise
2146 finally:
2146 finally:
2147 state.clear()
2147 state.clear()
2148
2148
2149
2149
2150 def hgaborthistedit(ui, repo):
2150 def hgaborthistedit(ui, repo):
2151 state = histeditstate(repo)
2151 state = histeditstate(repo)
2152 nobackup = not ui.configbool(b'rewrite', b'backup-bundle')
2152 nobackup = not ui.configbool(b'rewrite', b'backup-bundle')
2153 with repo.wlock() as wlock, repo.lock() as lock:
2153 with repo.wlock() as wlock, repo.lock() as lock:
2154 state.wlock = wlock
2154 state.wlock = wlock
2155 state.lock = lock
2155 state.lock = lock
2156 _aborthistedit(ui, repo, state, nobackup=nobackup)
2156 _aborthistedit(ui, repo, state, nobackup=nobackup)
2157
2157
2158
2158
2159 def _edithisteditplan(ui, repo, state, rules):
2159 def _edithisteditplan(ui, repo, state, rules):
2160 state.read()
2160 state.read()
2161 if not rules:
2161 if not rules:
2162 comment = geteditcomment(
2162 comment = geteditcomment(
2163 ui, node.short(state.parentctxnode), node.short(state.topmost)
2163 ui, node.short(state.parentctxnode), node.short(state.topmost)
2164 )
2164 )
2165 rules = ruleeditor(repo, ui, state.actions, comment)
2165 rules = ruleeditor(repo, ui, state.actions, comment)
2166 else:
2166 else:
2167 rules = _readfile(ui, rules)
2167 rules = _readfile(ui, rules)
2168 actions = parserules(rules, state)
2168 actions = parserules(rules, state)
2169 ctxs = [repo[act.node] for act in state.actions if act.node]
2169 ctxs = [repo[act.node] for act in state.actions if act.node]
2170 warnverifyactions(ui, repo, actions, state, ctxs)
2170 warnverifyactions(ui, repo, actions, state, ctxs)
2171 state.actions = actions
2171 state.actions = actions
2172 state.write()
2172 state.write()
2173
2173
2174
2174
2175 def _newhistedit(ui, repo, state, revs, freeargs, opts):
2175 def _newhistedit(ui, repo, state, revs, freeargs, opts):
2176 outg = opts.get(b'outgoing')
2176 outg = opts.get(b'outgoing')
2177 rules = opts.get(b'commands', b'')
2177 rules = opts.get(b'commands', b'')
2178 force = opts.get(b'force')
2178 force = opts.get(b'force')
2179
2179
2180 cmdutil.checkunfinished(repo)
2180 cmdutil.checkunfinished(repo)
2181 cmdutil.bailifchanged(repo)
2181 cmdutil.bailifchanged(repo)
2182
2182
2183 topmost = repo.dirstate.p1()
2183 topmost = repo.dirstate.p1()
2184 if outg:
2184 if outg:
2185 if freeargs:
2185 if freeargs:
2186 remote = freeargs[0]
2186 remote = freeargs[0]
2187 else:
2187 else:
2188 remote = None
2188 remote = None
2189 root = findoutgoing(ui, repo, remote, force, opts)
2189 root = findoutgoing(ui, repo, remote, force, opts)
2190 else:
2190 else:
2191 rr = list(repo.set(b'roots(%ld)', scmutil.revrange(repo, revs)))
2191 rr = list(repo.set(b'roots(%ld)', scmutil.revrange(repo, revs)))
2192 if len(rr) != 1:
2192 if len(rr) != 1:
2193 raise error.Abort(
2193 raise error.Abort(
2194 _(
2194 _(
2195 b'The specified revisions must have '
2195 b'The specified revisions must have '
2196 b'exactly one common root'
2196 b'exactly one common root'
2197 )
2197 )
2198 )
2198 )
2199 root = rr[0].node()
2199 root = rr[0].node()
2200
2200
2201 revs = between(repo, root, topmost, state.keep)
2201 revs = between(repo, root, topmost, state.keep)
2202 if not revs:
2202 if not revs:
2203 raise error.Abort(
2203 raise error.Abort(
2204 _(b'%s is not an ancestor of working directory') % node.short(root)
2204 _(b'%s is not an ancestor of working directory') % node.short(root)
2205 )
2205 )
2206
2206
2207 ctxs = [repo[r] for r in revs]
2207 ctxs = [repo[r] for r in revs]
2208
2208
2209 wctx = repo[None]
2209 wctx = repo[None]
2210 # Please don't ask me why `ancestors` is this value. I figured it
2210 # Please don't ask me why `ancestors` is this value. I figured it
2211 # out with print-debugging, not by actually understanding what the
2211 # out with print-debugging, not by actually understanding what the
2212 # merge code is doing. :(
2212 # merge code is doing. :(
2213 ancs = [repo[b'.']]
2213 ancs = [repo[b'.']]
2214 # Sniff-test to make sure we won't collide with untracked files in
2214 # Sniff-test to make sure we won't collide with untracked files in
2215 # the working directory. If we don't do this, we can get a
2215 # the working directory. If we don't do this, we can get a
2216 # collision after we've started histedit and backing out gets ugly
2216 # collision after we've started histedit and backing out gets ugly
2217 # for everyone, especially the user.
2217 # for everyone, especially the user.
2218 for c in [ctxs[0].p1()] + ctxs:
2218 for c in [ctxs[0].p1()] + ctxs:
2219 try:
2219 try:
2220 mergemod.calculateupdates(
2220 mergemod.calculateupdates(
2221 repo,
2221 repo,
2222 wctx,
2222 wctx,
2223 c,
2223 c,
2224 ancs,
2224 ancs,
2225 # These parameters were determined by print-debugging
2225 # These parameters were determined by print-debugging
2226 # what happens later on inside histedit.
2226 # what happens later on inside histedit.
2227 branchmerge=False,
2227 branchmerge=False,
2228 force=False,
2228 force=False,
2229 acceptremote=False,
2229 acceptremote=False,
2230 followcopies=False,
2230 followcopies=False,
2231 )
2231 )
2232 except error.Abort:
2232 except error.Abort:
2233 raise error.Abort(
2233 raise error.Abort(
2234 _(
2234 _(
2235 b"untracked files in working directory conflict with files in %s"
2235 b"untracked files in working directory conflict with files in %s"
2236 )
2236 )
2237 % c
2237 % c
2238 )
2238 )
2239
2239
2240 if not rules:
2240 if not rules:
2241 comment = geteditcomment(ui, node.short(root), node.short(topmost))
2241 comment = geteditcomment(ui, node.short(root), node.short(topmost))
2242 actions = [pick(state, r) for r in revs]
2242 actions = [pick(state, r) for r in revs]
2243 rules = ruleeditor(repo, ui, actions, comment)
2243 rules = ruleeditor(repo, ui, actions, comment)
2244 else:
2244 else:
2245 rules = _readfile(ui, rules)
2245 rules = _readfile(ui, rules)
2246 actions = parserules(rules, state)
2246 actions = parserules(rules, state)
2247 warnverifyactions(ui, repo, actions, state, ctxs)
2247 warnverifyactions(ui, repo, actions, state, ctxs)
2248
2248
2249 parentctxnode = repo[root].p1().node()
2249 parentctxnode = repo[root].p1().node()
2250
2250
2251 state.parentctxnode = parentctxnode
2251 state.parentctxnode = parentctxnode
2252 state.actions = actions
2252 state.actions = actions
2253 state.topmost = topmost
2253 state.topmost = topmost
2254 state.replacements = []
2254 state.replacements = []
2255
2255
2256 ui.log(
2256 ui.log(
2257 b"histedit",
2257 b"histedit",
2258 b"%d actions to histedit\n",
2258 b"%d actions to histedit\n",
2259 len(actions),
2259 len(actions),
2260 histedit_num_actions=len(actions),
2260 histedit_num_actions=len(actions),
2261 )
2261 )
2262
2262
2263 # Create a backup so we can always abort completely.
2263 # Create a backup so we can always abort completely.
2264 backupfile = None
2264 backupfile = None
2265 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2265 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2266 backupfile = repair.backupbundle(
2266 backupfile = repair.backupbundle(
2267 repo, [parentctxnode], [topmost], root, b'histedit'
2267 repo, [parentctxnode], [topmost], root, b'histedit'
2268 )
2268 )
2269 state.backupfile = backupfile
2269 state.backupfile = backupfile
2270
2270
2271
2271
2272 def _getsummary(ctx):
2272 def _getsummary(ctx):
2273 # a common pattern is to extract the summary but default to the empty
2273 # a common pattern is to extract the summary but default to the empty
2274 # string
2274 # string
2275 summary = ctx.description() or b''
2275 summary = ctx.description() or b''
2276 if summary:
2276 if summary:
2277 summary = summary.splitlines()[0]
2277 summary = summary.splitlines()[0]
2278 return summary
2278 return summary
2279
2279
2280
2280
2281 def bootstrapcontinue(ui, state, opts):
2281 def bootstrapcontinue(ui, state, opts):
2282 repo = state.repo
2282 repo = state.repo
2283
2283
2284 ms = mergemod.mergestate.read(repo)
2284 ms = mergemod.mergestate.read(repo)
2285 mergeutil.checkunresolved(ms)
2285 mergeutil.checkunresolved(ms)
2286
2286
2287 if state.actions:
2287 if state.actions:
2288 actobj = state.actions.pop(0)
2288 actobj = state.actions.pop(0)
2289
2289
2290 if _isdirtywc(repo):
2290 if _isdirtywc(repo):
2291 actobj.continuedirty()
2291 actobj.continuedirty()
2292 if _isdirtywc(repo):
2292 if _isdirtywc(repo):
2293 abortdirty()
2293 abortdirty()
2294
2294
2295 parentctx, replacements = actobj.continueclean()
2295 parentctx, replacements = actobj.continueclean()
2296
2296
2297 state.parentctxnode = parentctx.node()
2297 state.parentctxnode = parentctx.node()
2298 state.replacements.extend(replacements)
2298 state.replacements.extend(replacements)
2299
2299
2300 return state
2300 return state
2301
2301
2302
2302
2303 def between(repo, old, new, keep):
2303 def between(repo, old, new, keep):
2304 """select and validate the set of revision to edit
2304 """select and validate the set of revision to edit
2305
2305
2306 When keep is false, the specified set can't have children."""
2306 When keep is false, the specified set can't have children."""
2307 revs = repo.revs(b'%n::%n', old, new)
2307 revs = repo.revs(b'%n::%n', old, new)
2308 if revs and not keep:
2308 if revs and not keep:
2309 if not obsolete.isenabled(
2309 if not obsolete.isenabled(
2310 repo, obsolete.allowunstableopt
2310 repo, obsolete.allowunstableopt
2311 ) and repo.revs(b'(%ld::) - (%ld)', revs, revs):
2311 ) and repo.revs(b'(%ld::) - (%ld)', revs, revs):
2312 raise error.Abort(
2312 raise error.Abort(
2313 _(
2313 _(
2314 b'can only histedit a changeset together '
2314 b'can only histedit a changeset together '
2315 b'with all its descendants'
2315 b'with all its descendants'
2316 )
2316 )
2317 )
2317 )
2318 if repo.revs(b'(%ld) and merge()', revs):
2318 if repo.revs(b'(%ld) and merge()', revs):
2319 raise error.Abort(_(b'cannot edit history that contains merges'))
2319 raise error.Abort(_(b'cannot edit history that contains merges'))
2320 root = repo[revs.first()] # list is already sorted by repo.revs()
2320 root = repo[revs.first()] # list is already sorted by repo.revs()
2321 if not root.mutable():
2321 if not root.mutable():
2322 raise error.Abort(
2322 raise error.Abort(
2323 _(b'cannot edit public changeset: %s') % root,
2323 _(b'cannot edit public changeset: %s') % root,
2324 hint=_(b"see 'hg help phases' for details"),
2324 hint=_(b"see 'hg help phases' for details"),
2325 )
2325 )
2326 return pycompat.maplist(repo.changelog.node, revs)
2326 return pycompat.maplist(repo.changelog.node, revs)
2327
2327
2328
2328
2329 def ruleeditor(repo, ui, actions, editcomment=b""):
2329 def ruleeditor(repo, ui, actions, editcomment=b""):
2330 """open an editor to edit rules
2330 """open an editor to edit rules
2331
2331
2332 rules are in the format [ [act, ctx], ...] like in state.rules
2332 rules are in the format [ [act, ctx], ...] like in state.rules
2333 """
2333 """
2334 if repo.ui.configbool(b"experimental", b"histedit.autoverb"):
2334 if repo.ui.configbool(b"experimental", b"histedit.autoverb"):
2335 newact = util.sortdict()
2335 newact = util.sortdict()
2336 for act in actions:
2336 for act in actions:
2337 ctx = repo[act.node]
2337 ctx = repo[act.node]
2338 summary = _getsummary(ctx)
2338 summary = _getsummary(ctx)
2339 fword = summary.split(b' ', 1)[0].lower()
2339 fword = summary.split(b' ', 1)[0].lower()
2340 added = False
2340 added = False
2341
2341
2342 # if it doesn't end with the special character '!' just skip this
2342 # if it doesn't end with the special character '!' just skip this
2343 if fword.endswith(b'!'):
2343 if fword.endswith(b'!'):
2344 fword = fword[:-1]
2344 fword = fword[:-1]
2345 if fword in primaryactions | secondaryactions | tertiaryactions:
2345 if fword in primaryactions | secondaryactions | tertiaryactions:
2346 act.verb = fword
2346 act.verb = fword
2347 # get the target summary
2347 # get the target summary
2348 tsum = summary[len(fword) + 1 :].lstrip()
2348 tsum = summary[len(fword) + 1 :].lstrip()
2349 # safe but slow: reverse iterate over the actions so we
2349 # safe but slow: reverse iterate over the actions so we
2350 # don't clash on two commits having the same summary
2350 # don't clash on two commits having the same summary
2351 for na, l in reversed(list(pycompat.iteritems(newact))):
2351 for na, l in reversed(list(pycompat.iteritems(newact))):
2352 actx = repo[na.node]
2352 actx = repo[na.node]
2353 asum = _getsummary(actx)
2353 asum = _getsummary(actx)
2354 if asum == tsum:
2354 if asum == tsum:
2355 added = True
2355 added = True
2356 l.append(act)
2356 l.append(act)
2357 break
2357 break
2358
2358
2359 if not added:
2359 if not added:
2360 newact[act] = []
2360 newact[act] = []
2361
2361
2362 # copy over and flatten the new list
2362 # copy over and flatten the new list
2363 actions = []
2363 actions = []
2364 for na, l in pycompat.iteritems(newact):
2364 for na, l in pycompat.iteritems(newact):
2365 actions.append(na)
2365 actions.append(na)
2366 actions += l
2366 actions += l
2367
2367
2368 rules = b'\n'.join([act.torule() for act in actions])
2368 rules = b'\n'.join([act.torule() for act in actions])
2369 rules += b'\n\n'
2369 rules += b'\n\n'
2370 rules += editcomment
2370 rules += editcomment
2371 rules = ui.edit(
2371 rules = ui.edit(
2372 rules,
2372 rules,
2373 ui.username(),
2373 ui.username(),
2374 {b'prefix': b'histedit'},
2374 {b'prefix': b'histedit'},
2375 repopath=repo.path,
2375 repopath=repo.path,
2376 action=b'histedit',
2376 action=b'histedit',
2377 )
2377 )
2378
2378
2379 # Save edit rules in .hg/histedit-last-edit.txt in case
2379 # Save edit rules in .hg/histedit-last-edit.txt in case
2380 # the user needs to ask for help after something
2380 # the user needs to ask for help after something
2381 # surprising happens.
2381 # surprising happens.
2382 with repo.vfs(b'histedit-last-edit.txt', b'wb') as f:
2382 with repo.vfs(b'histedit-last-edit.txt', b'wb') as f:
2383 f.write(rules)
2383 f.write(rules)
2384
2384
2385 return rules
2385 return rules
2386
2386
2387
2387
2388 def parserules(rules, state):
2388 def parserules(rules, state):
2389 """Read the histedit rules string and return list of action objects """
2389 """Read the histedit rules string and return list of action objects """
2390 rules = [
2390 rules = [
2391 l
2391 l
2392 for l in (r.strip() for r in rules.splitlines())
2392 for l in (r.strip() for r in rules.splitlines())
2393 if l and not l.startswith(b'#')
2393 if l and not l.startswith(b'#')
2394 ]
2394 ]
2395 actions = []
2395 actions = []
2396 for r in rules:
2396 for r in rules:
2397 if b' ' not in r:
2397 if b' ' not in r:
2398 raise error.ParseError(_(b'malformed line "%s"') % r)
2398 raise error.ParseError(_(b'malformed line "%s"') % r)
2399 verb, rest = r.split(b' ', 1)
2399 verb, rest = r.split(b' ', 1)
2400
2400
2401 if verb not in actiontable:
2401 if verb not in actiontable:
2402 raise error.ParseError(_(b'unknown action "%s"') % verb)
2402 raise error.ParseError(_(b'unknown action "%s"') % verb)
2403
2403
2404 action = actiontable[verb].fromrule(state, rest)
2404 action = actiontable[verb].fromrule(state, rest)
2405 actions.append(action)
2405 actions.append(action)
2406 return actions
2406 return actions
2407
2407
2408
2408
2409 def warnverifyactions(ui, repo, actions, state, ctxs):
2409 def warnverifyactions(ui, repo, actions, state, ctxs):
2410 try:
2410 try:
2411 verifyactions(actions, state, ctxs)
2411 verifyactions(actions, state, ctxs)
2412 except error.ParseError:
2412 except error.ParseError:
2413 if repo.vfs.exists(b'histedit-last-edit.txt'):
2413 if repo.vfs.exists(b'histedit-last-edit.txt'):
2414 ui.warn(
2414 ui.warn(
2415 _(
2415 _(
2416 b'warning: histedit rules saved '
2416 b'warning: histedit rules saved '
2417 b'to: .hg/histedit-last-edit.txt\n'
2417 b'to: .hg/histedit-last-edit.txt\n'
2418 )
2418 )
2419 )
2419 )
2420 raise
2420 raise
2421
2421
2422
2422
2423 def verifyactions(actions, state, ctxs):
2423 def verifyactions(actions, state, ctxs):
2424 """Verify that there exists exactly one action per given changeset and
2424 """Verify that there exists exactly one action per given changeset and
2425 other constraints.
2425 other constraints.
2426
2426
2427 Will abort if there are to many or too few rules, a malformed rule,
2427 Will abort if there are to many or too few rules, a malformed rule,
2428 or a rule on a changeset outside of the user-given range.
2428 or a rule on a changeset outside of the user-given range.
2429 """
2429 """
2430 expected = set(c.node() for c in ctxs)
2430 expected = set(c.node() for c in ctxs)
2431 seen = set()
2431 seen = set()
2432 prev = None
2432 prev = None
2433
2433
2434 if actions and actions[0].verb in [b'roll', b'fold']:
2434 if actions and actions[0].verb in [b'roll', b'fold']:
2435 raise error.ParseError(
2435 raise error.ParseError(
2436 _(b'first changeset cannot use verb "%s"') % actions[0].verb
2436 _(b'first changeset cannot use verb "%s"') % actions[0].verb
2437 )
2437 )
2438
2438
2439 for action in actions:
2439 for action in actions:
2440 action.verify(prev, expected, seen)
2440 action.verify(prev, expected, seen)
2441 prev = action
2441 prev = action
2442 if action.node is not None:
2442 if action.node is not None:
2443 seen.add(action.node)
2443 seen.add(action.node)
2444 missing = sorted(expected - seen) # sort to stabilize output
2444 missing = sorted(expected - seen) # sort to stabilize output
2445
2445
2446 if state.repo.ui.configbool(b'histedit', b'dropmissing'):
2446 if state.repo.ui.configbool(b'histedit', b'dropmissing'):
2447 if len(actions) == 0:
2447 if len(actions) == 0:
2448 raise error.ParseError(
2448 raise error.ParseError(
2449 _(b'no rules provided'),
2449 _(b'no rules provided'),
2450 hint=_(b'use strip extension to remove commits'),
2450 hint=_(b'use strip extension to remove commits'),
2451 )
2451 )
2452
2452
2453 drops = [drop(state, n) for n in missing]
2453 drops = [drop(state, n) for n in missing]
2454 # put the in the beginning so they execute immediately and
2454 # put the in the beginning so they execute immediately and
2455 # don't show in the edit-plan in the future
2455 # don't show in the edit-plan in the future
2456 actions[:0] = drops
2456 actions[:0] = drops
2457 elif missing:
2457 elif missing:
2458 raise error.ParseError(
2458 raise error.ParseError(
2459 _(b'missing rules for changeset %s') % node.short(missing[0]),
2459 _(b'missing rules for changeset %s') % node.short(missing[0]),
2460 hint=_(
2460 hint=_(
2461 b'use "drop %s" to discard, see also: '
2461 b'use "drop %s" to discard, see also: '
2462 b"'hg help -e histedit.config'"
2462 b"'hg help -e histedit.config'"
2463 )
2463 )
2464 % node.short(missing[0]),
2464 % node.short(missing[0]),
2465 )
2465 )
2466
2466
2467
2467
2468 def adjustreplacementsfrommarkers(repo, oldreplacements):
2468 def adjustreplacementsfrommarkers(repo, oldreplacements):
2469 """Adjust replacements from obsolescence markers
2469 """Adjust replacements from obsolescence markers
2470
2470
2471 Replacements structure is originally generated based on
2471 Replacements structure is originally generated based on
2472 histedit's state and does not account for changes that are
2472 histedit's state and does not account for changes that are
2473 not recorded there. This function fixes that by adding
2473 not recorded there. This function fixes that by adding
2474 data read from obsolescence markers"""
2474 data read from obsolescence markers"""
2475 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2475 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2476 return oldreplacements
2476 return oldreplacements
2477
2477
2478 unfi = repo.unfiltered()
2478 unfi = repo.unfiltered()
2479 get_rev = unfi.changelog.index.get_rev
2479 get_rev = unfi.changelog.index.get_rev
2480 obsstore = repo.obsstore
2480 obsstore = repo.obsstore
2481 newreplacements = list(oldreplacements)
2481 newreplacements = list(oldreplacements)
2482 oldsuccs = [r[1] for r in oldreplacements]
2482 oldsuccs = [r[1] for r in oldreplacements]
2483 # successors that have already been added to succstocheck once
2483 # successors that have already been added to succstocheck once
2484 seensuccs = set().union(
2484 seensuccs = set().union(
2485 *oldsuccs
2485 *oldsuccs
2486 ) # create a set from an iterable of tuples
2486 ) # create a set from an iterable of tuples
2487 succstocheck = list(seensuccs)
2487 succstocheck = list(seensuccs)
2488 while succstocheck:
2488 while succstocheck:
2489 n = succstocheck.pop()
2489 n = succstocheck.pop()
2490 missing = get_rev(n) is None
2490 missing = get_rev(n) is None
2491 markers = obsstore.successors.get(n, ())
2491 markers = obsstore.successors.get(n, ())
2492 if missing and not markers:
2492 if missing and not markers:
2493 # dead end, mark it as such
2493 # dead end, mark it as such
2494 newreplacements.append((n, ()))
2494 newreplacements.append((n, ()))
2495 for marker in markers:
2495 for marker in markers:
2496 nsuccs = marker[1]
2496 nsuccs = marker[1]
2497 newreplacements.append((n, nsuccs))
2497 newreplacements.append((n, nsuccs))
2498 for nsucc in nsuccs:
2498 for nsucc in nsuccs:
2499 if nsucc not in seensuccs:
2499 if nsucc not in seensuccs:
2500 seensuccs.add(nsucc)
2500 seensuccs.add(nsucc)
2501 succstocheck.append(nsucc)
2501 succstocheck.append(nsucc)
2502
2502
2503 return newreplacements
2503 return newreplacements
2504
2504
2505
2505
2506 def processreplacement(state):
2506 def processreplacement(state):
2507 """process the list of replacements to return
2507 """process the list of replacements to return
2508
2508
2509 1) the final mapping between original and created nodes
2509 1) the final mapping between original and created nodes
2510 2) the list of temporary node created by histedit
2510 2) the list of temporary node created by histedit
2511 3) the list of new commit created by histedit"""
2511 3) the list of new commit created by histedit"""
2512 replacements = adjustreplacementsfrommarkers(state.repo, state.replacements)
2512 replacements = adjustreplacementsfrommarkers(state.repo, state.replacements)
2513 allsuccs = set()
2513 allsuccs = set()
2514 replaced = set()
2514 replaced = set()
2515 fullmapping = {}
2515 fullmapping = {}
2516 # initialize basic set
2516 # initialize basic set
2517 # fullmapping records all operations recorded in replacement
2517 # fullmapping records all operations recorded in replacement
2518 for rep in replacements:
2518 for rep in replacements:
2519 allsuccs.update(rep[1])
2519 allsuccs.update(rep[1])
2520 replaced.add(rep[0])
2520 replaced.add(rep[0])
2521 fullmapping.setdefault(rep[0], set()).update(rep[1])
2521 fullmapping.setdefault(rep[0], set()).update(rep[1])
2522 new = allsuccs - replaced
2522 new = allsuccs - replaced
2523 tmpnodes = allsuccs & replaced
2523 tmpnodes = allsuccs & replaced
2524 # Reduce content fullmapping into direct relation between original nodes
2524 # Reduce content fullmapping into direct relation between original nodes
2525 # and final node created during history edition
2525 # and final node created during history edition
2526 # Dropped changeset are replaced by an empty list
2526 # Dropped changeset are replaced by an empty list
2527 toproceed = set(fullmapping)
2527 toproceed = set(fullmapping)
2528 final = {}
2528 final = {}
2529 while toproceed:
2529 while toproceed:
2530 for x in list(toproceed):
2530 for x in list(toproceed):
2531 succs = fullmapping[x]
2531 succs = fullmapping[x]
2532 for s in list(succs):
2532 for s in list(succs):
2533 if s in toproceed:
2533 if s in toproceed:
2534 # non final node with unknown closure
2534 # non final node with unknown closure
2535 # We can't process this now
2535 # We can't process this now
2536 break
2536 break
2537 elif s in final:
2537 elif s in final:
2538 # non final node, replace with closure
2538 # non final node, replace with closure
2539 succs.remove(s)
2539 succs.remove(s)
2540 succs.update(final[s])
2540 succs.update(final[s])
2541 else:
2541 else:
2542 final[x] = succs
2542 final[x] = succs
2543 toproceed.remove(x)
2543 toproceed.remove(x)
2544 # remove tmpnodes from final mapping
2544 # remove tmpnodes from final mapping
2545 for n in tmpnodes:
2545 for n in tmpnodes:
2546 del final[n]
2546 del final[n]
2547 # we expect all changes involved in final to exist in the repo
2547 # we expect all changes involved in final to exist in the repo
2548 # turn `final` into list (topologically sorted)
2548 # turn `final` into list (topologically sorted)
2549 get_rev = state.repo.changelog.index.get_rev
2549 get_rev = state.repo.changelog.index.get_rev
2550 for prec, succs in final.items():
2550 for prec, succs in final.items():
2551 final[prec] = sorted(succs, key=get_rev)
2551 final[prec] = sorted(succs, key=get_rev)
2552
2552
2553 # computed topmost element (necessary for bookmark)
2553 # computed topmost element (necessary for bookmark)
2554 if new:
2554 if new:
2555 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
2555 newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
2556 elif not final:
2556 elif not final:
2557 # Nothing rewritten at all. we won't need `newtopmost`
2557 # Nothing rewritten at all. we won't need `newtopmost`
2558 # It is the same as `oldtopmost` and `processreplacement` know it
2558 # It is the same as `oldtopmost` and `processreplacement` know it
2559 newtopmost = None
2559 newtopmost = None
2560 else:
2560 else:
2561 # every body died. The newtopmost is the parent of the root.
2561 # every body died. The newtopmost is the parent of the root.
2562 r = state.repo.changelog.rev
2562 r = state.repo.changelog.rev
2563 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
2563 newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
2564
2564
2565 return final, tmpnodes, new, newtopmost
2565 return final, tmpnodes, new, newtopmost
2566
2566
2567
2567
2568 def movetopmostbookmarks(repo, oldtopmost, newtopmost):
2568 def movetopmostbookmarks(repo, oldtopmost, newtopmost):
2569 """Move bookmark from oldtopmost to newly created topmost
2569 """Move bookmark from oldtopmost to newly created topmost
2570
2570
2571 This is arguably a feature and we may only want that for the active
2571 This is arguably a feature and we may only want that for the active
2572 bookmark. But the behavior is kept compatible with the old version for now.
2572 bookmark. But the behavior is kept compatible with the old version for now.
2573 """
2573 """
2574 if not oldtopmost or not newtopmost:
2574 if not oldtopmost or not newtopmost:
2575 return
2575 return
2576 oldbmarks = repo.nodebookmarks(oldtopmost)
2576 oldbmarks = repo.nodebookmarks(oldtopmost)
2577 if oldbmarks:
2577 if oldbmarks:
2578 with repo.lock(), repo.transaction(b'histedit') as tr:
2578 with repo.lock(), repo.transaction(b'histedit') as tr:
2579 marks = repo._bookmarks
2579 marks = repo._bookmarks
2580 changes = []
2580 changes = []
2581 for name in oldbmarks:
2581 for name in oldbmarks:
2582 changes.append((name, newtopmost))
2582 changes.append((name, newtopmost))
2583 marks.applychanges(repo, tr, changes)
2583 marks.applychanges(repo, tr, changes)
2584
2584
2585
2585
2586 def cleanupnode(ui, repo, nodes, nobackup=False):
2586 def cleanupnode(ui, repo, nodes, nobackup=False):
2587 """strip a group of nodes from the repository
2587 """strip a group of nodes from the repository
2588
2588
2589 The set of node to strip may contains unknown nodes."""
2589 The set of node to strip may contains unknown nodes."""
2590 with repo.lock():
2590 with repo.lock():
2591 # do not let filtering get in the way of the cleanse
2591 # do not let filtering get in the way of the cleanse
2592 # we should probably get rid of obsolescence marker created during the
2592 # we should probably get rid of obsolescence marker created during the
2593 # histedit, but we currently do not have such information.
2593 # histedit, but we currently do not have such information.
2594 repo = repo.unfiltered()
2594 repo = repo.unfiltered()
2595 # Find all nodes that need to be stripped
2595 # Find all nodes that need to be stripped
2596 # (we use %lr instead of %ln to silently ignore unknown items)
2596 # (we use %lr instead of %ln to silently ignore unknown items)
2597 has_node = repo.changelog.index.has_node
2597 has_node = repo.changelog.index.has_node
2598 nodes = sorted(n for n in nodes if has_node(n))
2598 nodes = sorted(n for n in nodes if has_node(n))
2599 roots = [c.node() for c in repo.set(b"roots(%ln)", nodes)]
2599 roots = [c.node() for c in repo.set(b"roots(%ln)", nodes)]
2600 if roots:
2600 if roots:
2601 backup = not nobackup
2601 backup = not nobackup
2602 repair.strip(ui, repo, roots, backup=backup)
2602 repair.strip(ui, repo, roots, backup=backup)
2603
2603
2604
2604
2605 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
2605 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
2606 if isinstance(nodelist, str):
2606 if isinstance(nodelist, bytes):
2607 nodelist = [nodelist]
2607 nodelist = [nodelist]
2608 state = histeditstate(repo)
2608 state = histeditstate(repo)
2609 if state.inprogress():
2609 if state.inprogress():
2610 state.read()
2610 state.read()
2611 histedit_nodes = {
2611 histedit_nodes = {
2612 action.node for action in state.actions if action.node
2612 action.node for action in state.actions if action.node
2613 }
2613 }
2614 common_nodes = histedit_nodes & set(nodelist)
2614 common_nodes = histedit_nodes & set(nodelist)
2615 if common_nodes:
2615 if common_nodes:
2616 raise error.Abort(
2616 raise error.Abort(
2617 _(b"histedit in progress, can't strip %s")
2617 _(b"histedit in progress, can't strip %s")
2618 % b', '.join(node.short(x) for x in common_nodes)
2618 % b', '.join(node.short(x) for x in common_nodes)
2619 )
2619 )
2620 return orig(ui, repo, nodelist, *args, **kwargs)
2620 return orig(ui, repo, nodelist, *args, **kwargs)
2621
2621
2622
2622
2623 extensions.wrapfunction(repair, b'strip', stripwrapper)
2623 extensions.wrapfunction(repair, b'strip', stripwrapper)
2624
2624
2625
2625
2626 def summaryhook(ui, repo):
2626 def summaryhook(ui, repo):
2627 state = histeditstate(repo)
2627 state = histeditstate(repo)
2628 if not state.inprogress():
2628 if not state.inprogress():
2629 return
2629 return
2630 state.read()
2630 state.read()
2631 if state.actions:
2631 if state.actions:
2632 # i18n: column positioning for "hg summary"
2632 # i18n: column positioning for "hg summary"
2633 ui.write(
2633 ui.write(
2634 _(b'hist: %s (histedit --continue)\n')
2634 _(b'hist: %s (histedit --continue)\n')
2635 % (
2635 % (
2636 ui.label(_(b'%d remaining'), b'histedit.remaining')
2636 ui.label(_(b'%d remaining'), b'histedit.remaining')
2637 % len(state.actions)
2637 % len(state.actions)
2638 )
2638 )
2639 )
2639 )
2640
2640
2641
2641
2642 def extsetup(ui):
2642 def extsetup(ui):
2643 cmdutil.summaryhooks.add(b'histedit', summaryhook)
2643 cmdutil.summaryhooks.add(b'histedit', summaryhook)
2644 statemod.addunfinished(
2644 statemod.addunfinished(
2645 b'histedit',
2645 b'histedit',
2646 fname=b'histedit-state',
2646 fname=b'histedit-state',
2647 allowcommit=True,
2647 allowcommit=True,
2648 continueflag=True,
2648 continueflag=True,
2649 abortfunc=hgaborthistedit,
2649 abortfunc=hgaborthistedit,
2650 )
2650 )
@@ -1,221 +1,221 b''
1 # win32mbcs.py -- MBCS filename support for Mercurial
1 # win32mbcs.py -- MBCS filename support for Mercurial
2 #
2 #
3 # Copyright (c) 2008 Shun-ichi Goto <shunichi.goto@gmail.com>
3 # Copyright (c) 2008 Shun-ichi Goto <shunichi.goto@gmail.com>
4 #
4 #
5 # Version: 0.3
5 # Version: 0.3
6 # Author: Shun-ichi Goto <shunichi.goto@gmail.com>
6 # Author: Shun-ichi Goto <shunichi.goto@gmail.com>
7 #
7 #
8 # This software may be used and distributed according to the terms of the
8 # This software may be used and distributed according to the terms of the
9 # GNU General Public License version 2 or any later version.
9 # GNU General Public License version 2 or any later version.
10 #
10 #
11
11
12 '''allow the use of MBCS paths with problematic encodings
12 '''allow the use of MBCS paths with problematic encodings
13
13
14 Some MBCS encodings are not good for some path operations (i.e.
14 Some MBCS encodings are not good for some path operations (i.e.
15 splitting path, case conversion, etc.) with its encoded bytes. We call
15 splitting path, case conversion, etc.) with its encoded bytes. We call
16 such a encoding (i.e. shift_jis and big5) as "problematic encoding".
16 such a encoding (i.e. shift_jis and big5) as "problematic encoding".
17 This extension can be used to fix the issue with those encodings by
17 This extension can be used to fix the issue with those encodings by
18 wrapping some functions to convert to Unicode string before path
18 wrapping some functions to convert to Unicode string before path
19 operation.
19 operation.
20
20
21 This extension is useful for:
21 This extension is useful for:
22
22
23 - Japanese Windows users using shift_jis encoding.
23 - Japanese Windows users using shift_jis encoding.
24 - Chinese Windows users using big5 encoding.
24 - Chinese Windows users using big5 encoding.
25 - All users who use a repository with one of problematic encodings on
25 - All users who use a repository with one of problematic encodings on
26 case-insensitive file system.
26 case-insensitive file system.
27
27
28 This extension is not needed for:
28 This extension is not needed for:
29
29
30 - Any user who use only ASCII chars in path.
30 - Any user who use only ASCII chars in path.
31 - Any user who do not use any of problematic encodings.
31 - Any user who do not use any of problematic encodings.
32
32
33 Note that there are some limitations on using this extension:
33 Note that there are some limitations on using this extension:
34
34
35 - You should use single encoding in one repository.
35 - You should use single encoding in one repository.
36 - If the repository path ends with 0x5c, .hg/hgrc cannot be read.
36 - If the repository path ends with 0x5c, .hg/hgrc cannot be read.
37 - win32mbcs is not compatible with fixutf8 extension.
37 - win32mbcs is not compatible with fixutf8 extension.
38
38
39 By default, win32mbcs uses encoding.encoding decided by Mercurial.
39 By default, win32mbcs uses encoding.encoding decided by Mercurial.
40 You can specify the encoding by config option::
40 You can specify the encoding by config option::
41
41
42 [win32mbcs]
42 [win32mbcs]
43 encoding = sjis
43 encoding = sjis
44
44
45 It is useful for the users who want to commit with UTF-8 log message.
45 It is useful for the users who want to commit with UTF-8 log message.
46 '''
46 '''
47 from __future__ import absolute_import
47 from __future__ import absolute_import
48
48
49 import os
49 import os
50 import sys
50 import sys
51
51
52 from mercurial.i18n import _
52 from mercurial.i18n import _
53 from mercurial.pycompat import getattr, setattr
53 from mercurial.pycompat import getattr, setattr
54 from mercurial import (
54 from mercurial import (
55 encoding,
55 encoding,
56 error,
56 error,
57 pycompat,
57 pycompat,
58 registrar,
58 registrar,
59 )
59 )
60
60
61 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
61 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
62 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
62 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
63 # be specifying the version(s) of Mercurial they are tested with, or
63 # be specifying the version(s) of Mercurial they are tested with, or
64 # leave the attribute unspecified.
64 # leave the attribute unspecified.
65 testedwith = b'ships-with-hg-core'
65 testedwith = b'ships-with-hg-core'
66
66
67 configtable = {}
67 configtable = {}
68 configitem = registrar.configitem(configtable)
68 configitem = registrar.configitem(configtable)
69
69
70 # Encoding.encoding may be updated by --encoding option.
70 # Encoding.encoding may be updated by --encoding option.
71 # Use a lambda do delay the resolution.
71 # Use a lambda do delay the resolution.
72 configitem(
72 configitem(
73 b'win32mbcs', b'encoding', default=lambda: encoding.encoding,
73 b'win32mbcs', b'encoding', default=lambda: encoding.encoding,
74 )
74 )
75
75
76 _encoding = None # see extsetup
76 _encoding = None # see extsetup
77
77
78
78
79 def decode(arg):
79 def decode(arg):
80 if isinstance(arg, str):
80 if isinstance(arg, bytes):
81 uarg = arg.decode(_encoding)
81 uarg = arg.decode(_encoding)
82 if arg == uarg.encode(_encoding):
82 if arg == uarg.encode(_encoding):
83 return uarg
83 return uarg
84 raise UnicodeError(b"Not local encoding")
84 raise UnicodeError(b"Not local encoding")
85 elif isinstance(arg, tuple):
85 elif isinstance(arg, tuple):
86 return tuple(map(decode, arg))
86 return tuple(map(decode, arg))
87 elif isinstance(arg, list):
87 elif isinstance(arg, list):
88 return map(decode, arg)
88 return map(decode, arg)
89 elif isinstance(arg, dict):
89 elif isinstance(arg, dict):
90 for k, v in arg.items():
90 for k, v in arg.items():
91 arg[k] = decode(v)
91 arg[k] = decode(v)
92 return arg
92 return arg
93
93
94
94
95 def encode(arg):
95 def encode(arg):
96 if isinstance(arg, pycompat.unicode):
96 if isinstance(arg, pycompat.unicode):
97 return arg.encode(_encoding)
97 return arg.encode(_encoding)
98 elif isinstance(arg, tuple):
98 elif isinstance(arg, tuple):
99 return tuple(map(encode, arg))
99 return tuple(map(encode, arg))
100 elif isinstance(arg, list):
100 elif isinstance(arg, list):
101 return map(encode, arg)
101 return map(encode, arg)
102 elif isinstance(arg, dict):
102 elif isinstance(arg, dict):
103 for k, v in arg.items():
103 for k, v in arg.items():
104 arg[k] = encode(v)
104 arg[k] = encode(v)
105 return arg
105 return arg
106
106
107
107
108 def appendsep(s):
108 def appendsep(s):
109 # ensure the path ends with os.sep, appending it if necessary.
109 # ensure the path ends with os.sep, appending it if necessary.
110 try:
110 try:
111 us = decode(s)
111 us = decode(s)
112 except UnicodeError:
112 except UnicodeError:
113 us = s
113 us = s
114 if us and us[-1] not in b':/\\':
114 if us and us[-1] not in b':/\\':
115 s += pycompat.ossep
115 s += pycompat.ossep
116 return s
116 return s
117
117
118
118
119 def basewrapper(func, argtype, enc, dec, args, kwds):
119 def basewrapper(func, argtype, enc, dec, args, kwds):
120 # check check already converted, then call original
120 # check check already converted, then call original
121 for arg in args:
121 for arg in args:
122 if isinstance(arg, argtype):
122 if isinstance(arg, argtype):
123 return func(*args, **kwds)
123 return func(*args, **kwds)
124
124
125 try:
125 try:
126 # convert string arguments, call func, then convert back the
126 # convert string arguments, call func, then convert back the
127 # return value.
127 # return value.
128 return enc(func(*dec(args), **dec(kwds)))
128 return enc(func(*dec(args), **dec(kwds)))
129 except UnicodeError:
129 except UnicodeError:
130 raise error.Abort(
130 raise error.Abort(
131 _(b"[win32mbcs] filename conversion failed with %s encoding\n")
131 _(b"[win32mbcs] filename conversion failed with %s encoding\n")
132 % _encoding
132 % _encoding
133 )
133 )
134
134
135
135
136 def wrapper(func, args, kwds):
136 def wrapper(func, args, kwds):
137 return basewrapper(func, pycompat.unicode, encode, decode, args, kwds)
137 return basewrapper(func, pycompat.unicode, encode, decode, args, kwds)
138
138
139
139
140 def reversewrapper(func, args, kwds):
140 def reversewrapper(func, args, kwds):
141 return basewrapper(func, str, decode, encode, args, kwds)
141 return basewrapper(func, str, decode, encode, args, kwds)
142
142
143
143
144 def wrapperforlistdir(func, args, kwds):
144 def wrapperforlistdir(func, args, kwds):
145 # Ensure 'path' argument ends with os.sep to avoids
145 # Ensure 'path' argument ends with os.sep to avoids
146 # misinterpreting last 0x5c of MBCS 2nd byte as path separator.
146 # misinterpreting last 0x5c of MBCS 2nd byte as path separator.
147 if args:
147 if args:
148 args = list(args)
148 args = list(args)
149 args[0] = appendsep(args[0])
149 args[0] = appendsep(args[0])
150 if b'path' in kwds:
150 if b'path' in kwds:
151 kwds[b'path'] = appendsep(kwds[b'path'])
151 kwds[b'path'] = appendsep(kwds[b'path'])
152 return func(*args, **kwds)
152 return func(*args, **kwds)
153
153
154
154
155 def wrapname(name, wrapper):
155 def wrapname(name, wrapper):
156 module, name = name.rsplit(b'.', 1)
156 module, name = name.rsplit(b'.', 1)
157 module = sys.modules[module]
157 module = sys.modules[module]
158 func = getattr(module, name)
158 func = getattr(module, name)
159
159
160 def f(*args, **kwds):
160 def f(*args, **kwds):
161 return wrapper(func, args, kwds)
161 return wrapper(func, args, kwds)
162
162
163 f.__name__ = func.__name__
163 f.__name__ = func.__name__
164 setattr(module, name, f)
164 setattr(module, name, f)
165
165
166
166
167 # List of functions to be wrapped.
167 # List of functions to be wrapped.
168 # NOTE: os.path.dirname() and os.path.basename() are safe because
168 # NOTE: os.path.dirname() and os.path.basename() are safe because
169 # they use result of os.path.split()
169 # they use result of os.path.split()
170 funcs = b'''os.path.join os.path.split os.path.splitext
170 funcs = b'''os.path.join os.path.split os.path.splitext
171 os.path.normpath os.makedirs mercurial.util.endswithsep
171 os.path.normpath os.makedirs mercurial.util.endswithsep
172 mercurial.util.splitpath mercurial.util.fscasesensitive
172 mercurial.util.splitpath mercurial.util.fscasesensitive
173 mercurial.util.fspath mercurial.util.pconvert mercurial.util.normpath
173 mercurial.util.fspath mercurial.util.pconvert mercurial.util.normpath
174 mercurial.util.checkwinfilename mercurial.util.checkosfilename
174 mercurial.util.checkwinfilename mercurial.util.checkosfilename
175 mercurial.util.split'''
175 mercurial.util.split'''
176
176
177 # These functions are required to be called with local encoded string
177 # These functions are required to be called with local encoded string
178 # because they expects argument is local encoded string and cause
178 # because they expects argument is local encoded string and cause
179 # problem with unicode string.
179 # problem with unicode string.
180 rfuncs = b'''mercurial.encoding.upper mercurial.encoding.lower
180 rfuncs = b'''mercurial.encoding.upper mercurial.encoding.lower
181 mercurial.util._filenamebytestr'''
181 mercurial.util._filenamebytestr'''
182
182
183 # List of Windows specific functions to be wrapped.
183 # List of Windows specific functions to be wrapped.
184 winfuncs = b'''os.path.splitunc'''
184 winfuncs = b'''os.path.splitunc'''
185
185
186 # codec and alias names of sjis and big5 to be faked.
186 # codec and alias names of sjis and big5 to be faked.
187 problematic_encodings = b'''big5 big5-tw csbig5 big5hkscs big5-hkscs
187 problematic_encodings = b'''big5 big5-tw csbig5 big5hkscs big5-hkscs
188 hkscs cp932 932 ms932 mskanji ms-kanji shift_jis csshiftjis shiftjis
188 hkscs cp932 932 ms932 mskanji ms-kanji shift_jis csshiftjis shiftjis
189 sjis s_jis shift_jis_2004 shiftjis2004 sjis_2004 sjis2004
189 sjis s_jis shift_jis_2004 shiftjis2004 sjis_2004 sjis2004
190 shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213 950 cp950 ms950 '''
190 shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213 950 cp950 ms950 '''
191
191
192
192
193 def extsetup(ui):
193 def extsetup(ui):
194 # TODO: decide use of config section for this extension
194 # TODO: decide use of config section for this extension
195 if (not os.path.supports_unicode_filenames) and (
195 if (not os.path.supports_unicode_filenames) and (
196 pycompat.sysplatform != b'cygwin'
196 pycompat.sysplatform != b'cygwin'
197 ):
197 ):
198 ui.warn(_(b"[win32mbcs] cannot activate on this platform.\n"))
198 ui.warn(_(b"[win32mbcs] cannot activate on this platform.\n"))
199 return
199 return
200 # determine encoding for filename
200 # determine encoding for filename
201 global _encoding
201 global _encoding
202 _encoding = ui.config(b'win32mbcs', b'encoding')
202 _encoding = ui.config(b'win32mbcs', b'encoding')
203 # fake is only for relevant environment.
203 # fake is only for relevant environment.
204 if _encoding.lower() in problematic_encodings.split():
204 if _encoding.lower() in problematic_encodings.split():
205 for f in funcs.split():
205 for f in funcs.split():
206 wrapname(f, wrapper)
206 wrapname(f, wrapper)
207 if pycompat.iswindows:
207 if pycompat.iswindows:
208 for f in winfuncs.split():
208 for f in winfuncs.split():
209 wrapname(f, wrapper)
209 wrapname(f, wrapper)
210 wrapname(b"mercurial.util.listdir", wrapperforlistdir)
210 wrapname(b"mercurial.util.listdir", wrapperforlistdir)
211 wrapname(b"mercurial.windows.listdir", wrapperforlistdir)
211 wrapname(b"mercurial.windows.listdir", wrapperforlistdir)
212 # wrap functions to be called with local byte string arguments
212 # wrap functions to be called with local byte string arguments
213 for f in rfuncs.split():
213 for f in rfuncs.split():
214 wrapname(f, reversewrapper)
214 wrapname(f, reversewrapper)
215 # Check sys.args manually instead of using ui.debug() because
215 # Check sys.args manually instead of using ui.debug() because
216 # command line options is not yet applied when
216 # command line options is not yet applied when
217 # extensions.loadall() is called.
217 # extensions.loadall() is called.
218 if b'--debug' in sys.argv:
218 if b'--debug' in sys.argv:
219 ui.writenoi18n(
219 ui.writenoi18n(
220 b"[win32mbcs] activated with encoding: %s\n" % _encoding
220 b"[win32mbcs] activated with encoding: %s\n" % _encoding
221 )
221 )
@@ -1,287 +1,287 b''
1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import base64
11 import base64
12 import errno
12 import errno
13 import mimetypes
13 import mimetypes
14 import os
14 import os
15 import stat
15 import stat
16
16
17 from ..pycompat import (
17 from ..pycompat import (
18 getattr,
18 getattr,
19 open,
19 open,
20 )
20 )
21 from .. import (
21 from .. import (
22 encoding,
22 encoding,
23 pycompat,
23 pycompat,
24 util,
24 util,
25 )
25 )
26
26
27 httpserver = util.httpserver
27 httpserver = util.httpserver
28
28
29 HTTP_OK = 200
29 HTTP_OK = 200
30 HTTP_CREATED = 201
30 HTTP_CREATED = 201
31 HTTP_NOT_MODIFIED = 304
31 HTTP_NOT_MODIFIED = 304
32 HTTP_BAD_REQUEST = 400
32 HTTP_BAD_REQUEST = 400
33 HTTP_UNAUTHORIZED = 401
33 HTTP_UNAUTHORIZED = 401
34 HTTP_FORBIDDEN = 403
34 HTTP_FORBIDDEN = 403
35 HTTP_NOT_FOUND = 404
35 HTTP_NOT_FOUND = 404
36 HTTP_METHOD_NOT_ALLOWED = 405
36 HTTP_METHOD_NOT_ALLOWED = 405
37 HTTP_NOT_ACCEPTABLE = 406
37 HTTP_NOT_ACCEPTABLE = 406
38 HTTP_UNSUPPORTED_MEDIA_TYPE = 415
38 HTTP_UNSUPPORTED_MEDIA_TYPE = 415
39 HTTP_SERVER_ERROR = 500
39 HTTP_SERVER_ERROR = 500
40
40
41
41
42 def ismember(ui, username, userlist):
42 def ismember(ui, username, userlist):
43 """Check if username is a member of userlist.
43 """Check if username is a member of userlist.
44
44
45 If userlist has a single '*' member, all users are considered members.
45 If userlist has a single '*' member, all users are considered members.
46 Can be overridden by extensions to provide more complex authorization
46 Can be overridden by extensions to provide more complex authorization
47 schemes.
47 schemes.
48 """
48 """
49 return userlist == [b'*'] or username in userlist
49 return userlist == [b'*'] or username in userlist
50
50
51
51
52 def checkauthz(hgweb, req, op):
52 def checkauthz(hgweb, req, op):
53 '''Check permission for operation based on request data (including
53 '''Check permission for operation based on request data (including
54 authentication info). Return if op allowed, else raise an ErrorResponse
54 authentication info). Return if op allowed, else raise an ErrorResponse
55 exception.'''
55 exception.'''
56
56
57 user = req.remoteuser
57 user = req.remoteuser
58
58
59 deny_read = hgweb.configlist(b'web', b'deny_read')
59 deny_read = hgweb.configlist(b'web', b'deny_read')
60 if deny_read and (not user or ismember(hgweb.repo.ui, user, deny_read)):
60 if deny_read and (not user or ismember(hgweb.repo.ui, user, deny_read)):
61 raise ErrorResponse(HTTP_UNAUTHORIZED, b'read not authorized')
61 raise ErrorResponse(HTTP_UNAUTHORIZED, b'read not authorized')
62
62
63 allow_read = hgweb.configlist(b'web', b'allow_read')
63 allow_read = hgweb.configlist(b'web', b'allow_read')
64 if allow_read and (not ismember(hgweb.repo.ui, user, allow_read)):
64 if allow_read and (not ismember(hgweb.repo.ui, user, allow_read)):
65 raise ErrorResponse(HTTP_UNAUTHORIZED, b'read not authorized')
65 raise ErrorResponse(HTTP_UNAUTHORIZED, b'read not authorized')
66
66
67 if op == b'pull' and not hgweb.allowpull:
67 if op == b'pull' and not hgweb.allowpull:
68 raise ErrorResponse(HTTP_UNAUTHORIZED, b'pull not authorized')
68 raise ErrorResponse(HTTP_UNAUTHORIZED, b'pull not authorized')
69 elif op == b'pull' or op is None: # op is None for interface requests
69 elif op == b'pull' or op is None: # op is None for interface requests
70 return
70 return
71
71
72 # Allow LFS uploading via PUT requests
72 # Allow LFS uploading via PUT requests
73 if op == b'upload':
73 if op == b'upload':
74 if req.method != b'PUT':
74 if req.method != b'PUT':
75 msg = b'upload requires PUT request'
75 msg = b'upload requires PUT request'
76 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
76 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
77 # enforce that you can only push using POST requests
77 # enforce that you can only push using POST requests
78 elif req.method != b'POST':
78 elif req.method != b'POST':
79 msg = b'push requires POST request'
79 msg = b'push requires POST request'
80 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
80 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
81
81
82 # require ssl by default for pushing, auth info cannot be sniffed
82 # require ssl by default for pushing, auth info cannot be sniffed
83 # and replayed
83 # and replayed
84 if hgweb.configbool(b'web', b'push_ssl') and req.urlscheme != b'https':
84 if hgweb.configbool(b'web', b'push_ssl') and req.urlscheme != b'https':
85 raise ErrorResponse(HTTP_FORBIDDEN, b'ssl required')
85 raise ErrorResponse(HTTP_FORBIDDEN, b'ssl required')
86
86
87 deny = hgweb.configlist(b'web', b'deny_push')
87 deny = hgweb.configlist(b'web', b'deny_push')
88 if deny and (not user or ismember(hgweb.repo.ui, user, deny)):
88 if deny and (not user or ismember(hgweb.repo.ui, user, deny)):
89 raise ErrorResponse(HTTP_UNAUTHORIZED, b'push not authorized')
89 raise ErrorResponse(HTTP_UNAUTHORIZED, b'push not authorized')
90
90
91 allow = hgweb.configlist(b'web', b'allow-push')
91 allow = hgweb.configlist(b'web', b'allow-push')
92 if not (allow and ismember(hgweb.repo.ui, user, allow)):
92 if not (allow and ismember(hgweb.repo.ui, user, allow)):
93 raise ErrorResponse(HTTP_UNAUTHORIZED, b'push not authorized')
93 raise ErrorResponse(HTTP_UNAUTHORIZED, b'push not authorized')
94
94
95
95
96 # Hooks for hgweb permission checks; extensions can add hooks here.
96 # Hooks for hgweb permission checks; extensions can add hooks here.
97 # Each hook is invoked like this: hook(hgweb, request, operation),
97 # Each hook is invoked like this: hook(hgweb, request, operation),
98 # where operation is either read, pull, push or upload. Hooks should either
98 # where operation is either read, pull, push or upload. Hooks should either
99 # raise an ErrorResponse exception, or just return.
99 # raise an ErrorResponse exception, or just return.
100 #
100 #
101 # It is possible to do both authentication and authorization through
101 # It is possible to do both authentication and authorization through
102 # this.
102 # this.
103 permhooks = [checkauthz]
103 permhooks = [checkauthz]
104
104
105
105
106 class ErrorResponse(Exception):
106 class ErrorResponse(Exception):
107 def __init__(self, code, message=None, headers=None):
107 def __init__(self, code, message=None, headers=None):
108 if message is None:
108 if message is None:
109 message = _statusmessage(code)
109 message = _statusmessage(code)
110 Exception.__init__(self, pycompat.sysstr(message))
110 Exception.__init__(self, pycompat.sysstr(message))
111 self.code = code
111 self.code = code
112 if headers is None:
112 if headers is None:
113 headers = []
113 headers = []
114 self.headers = headers
114 self.headers = headers
115 self.message = message
115 self.message = message
116
116
117
117
118 class continuereader(object):
118 class continuereader(object):
119 """File object wrapper to handle HTTP 100-continue.
119 """File object wrapper to handle HTTP 100-continue.
120
120
121 This is used by servers so they automatically handle Expect: 100-continue
121 This is used by servers so they automatically handle Expect: 100-continue
122 request headers. On first read of the request body, the 100 Continue
122 request headers. On first read of the request body, the 100 Continue
123 response is sent. This should trigger the client into actually sending
123 response is sent. This should trigger the client into actually sending
124 the request body.
124 the request body.
125 """
125 """
126
126
127 def __init__(self, f, write):
127 def __init__(self, f, write):
128 self.f = f
128 self.f = f
129 self._write = write
129 self._write = write
130 self.continued = False
130 self.continued = False
131
131
132 def read(self, amt=-1):
132 def read(self, amt=-1):
133 if not self.continued:
133 if not self.continued:
134 self.continued = True
134 self.continued = True
135 self._write(b'HTTP/1.1 100 Continue\r\n\r\n')
135 self._write(b'HTTP/1.1 100 Continue\r\n\r\n')
136 return self.f.read(amt)
136 return self.f.read(amt)
137
137
138 def __getattr__(self, attr):
138 def __getattr__(self, attr):
139 if attr in (b'close', b'readline', b'readlines', b'__iter__'):
139 if attr in (b'close', b'readline', b'readlines', b'__iter__'):
140 return getattr(self.f, attr)
140 return getattr(self.f, attr)
141 raise AttributeError
141 raise AttributeError
142
142
143
143
144 def _statusmessage(code):
144 def _statusmessage(code):
145 responses = httpserver.basehttprequesthandler.responses
145 responses = httpserver.basehttprequesthandler.responses
146 return pycompat.bytesurl(responses.get(code, ('Error', 'Unknown error'))[0])
146 return pycompat.bytesurl(responses.get(code, ('Error', 'Unknown error'))[0])
147
147
148
148
149 def statusmessage(code, message=None):
149 def statusmessage(code, message=None):
150 return b'%d %s' % (code, message or _statusmessage(code))
150 return b'%d %s' % (code, message or _statusmessage(code))
151
151
152
152
153 def get_stat(spath, fn):
153 def get_stat(spath, fn):
154 """stat fn if it exists, spath otherwise"""
154 """stat fn if it exists, spath otherwise"""
155 cl_path = os.path.join(spath, fn)
155 cl_path = os.path.join(spath, fn)
156 if os.path.exists(cl_path):
156 if os.path.exists(cl_path):
157 return os.stat(cl_path)
157 return os.stat(cl_path)
158 else:
158 else:
159 return os.stat(spath)
159 return os.stat(spath)
160
160
161
161
162 def get_mtime(spath):
162 def get_mtime(spath):
163 return get_stat(spath, b"00changelog.i")[stat.ST_MTIME]
163 return get_stat(spath, b"00changelog.i")[stat.ST_MTIME]
164
164
165
165
166 def ispathsafe(path):
166 def ispathsafe(path):
167 """Determine if a path is safe to use for filesystem access."""
167 """Determine if a path is safe to use for filesystem access."""
168 parts = path.split(b'/')
168 parts = path.split(b'/')
169 for part in parts:
169 for part in parts:
170 if (
170 if (
171 part in (b'', pycompat.oscurdir, pycompat.ospardir)
171 part in (b'', pycompat.oscurdir, pycompat.ospardir)
172 or pycompat.ossep in part
172 or pycompat.ossep in part
173 or pycompat.osaltsep is not None
173 or pycompat.osaltsep is not None
174 and pycompat.osaltsep in part
174 and pycompat.osaltsep in part
175 ):
175 ):
176 return False
176 return False
177
177
178 return True
178 return True
179
179
180
180
181 def staticfile(directory, fname, res):
181 def staticfile(directory, fname, res):
182 """return a file inside directory with guessed Content-Type header
182 """return a file inside directory with guessed Content-Type header
183
183
184 fname always uses '/' as directory separator and isn't allowed to
184 fname always uses '/' as directory separator and isn't allowed to
185 contain unusual path components.
185 contain unusual path components.
186 Content-Type is guessed using the mimetypes module.
186 Content-Type is guessed using the mimetypes module.
187 Return an empty string if fname is illegal or file not found.
187 Return an empty string if fname is illegal or file not found.
188
188
189 """
189 """
190 if not ispathsafe(fname):
190 if not ispathsafe(fname):
191 return
191 return
192
192
193 fpath = os.path.join(*fname.split(b'/'))
193 fpath = os.path.join(*fname.split(b'/'))
194 if isinstance(directory, str):
194 if isinstance(directory, bytes):
195 directory = [directory]
195 directory = [directory]
196 for d in directory:
196 for d in directory:
197 path = os.path.join(d, fpath)
197 path = os.path.join(d, fpath)
198 if os.path.exists(path):
198 if os.path.exists(path):
199 break
199 break
200 try:
200 try:
201 os.stat(path)
201 os.stat(path)
202 ct = pycompat.sysbytes(
202 ct = pycompat.sysbytes(
203 mimetypes.guess_type(pycompat.fsdecode(path))[0] or r"text/plain"
203 mimetypes.guess_type(pycompat.fsdecode(path))[0] or r"text/plain"
204 )
204 )
205 with open(path, b'rb') as fh:
205 with open(path, b'rb') as fh:
206 data = fh.read()
206 data = fh.read()
207
207
208 res.headers[b'Content-Type'] = ct
208 res.headers[b'Content-Type'] = ct
209 res.setbodybytes(data)
209 res.setbodybytes(data)
210 return res
210 return res
211 except TypeError:
211 except TypeError:
212 raise ErrorResponse(HTTP_SERVER_ERROR, b'illegal filename')
212 raise ErrorResponse(HTTP_SERVER_ERROR, b'illegal filename')
213 except OSError as err:
213 except OSError as err:
214 if err.errno == errno.ENOENT:
214 if err.errno == errno.ENOENT:
215 raise ErrorResponse(HTTP_NOT_FOUND)
215 raise ErrorResponse(HTTP_NOT_FOUND)
216 else:
216 else:
217 raise ErrorResponse(
217 raise ErrorResponse(
218 HTTP_SERVER_ERROR, encoding.strtolocal(err.strerror)
218 HTTP_SERVER_ERROR, encoding.strtolocal(err.strerror)
219 )
219 )
220
220
221
221
222 def paritygen(stripecount, offset=0):
222 def paritygen(stripecount, offset=0):
223 """count parity of horizontal stripes for easier reading"""
223 """count parity of horizontal stripes for easier reading"""
224 if stripecount and offset:
224 if stripecount and offset:
225 # account for offset, e.g. due to building the list in reverse
225 # account for offset, e.g. due to building the list in reverse
226 count = (stripecount + offset) % stripecount
226 count = (stripecount + offset) % stripecount
227 parity = (stripecount + offset) // stripecount & 1
227 parity = (stripecount + offset) // stripecount & 1
228 else:
228 else:
229 count = 0
229 count = 0
230 parity = 0
230 parity = 0
231 while True:
231 while True:
232 yield parity
232 yield parity
233 count += 1
233 count += 1
234 if stripecount and count >= stripecount:
234 if stripecount and count >= stripecount:
235 parity = 1 - parity
235 parity = 1 - parity
236 count = 0
236 count = 0
237
237
238
238
239 def get_contact(config):
239 def get_contact(config):
240 """Return repo contact information or empty string.
240 """Return repo contact information or empty string.
241
241
242 web.contact is the primary source, but if that is not set, try
242 web.contact is the primary source, but if that is not set, try
243 ui.username or $EMAIL as a fallback to display something useful.
243 ui.username or $EMAIL as a fallback to display something useful.
244 """
244 """
245 return (
245 return (
246 config(b"web", b"contact")
246 config(b"web", b"contact")
247 or config(b"ui", b"username")
247 or config(b"ui", b"username")
248 or encoding.environ.get(b"EMAIL")
248 or encoding.environ.get(b"EMAIL")
249 or b""
249 or b""
250 )
250 )
251
251
252
252
253 def cspvalues(ui):
253 def cspvalues(ui):
254 """Obtain the Content-Security-Policy header and nonce value.
254 """Obtain the Content-Security-Policy header and nonce value.
255
255
256 Returns a 2-tuple of the CSP header value and the nonce value.
256 Returns a 2-tuple of the CSP header value and the nonce value.
257
257
258 First value is ``None`` if CSP isn't enabled. Second value is ``None``
258 First value is ``None`` if CSP isn't enabled. Second value is ``None``
259 if CSP isn't enabled or if the CSP header doesn't need a nonce.
259 if CSP isn't enabled or if the CSP header doesn't need a nonce.
260 """
260 """
261 # Without demandimport, "import uuid" could have an immediate side-effect
261 # Without demandimport, "import uuid" could have an immediate side-effect
262 # running "ldconfig" on Linux trying to find libuuid.
262 # running "ldconfig" on Linux trying to find libuuid.
263 # With Python <= 2.7.12, that "ldconfig" is run via a shell and the shell
263 # With Python <= 2.7.12, that "ldconfig" is run via a shell and the shell
264 # may pollute the terminal with:
264 # may pollute the terminal with:
265 #
265 #
266 # shell-init: error retrieving current directory: getcwd: cannot access
266 # shell-init: error retrieving current directory: getcwd: cannot access
267 # parent directories: No such file or directory
267 # parent directories: No such file or directory
268 #
268 #
269 # Python >= 2.7.13 has fixed it by running "ldconfig" directly without a
269 # Python >= 2.7.13 has fixed it by running "ldconfig" directly without a
270 # shell (hg changeset a09ae70f3489).
270 # shell (hg changeset a09ae70f3489).
271 #
271 #
272 # Moved "import uuid" from here so it's executed after we know we have
272 # Moved "import uuid" from here so it's executed after we know we have
273 # a sane cwd (i.e. after dispatch.py cwd check).
273 # a sane cwd (i.e. after dispatch.py cwd check).
274 #
274 #
275 # We can move it back once we no longer need Python <= 2.7.12 support.
275 # We can move it back once we no longer need Python <= 2.7.12 support.
276 import uuid
276 import uuid
277
277
278 # Don't allow untrusted CSP setting since it be disable protections
278 # Don't allow untrusted CSP setting since it be disable protections
279 # from a trusted/global source.
279 # from a trusted/global source.
280 csp = ui.config(b'web', b'csp', untrusted=False)
280 csp = ui.config(b'web', b'csp', untrusted=False)
281 nonce = None
281 nonce = None
282
282
283 if csp and b'%nonce%' in csp:
283 if csp and b'%nonce%' in csp:
284 nonce = base64.urlsafe_b64encode(uuid.uuid4().bytes).rstrip(b'=')
284 nonce = base64.urlsafe_b64encode(uuid.uuid4().bytes).rstrip(b'=')
285 csp = csp.replace(b'%nonce%', nonce)
285 csp = csp.replace(b'%nonce%', nonce)
286
286
287 return csp, nonce
287 return csp, nonce
@@ -1,579 +1,579 b''
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import gc
11 import gc
12 import os
12 import os
13 import time
13 import time
14
14
15 from ..i18n import _
15 from ..i18n import _
16
16
17 from .common import (
17 from .common import (
18 ErrorResponse,
18 ErrorResponse,
19 HTTP_SERVER_ERROR,
19 HTTP_SERVER_ERROR,
20 cspvalues,
20 cspvalues,
21 get_contact,
21 get_contact,
22 get_mtime,
22 get_mtime,
23 ismember,
23 ismember,
24 paritygen,
24 paritygen,
25 staticfile,
25 staticfile,
26 statusmessage,
26 statusmessage,
27 )
27 )
28
28
29 from .. import (
29 from .. import (
30 configitems,
30 configitems,
31 encoding,
31 encoding,
32 error,
32 error,
33 extensions,
33 extensions,
34 hg,
34 hg,
35 pathutil,
35 pathutil,
36 profiling,
36 profiling,
37 pycompat,
37 pycompat,
38 registrar,
38 registrar,
39 scmutil,
39 scmutil,
40 templater,
40 templater,
41 templateutil,
41 templateutil,
42 ui as uimod,
42 ui as uimod,
43 util,
43 util,
44 )
44 )
45
45
46 from . import (
46 from . import (
47 hgweb_mod,
47 hgweb_mod,
48 request as requestmod,
48 request as requestmod,
49 webutil,
49 webutil,
50 wsgicgi,
50 wsgicgi,
51 )
51 )
52 from ..utils import dateutil
52 from ..utils import dateutil
53
53
54
54
55 def cleannames(items):
55 def cleannames(items):
56 return [(util.pconvert(name).strip(b'/'), path) for name, path in items]
56 return [(util.pconvert(name).strip(b'/'), path) for name, path in items]
57
57
58
58
59 def findrepos(paths):
59 def findrepos(paths):
60 repos = []
60 repos = []
61 for prefix, root in cleannames(paths):
61 for prefix, root in cleannames(paths):
62 roothead, roottail = os.path.split(root)
62 roothead, roottail = os.path.split(root)
63 # "foo = /bar/*" or "foo = /bar/**" lets every repo /bar/N in or below
63 # "foo = /bar/*" or "foo = /bar/**" lets every repo /bar/N in or below
64 # /bar/ be served as as foo/N .
64 # /bar/ be served as as foo/N .
65 # '*' will not search inside dirs with .hg (except .hg/patches),
65 # '*' will not search inside dirs with .hg (except .hg/patches),
66 # '**' will search inside dirs with .hg (and thus also find subrepos).
66 # '**' will search inside dirs with .hg (and thus also find subrepos).
67 try:
67 try:
68 recurse = {b'*': False, b'**': True}[roottail]
68 recurse = {b'*': False, b'**': True}[roottail]
69 except KeyError:
69 except KeyError:
70 repos.append((prefix, root))
70 repos.append((prefix, root))
71 continue
71 continue
72 roothead = os.path.normpath(os.path.abspath(roothead))
72 roothead = os.path.normpath(os.path.abspath(roothead))
73 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse)
73 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse)
74 repos.extend(urlrepos(prefix, roothead, paths))
74 repos.extend(urlrepos(prefix, roothead, paths))
75 return repos
75 return repos
76
76
77
77
78 def urlrepos(prefix, roothead, paths):
78 def urlrepos(prefix, roothead, paths):
79 """yield url paths and filesystem paths from a list of repo paths
79 """yield url paths and filesystem paths from a list of repo paths
80
80
81 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
81 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
82 >>> conv(urlrepos(b'hg', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
82 >>> conv(urlrepos(b'hg', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
83 [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
83 [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
84 >>> conv(urlrepos(b'', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
84 >>> conv(urlrepos(b'', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
85 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
85 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
86 """
86 """
87 for path in paths:
87 for path in paths:
88 path = os.path.normpath(path)
88 path = os.path.normpath(path)
89 yield (
89 yield (
90 prefix + b'/' + util.pconvert(path[len(roothead) :]).lstrip(b'/')
90 prefix + b'/' + util.pconvert(path[len(roothead) :]).lstrip(b'/')
91 ).strip(b'/'), path
91 ).strip(b'/'), path
92
92
93
93
94 def readallowed(ui, req):
94 def readallowed(ui, req):
95 """Check allow_read and deny_read config options of a repo's ui object
95 """Check allow_read and deny_read config options of a repo's ui object
96 to determine user permissions. By default, with neither option set (or
96 to determine user permissions. By default, with neither option set (or
97 both empty), allow all users to read the repo. There are two ways a
97 both empty), allow all users to read the repo. There are two ways a
98 user can be denied read access: (1) deny_read is not empty, and the
98 user can be denied read access: (1) deny_read is not empty, and the
99 user is unauthenticated or deny_read contains user (or *), and (2)
99 user is unauthenticated or deny_read contains user (or *), and (2)
100 allow_read is not empty and the user is not in allow_read. Return True
100 allow_read is not empty and the user is not in allow_read. Return True
101 if user is allowed to read the repo, else return False."""
101 if user is allowed to read the repo, else return False."""
102
102
103 user = req.remoteuser
103 user = req.remoteuser
104
104
105 deny_read = ui.configlist(b'web', b'deny_read', untrusted=True)
105 deny_read = ui.configlist(b'web', b'deny_read', untrusted=True)
106 if deny_read and (not user or ismember(ui, user, deny_read)):
106 if deny_read and (not user or ismember(ui, user, deny_read)):
107 return False
107 return False
108
108
109 allow_read = ui.configlist(b'web', b'allow_read', untrusted=True)
109 allow_read = ui.configlist(b'web', b'allow_read', untrusted=True)
110 # by default, allow reading if no allow_read option has been set
110 # by default, allow reading if no allow_read option has been set
111 if not allow_read or ismember(ui, user, allow_read):
111 if not allow_read or ismember(ui, user, allow_read):
112 return True
112 return True
113
113
114 return False
114 return False
115
115
116
116
117 def rawindexentries(ui, repos, req, subdir=b''):
117 def rawindexentries(ui, repos, req, subdir=b''):
118 descend = ui.configbool(b'web', b'descend')
118 descend = ui.configbool(b'web', b'descend')
119 collapse = ui.configbool(b'web', b'collapse')
119 collapse = ui.configbool(b'web', b'collapse')
120 seenrepos = set()
120 seenrepos = set()
121 seendirs = set()
121 seendirs = set()
122 for name, path in repos:
122 for name, path in repos:
123
123
124 if not name.startswith(subdir):
124 if not name.startswith(subdir):
125 continue
125 continue
126 name = name[len(subdir) :]
126 name = name[len(subdir) :]
127 directory = False
127 directory = False
128
128
129 if b'/' in name:
129 if b'/' in name:
130 if not descend:
130 if not descend:
131 continue
131 continue
132
132
133 nameparts = name.split(b'/')
133 nameparts = name.split(b'/')
134 rootname = nameparts[0]
134 rootname = nameparts[0]
135
135
136 if not collapse:
136 if not collapse:
137 pass
137 pass
138 elif rootname in seendirs:
138 elif rootname in seendirs:
139 continue
139 continue
140 elif rootname in seenrepos:
140 elif rootname in seenrepos:
141 pass
141 pass
142 else:
142 else:
143 directory = True
143 directory = True
144 name = rootname
144 name = rootname
145
145
146 # redefine the path to refer to the directory
146 # redefine the path to refer to the directory
147 discarded = b'/'.join(nameparts[1:])
147 discarded = b'/'.join(nameparts[1:])
148
148
149 # remove name parts plus accompanying slash
149 # remove name parts plus accompanying slash
150 path = path[: -len(discarded) - 1]
150 path = path[: -len(discarded) - 1]
151
151
152 try:
152 try:
153 hg.repository(ui, path)
153 hg.repository(ui, path)
154 directory = False
154 directory = False
155 except (IOError, error.RepoError):
155 except (IOError, error.RepoError):
156 pass
156 pass
157
157
158 parts = [
158 parts = [
159 req.apppath.strip(b'/'),
159 req.apppath.strip(b'/'),
160 subdir.strip(b'/'),
160 subdir.strip(b'/'),
161 name.strip(b'/'),
161 name.strip(b'/'),
162 ]
162 ]
163 url = b'/' + b'/'.join(p for p in parts if p) + b'/'
163 url = b'/' + b'/'.join(p for p in parts if p) + b'/'
164
164
165 # show either a directory entry or a repository
165 # show either a directory entry or a repository
166 if directory:
166 if directory:
167 # get the directory's time information
167 # get the directory's time information
168 try:
168 try:
169 d = (get_mtime(path), dateutil.makedate()[1])
169 d = (get_mtime(path), dateutil.makedate()[1])
170 except OSError:
170 except OSError:
171 continue
171 continue
172
172
173 # add '/' to the name to make it obvious that
173 # add '/' to the name to make it obvious that
174 # the entry is a directory, not a regular repository
174 # the entry is a directory, not a regular repository
175 row = {
175 row = {
176 b'contact': b"",
176 b'contact': b"",
177 b'contact_sort': b"",
177 b'contact_sort': b"",
178 b'name': name + b'/',
178 b'name': name + b'/',
179 b'name_sort': name,
179 b'name_sort': name,
180 b'url': url,
180 b'url': url,
181 b'description': b"",
181 b'description': b"",
182 b'description_sort': b"",
182 b'description_sort': b"",
183 b'lastchange': d,
183 b'lastchange': d,
184 b'lastchange_sort': d[1] - d[0],
184 b'lastchange_sort': d[1] - d[0],
185 b'archives': templateutil.mappinglist([]),
185 b'archives': templateutil.mappinglist([]),
186 b'isdirectory': True,
186 b'isdirectory': True,
187 b'labels': templateutil.hybridlist([], name=b'label'),
187 b'labels': templateutil.hybridlist([], name=b'label'),
188 }
188 }
189
189
190 seendirs.add(name)
190 seendirs.add(name)
191 yield row
191 yield row
192 continue
192 continue
193
193
194 u = ui.copy()
194 u = ui.copy()
195 try:
195 try:
196 u.readconfig(os.path.join(path, b'.hg', b'hgrc'))
196 u.readconfig(os.path.join(path, b'.hg', b'hgrc'))
197 except Exception as e:
197 except Exception as e:
198 u.warn(_(b'error reading %s/.hg/hgrc: %s\n') % (path, e))
198 u.warn(_(b'error reading %s/.hg/hgrc: %s\n') % (path, e))
199 continue
199 continue
200
200
201 def get(section, name, default=uimod._unset):
201 def get(section, name, default=uimod._unset):
202 return u.config(section, name, default, untrusted=True)
202 return u.config(section, name, default, untrusted=True)
203
203
204 if u.configbool(b"web", b"hidden", untrusted=True):
204 if u.configbool(b"web", b"hidden", untrusted=True):
205 continue
205 continue
206
206
207 if not readallowed(u, req):
207 if not readallowed(u, req):
208 continue
208 continue
209
209
210 # update time with local timezone
210 # update time with local timezone
211 try:
211 try:
212 r = hg.repository(ui, path)
212 r = hg.repository(ui, path)
213 except IOError:
213 except IOError:
214 u.warn(_(b'error accessing repository at %s\n') % path)
214 u.warn(_(b'error accessing repository at %s\n') % path)
215 continue
215 continue
216 except error.RepoError:
216 except error.RepoError:
217 u.warn(_(b'error accessing repository at %s\n') % path)
217 u.warn(_(b'error accessing repository at %s\n') % path)
218 continue
218 continue
219 try:
219 try:
220 d = (get_mtime(r.spath), dateutil.makedate()[1])
220 d = (get_mtime(r.spath), dateutil.makedate()[1])
221 except OSError:
221 except OSError:
222 continue
222 continue
223
223
224 contact = get_contact(get)
224 contact = get_contact(get)
225 description = get(b"web", b"description")
225 description = get(b"web", b"description")
226 seenrepos.add(name)
226 seenrepos.add(name)
227 name = get(b"web", b"name", name)
227 name = get(b"web", b"name", name)
228 labels = u.configlist(b'web', b'labels', untrusted=True)
228 labels = u.configlist(b'web', b'labels', untrusted=True)
229 row = {
229 row = {
230 b'contact': contact or b"unknown",
230 b'contact': contact or b"unknown",
231 b'contact_sort': contact.upper() or b"unknown",
231 b'contact_sort': contact.upper() or b"unknown",
232 b'name': name,
232 b'name': name,
233 b'name_sort': name,
233 b'name_sort': name,
234 b'url': url,
234 b'url': url,
235 b'description': description or b"unknown",
235 b'description': description or b"unknown",
236 b'description_sort': description.upper() or b"unknown",
236 b'description_sort': description.upper() or b"unknown",
237 b'lastchange': d,
237 b'lastchange': d,
238 b'lastchange_sort': d[1] - d[0],
238 b'lastchange_sort': d[1] - d[0],
239 b'archives': webutil.archivelist(u, b"tip", url),
239 b'archives': webutil.archivelist(u, b"tip", url),
240 b'isdirectory': None,
240 b'isdirectory': None,
241 b'labels': templateutil.hybridlist(labels, name=b'label'),
241 b'labels': templateutil.hybridlist(labels, name=b'label'),
242 }
242 }
243
243
244 yield row
244 yield row
245
245
246
246
247 def _indexentriesgen(
247 def _indexentriesgen(
248 context, ui, repos, req, stripecount, sortcolumn, descending, subdir
248 context, ui, repos, req, stripecount, sortcolumn, descending, subdir
249 ):
249 ):
250 rows = rawindexentries(ui, repos, req, subdir=subdir)
250 rows = rawindexentries(ui, repos, req, subdir=subdir)
251
251
252 sortdefault = None, False
252 sortdefault = None, False
253
253
254 if sortcolumn and sortdefault != (sortcolumn, descending):
254 if sortcolumn and sortdefault != (sortcolumn, descending):
255 sortkey = b'%s_sort' % sortcolumn
255 sortkey = b'%s_sort' % sortcolumn
256 rows = sorted(rows, key=lambda x: x[sortkey], reverse=descending)
256 rows = sorted(rows, key=lambda x: x[sortkey], reverse=descending)
257
257
258 for row, parity in zip(rows, paritygen(stripecount)):
258 for row, parity in zip(rows, paritygen(stripecount)):
259 row[b'parity'] = parity
259 row[b'parity'] = parity
260 yield row
260 yield row
261
261
262
262
263 def indexentries(
263 def indexentries(
264 ui, repos, req, stripecount, sortcolumn=b'', descending=False, subdir=b''
264 ui, repos, req, stripecount, sortcolumn=b'', descending=False, subdir=b''
265 ):
265 ):
266 args = (ui, repos, req, stripecount, sortcolumn, descending, subdir)
266 args = (ui, repos, req, stripecount, sortcolumn, descending, subdir)
267 return templateutil.mappinggenerator(_indexentriesgen, args=args)
267 return templateutil.mappinggenerator(_indexentriesgen, args=args)
268
268
269
269
270 class hgwebdir(object):
270 class hgwebdir(object):
271 """HTTP server for multiple repositories.
271 """HTTP server for multiple repositories.
272
272
273 Given a configuration, different repositories will be served depending
273 Given a configuration, different repositories will be served depending
274 on the request path.
274 on the request path.
275
275
276 Instances are typically used as WSGI applications.
276 Instances are typically used as WSGI applications.
277 """
277 """
278
278
279 def __init__(self, conf, baseui=None):
279 def __init__(self, conf, baseui=None):
280 self.conf = conf
280 self.conf = conf
281 self.baseui = baseui
281 self.baseui = baseui
282 self.ui = None
282 self.ui = None
283 self.lastrefresh = 0
283 self.lastrefresh = 0
284 self.motd = None
284 self.motd = None
285 self.refresh()
285 self.refresh()
286 if not baseui:
286 if not baseui:
287 # set up environment for new ui
287 # set up environment for new ui
288 extensions.loadall(self.ui)
288 extensions.loadall(self.ui)
289 extensions.populateui(self.ui)
289 extensions.populateui(self.ui)
290
290
291 def refresh(self):
291 def refresh(self):
292 if self.ui:
292 if self.ui:
293 refreshinterval = self.ui.configint(b'web', b'refreshinterval')
293 refreshinterval = self.ui.configint(b'web', b'refreshinterval')
294 else:
294 else:
295 item = configitems.coreitems[b'web'][b'refreshinterval']
295 item = configitems.coreitems[b'web'][b'refreshinterval']
296 refreshinterval = item.default
296 refreshinterval = item.default
297
297
298 # refreshinterval <= 0 means to always refresh.
298 # refreshinterval <= 0 means to always refresh.
299 if (
299 if (
300 refreshinterval > 0
300 refreshinterval > 0
301 and self.lastrefresh + refreshinterval > time.time()
301 and self.lastrefresh + refreshinterval > time.time()
302 ):
302 ):
303 return
303 return
304
304
305 if self.baseui:
305 if self.baseui:
306 u = self.baseui.copy()
306 u = self.baseui.copy()
307 else:
307 else:
308 u = uimod.ui.load()
308 u = uimod.ui.load()
309 u.setconfig(b'ui', b'report_untrusted', b'off', b'hgwebdir')
309 u.setconfig(b'ui', b'report_untrusted', b'off', b'hgwebdir')
310 u.setconfig(b'ui', b'nontty', b'true', b'hgwebdir')
310 u.setconfig(b'ui', b'nontty', b'true', b'hgwebdir')
311 # displaying bundling progress bar while serving feels wrong and may
311 # displaying bundling progress bar while serving feels wrong and may
312 # break some wsgi implementations.
312 # break some wsgi implementations.
313 u.setconfig(b'progress', b'disable', b'true', b'hgweb')
313 u.setconfig(b'progress', b'disable', b'true', b'hgweb')
314
314
315 if not isinstance(self.conf, (dict, list, tuple)):
315 if not isinstance(self.conf, (dict, list, tuple)):
316 map = {b'paths': b'hgweb-paths'}
316 map = {b'paths': b'hgweb-paths'}
317 if not os.path.exists(self.conf):
317 if not os.path.exists(self.conf):
318 raise error.Abort(_(b'config file %s not found!') % self.conf)
318 raise error.Abort(_(b'config file %s not found!') % self.conf)
319 u.readconfig(self.conf, remap=map, trust=True)
319 u.readconfig(self.conf, remap=map, trust=True)
320 paths = []
320 paths = []
321 for name, ignored in u.configitems(b'hgweb-paths'):
321 for name, ignored in u.configitems(b'hgweb-paths'):
322 for path in u.configlist(b'hgweb-paths', name):
322 for path in u.configlist(b'hgweb-paths', name):
323 paths.append((name, path))
323 paths.append((name, path))
324 elif isinstance(self.conf, (list, tuple)):
324 elif isinstance(self.conf, (list, tuple)):
325 paths = self.conf
325 paths = self.conf
326 elif isinstance(self.conf, dict):
326 elif isinstance(self.conf, dict):
327 paths = self.conf.items()
327 paths = self.conf.items()
328 extensions.populateui(u)
328 extensions.populateui(u)
329
329
330 repos = findrepos(paths)
330 repos = findrepos(paths)
331 for prefix, root in u.configitems(b'collections'):
331 for prefix, root in u.configitems(b'collections'):
332 prefix = util.pconvert(prefix)
332 prefix = util.pconvert(prefix)
333 for path in scmutil.walkrepos(root, followsym=True):
333 for path in scmutil.walkrepos(root, followsym=True):
334 repo = os.path.normpath(path)
334 repo = os.path.normpath(path)
335 name = util.pconvert(repo)
335 name = util.pconvert(repo)
336 if name.startswith(prefix):
336 if name.startswith(prefix):
337 name = name[len(prefix) :]
337 name = name[len(prefix) :]
338 repos.append((name.lstrip(b'/'), repo))
338 repos.append((name.lstrip(b'/'), repo))
339
339
340 self.repos = repos
340 self.repos = repos
341 self.ui = u
341 self.ui = u
342 encoding.encoding = self.ui.config(b'web', b'encoding')
342 encoding.encoding = self.ui.config(b'web', b'encoding')
343 self.style = self.ui.config(b'web', b'style')
343 self.style = self.ui.config(b'web', b'style')
344 self.templatepath = self.ui.config(
344 self.templatepath = self.ui.config(
345 b'web', b'templates', untrusted=False
345 b'web', b'templates', untrusted=False
346 )
346 )
347 self.stripecount = self.ui.config(b'web', b'stripes')
347 self.stripecount = self.ui.config(b'web', b'stripes')
348 if self.stripecount:
348 if self.stripecount:
349 self.stripecount = int(self.stripecount)
349 self.stripecount = int(self.stripecount)
350 prefix = self.ui.config(b'web', b'prefix')
350 prefix = self.ui.config(b'web', b'prefix')
351 if prefix.startswith(b'/'):
351 if prefix.startswith(b'/'):
352 prefix = prefix[1:]
352 prefix = prefix[1:]
353 if prefix.endswith(b'/'):
353 if prefix.endswith(b'/'):
354 prefix = prefix[:-1]
354 prefix = prefix[:-1]
355 self.prefix = prefix
355 self.prefix = prefix
356 self.lastrefresh = time.time()
356 self.lastrefresh = time.time()
357
357
358 def run(self):
358 def run(self):
359 if not encoding.environ.get(b'GATEWAY_INTERFACE', b'').startswith(
359 if not encoding.environ.get(b'GATEWAY_INTERFACE', b'').startswith(
360 b"CGI/1."
360 b"CGI/1."
361 ):
361 ):
362 raise RuntimeError(
362 raise RuntimeError(
363 b"This function is only intended to be "
363 b"This function is only intended to be "
364 b"called while running as a CGI script."
364 b"called while running as a CGI script."
365 )
365 )
366 wsgicgi.launch(self)
366 wsgicgi.launch(self)
367
367
368 def __call__(self, env, respond):
368 def __call__(self, env, respond):
369 baseurl = self.ui.config(b'web', b'baseurl')
369 baseurl = self.ui.config(b'web', b'baseurl')
370 req = requestmod.parserequestfromenv(env, altbaseurl=baseurl)
370 req = requestmod.parserequestfromenv(env, altbaseurl=baseurl)
371 res = requestmod.wsgiresponse(req, respond)
371 res = requestmod.wsgiresponse(req, respond)
372
372
373 return self.run_wsgi(req, res)
373 return self.run_wsgi(req, res)
374
374
375 def run_wsgi(self, req, res):
375 def run_wsgi(self, req, res):
376 profile = self.ui.configbool(b'profiling', b'enabled')
376 profile = self.ui.configbool(b'profiling', b'enabled')
377 with profiling.profile(self.ui, enabled=profile):
377 with profiling.profile(self.ui, enabled=profile):
378 try:
378 try:
379 for r in self._runwsgi(req, res):
379 for r in self._runwsgi(req, res):
380 yield r
380 yield r
381 finally:
381 finally:
382 # There are known cycles in localrepository that prevent
382 # There are known cycles in localrepository that prevent
383 # those objects (and tons of held references) from being
383 # those objects (and tons of held references) from being
384 # collected through normal refcounting. We mitigate those
384 # collected through normal refcounting. We mitigate those
385 # leaks by performing an explicit GC on every request.
385 # leaks by performing an explicit GC on every request.
386 # TODO remove this once leaks are fixed.
386 # TODO remove this once leaks are fixed.
387 # TODO only run this on requests that create localrepository
387 # TODO only run this on requests that create localrepository
388 # instances instead of every request.
388 # instances instead of every request.
389 gc.collect()
389 gc.collect()
390
390
391 def _runwsgi(self, req, res):
391 def _runwsgi(self, req, res):
392 try:
392 try:
393 self.refresh()
393 self.refresh()
394
394
395 csp, nonce = cspvalues(self.ui)
395 csp, nonce = cspvalues(self.ui)
396 if csp:
396 if csp:
397 res.headers[b'Content-Security-Policy'] = csp
397 res.headers[b'Content-Security-Policy'] = csp
398
398
399 virtual = req.dispatchpath.strip(b'/')
399 virtual = req.dispatchpath.strip(b'/')
400 tmpl = self.templater(req, nonce)
400 tmpl = self.templater(req, nonce)
401 ctype = tmpl.render(b'mimetype', {b'encoding': encoding.encoding})
401 ctype = tmpl.render(b'mimetype', {b'encoding': encoding.encoding})
402
402
403 # Global defaults. These can be overridden by any handler.
403 # Global defaults. These can be overridden by any handler.
404 res.status = b'200 Script output follows'
404 res.status = b'200 Script output follows'
405 res.headers[b'Content-Type'] = ctype
405 res.headers[b'Content-Type'] = ctype
406
406
407 # a static file
407 # a static file
408 if virtual.startswith(b'static/') or b'static' in req.qsparams:
408 if virtual.startswith(b'static/') or b'static' in req.qsparams:
409 if virtual.startswith(b'static/'):
409 if virtual.startswith(b'static/'):
410 fname = virtual[7:]
410 fname = virtual[7:]
411 else:
411 else:
412 fname = req.qsparams[b'static']
412 fname = req.qsparams[b'static']
413 static = self.ui.config(b"web", b"static", untrusted=False)
413 static = self.ui.config(b"web", b"static", untrusted=False)
414 if not static:
414 if not static:
415 tp = self.templatepath or templater.templatepaths()
415 tp = self.templatepath or templater.templatepaths()
416 if isinstance(tp, str):
416 if isinstance(tp, bytes):
417 tp = [tp]
417 tp = [tp]
418 static = [os.path.join(p, b'static') for p in tp]
418 static = [os.path.join(p, b'static') for p in tp]
419
419
420 staticfile(static, fname, res)
420 staticfile(static, fname, res)
421 return res.sendresponse()
421 return res.sendresponse()
422
422
423 # top-level index
423 # top-level index
424
424
425 repos = dict(self.repos)
425 repos = dict(self.repos)
426
426
427 if (not virtual or virtual == b'index') and virtual not in repos:
427 if (not virtual or virtual == b'index') and virtual not in repos:
428 return self.makeindex(req, res, tmpl)
428 return self.makeindex(req, res, tmpl)
429
429
430 # nested indexes and hgwebs
430 # nested indexes and hgwebs
431
431
432 if virtual.endswith(b'/index') and virtual not in repos:
432 if virtual.endswith(b'/index') and virtual not in repos:
433 subdir = virtual[: -len(b'index')]
433 subdir = virtual[: -len(b'index')]
434 if any(r.startswith(subdir) for r in repos):
434 if any(r.startswith(subdir) for r in repos):
435 return self.makeindex(req, res, tmpl, subdir)
435 return self.makeindex(req, res, tmpl, subdir)
436
436
437 def _virtualdirs():
437 def _virtualdirs():
438 # Check the full virtual path, and each parent
438 # Check the full virtual path, and each parent
439 yield virtual
439 yield virtual
440 for p in pathutil.finddirs(virtual):
440 for p in pathutil.finddirs(virtual):
441 yield p
441 yield p
442
442
443 for virtualrepo in _virtualdirs():
443 for virtualrepo in _virtualdirs():
444 real = repos.get(virtualrepo)
444 real = repos.get(virtualrepo)
445 if real:
445 if real:
446 # Re-parse the WSGI environment to take into account our
446 # Re-parse the WSGI environment to take into account our
447 # repository path component.
447 # repository path component.
448 uenv = req.rawenv
448 uenv = req.rawenv
449 if pycompat.ispy3:
449 if pycompat.ispy3:
450 uenv = {
450 uenv = {
451 k.decode('latin1'): v
451 k.decode('latin1'): v
452 for k, v in pycompat.iteritems(uenv)
452 for k, v in pycompat.iteritems(uenv)
453 }
453 }
454 req = requestmod.parserequestfromenv(
454 req = requestmod.parserequestfromenv(
455 uenv,
455 uenv,
456 reponame=virtualrepo,
456 reponame=virtualrepo,
457 altbaseurl=self.ui.config(b'web', b'baseurl'),
457 altbaseurl=self.ui.config(b'web', b'baseurl'),
458 # Reuse wrapped body file object otherwise state
458 # Reuse wrapped body file object otherwise state
459 # tracking can get confused.
459 # tracking can get confused.
460 bodyfh=req.bodyfh,
460 bodyfh=req.bodyfh,
461 )
461 )
462 try:
462 try:
463 # ensure caller gets private copy of ui
463 # ensure caller gets private copy of ui
464 repo = hg.repository(self.ui.copy(), real)
464 repo = hg.repository(self.ui.copy(), real)
465 return hgweb_mod.hgweb(repo).run_wsgi(req, res)
465 return hgweb_mod.hgweb(repo).run_wsgi(req, res)
466 except IOError as inst:
466 except IOError as inst:
467 msg = encoding.strtolocal(inst.strerror)
467 msg = encoding.strtolocal(inst.strerror)
468 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
468 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
469 except error.RepoError as inst:
469 except error.RepoError as inst:
470 raise ErrorResponse(HTTP_SERVER_ERROR, bytes(inst))
470 raise ErrorResponse(HTTP_SERVER_ERROR, bytes(inst))
471
471
472 # browse subdirectories
472 # browse subdirectories
473 subdir = virtual + b'/'
473 subdir = virtual + b'/'
474 if [r for r in repos if r.startswith(subdir)]:
474 if [r for r in repos if r.startswith(subdir)]:
475 return self.makeindex(req, res, tmpl, subdir)
475 return self.makeindex(req, res, tmpl, subdir)
476
476
477 # prefixes not found
477 # prefixes not found
478 res.status = b'404 Not Found'
478 res.status = b'404 Not Found'
479 res.setbodygen(tmpl.generate(b'notfound', {b'repo': virtual}))
479 res.setbodygen(tmpl.generate(b'notfound', {b'repo': virtual}))
480 return res.sendresponse()
480 return res.sendresponse()
481
481
482 except ErrorResponse as e:
482 except ErrorResponse as e:
483 res.status = statusmessage(e.code, pycompat.bytestr(e))
483 res.status = statusmessage(e.code, pycompat.bytestr(e))
484 res.setbodygen(
484 res.setbodygen(
485 tmpl.generate(b'error', {b'error': e.message or b''})
485 tmpl.generate(b'error', {b'error': e.message or b''})
486 )
486 )
487 return res.sendresponse()
487 return res.sendresponse()
488 finally:
488 finally:
489 tmpl = None
489 tmpl = None
490
490
491 def makeindex(self, req, res, tmpl, subdir=b""):
491 def makeindex(self, req, res, tmpl, subdir=b""):
492 self.refresh()
492 self.refresh()
493 sortable = [b"name", b"description", b"contact", b"lastchange"]
493 sortable = [b"name", b"description", b"contact", b"lastchange"]
494 sortcolumn, descending = None, False
494 sortcolumn, descending = None, False
495 if b'sort' in req.qsparams:
495 if b'sort' in req.qsparams:
496 sortcolumn = req.qsparams[b'sort']
496 sortcolumn = req.qsparams[b'sort']
497 descending = sortcolumn.startswith(b'-')
497 descending = sortcolumn.startswith(b'-')
498 if descending:
498 if descending:
499 sortcolumn = sortcolumn[1:]
499 sortcolumn = sortcolumn[1:]
500 if sortcolumn not in sortable:
500 if sortcolumn not in sortable:
501 sortcolumn = b""
501 sortcolumn = b""
502
502
503 sort = [
503 sort = [
504 (
504 (
505 b"sort_%s" % column,
505 b"sort_%s" % column,
506 b"%s%s"
506 b"%s%s"
507 % (
507 % (
508 (not descending and column == sortcolumn) and b"-" or b"",
508 (not descending and column == sortcolumn) and b"-" or b"",
509 column,
509 column,
510 ),
510 ),
511 )
511 )
512 for column in sortable
512 for column in sortable
513 ]
513 ]
514
514
515 self.refresh()
515 self.refresh()
516
516
517 entries = indexentries(
517 entries = indexentries(
518 self.ui,
518 self.ui,
519 self.repos,
519 self.repos,
520 req,
520 req,
521 self.stripecount,
521 self.stripecount,
522 sortcolumn=sortcolumn,
522 sortcolumn=sortcolumn,
523 descending=descending,
523 descending=descending,
524 subdir=subdir,
524 subdir=subdir,
525 )
525 )
526
526
527 mapping = {
527 mapping = {
528 b'entries': entries,
528 b'entries': entries,
529 b'subdir': subdir,
529 b'subdir': subdir,
530 b'pathdef': hgweb_mod.makebreadcrumb(b'/' + subdir, self.prefix),
530 b'pathdef': hgweb_mod.makebreadcrumb(b'/' + subdir, self.prefix),
531 b'sortcolumn': sortcolumn,
531 b'sortcolumn': sortcolumn,
532 b'descending': descending,
532 b'descending': descending,
533 }
533 }
534 mapping.update(sort)
534 mapping.update(sort)
535 res.setbodygen(tmpl.generate(b'index', mapping))
535 res.setbodygen(tmpl.generate(b'index', mapping))
536 return res.sendresponse()
536 return res.sendresponse()
537
537
538 def templater(self, req, nonce):
538 def templater(self, req, nonce):
539 def config(section, name, default=uimod._unset, untrusted=True):
539 def config(section, name, default=uimod._unset, untrusted=True):
540 return self.ui.config(section, name, default, untrusted)
540 return self.ui.config(section, name, default, untrusted)
541
541
542 vars = {}
542 vars = {}
543 styles, (style, mapfile) = hgweb_mod.getstyle(
543 styles, (style, mapfile) = hgweb_mod.getstyle(
544 req, config, self.templatepath
544 req, config, self.templatepath
545 )
545 )
546 if style == styles[0]:
546 if style == styles[0]:
547 vars[b'style'] = style
547 vars[b'style'] = style
548
548
549 sessionvars = webutil.sessionvars(vars, b'?')
549 sessionvars = webutil.sessionvars(vars, b'?')
550 logourl = config(b'web', b'logourl')
550 logourl = config(b'web', b'logourl')
551 logoimg = config(b'web', b'logoimg')
551 logoimg = config(b'web', b'logoimg')
552 staticurl = (
552 staticurl = (
553 config(b'web', b'staticurl')
553 config(b'web', b'staticurl')
554 or req.apppath.rstrip(b'/') + b'/static/'
554 or req.apppath.rstrip(b'/') + b'/static/'
555 )
555 )
556 if not staticurl.endswith(b'/'):
556 if not staticurl.endswith(b'/'):
557 staticurl += b'/'
557 staticurl += b'/'
558
558
559 defaults = {
559 defaults = {
560 b"encoding": encoding.encoding,
560 b"encoding": encoding.encoding,
561 b"url": req.apppath + b'/',
561 b"url": req.apppath + b'/',
562 b"logourl": logourl,
562 b"logourl": logourl,
563 b"logoimg": logoimg,
563 b"logoimg": logoimg,
564 b"staticurl": staticurl,
564 b"staticurl": staticurl,
565 b"sessionvars": sessionvars,
565 b"sessionvars": sessionvars,
566 b"style": style,
566 b"style": style,
567 b"nonce": nonce,
567 b"nonce": nonce,
568 }
568 }
569 templatekeyword = registrar.templatekeyword(defaults)
569 templatekeyword = registrar.templatekeyword(defaults)
570
570
571 @templatekeyword(b'motd', requires=())
571 @templatekeyword(b'motd', requires=())
572 def motd(context, mapping):
572 def motd(context, mapping):
573 if self.motd is not None:
573 if self.motd is not None:
574 yield self.motd
574 yield self.motd
575 else:
575 else:
576 yield config(b'web', b'motd')
576 yield config(b'web', b'motd')
577
577
578 tmpl = templater.templater.frommapfile(mapfile, defaults=defaults)
578 tmpl = templater.templater.frommapfile(mapfile, defaults=defaults)
579 return tmpl
579 return tmpl
@@ -1,1599 +1,1599 b''
1 #
1 #
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import copy
10 import copy
11 import mimetypes
11 import mimetypes
12 import os
12 import os
13 import re
13 import re
14
14
15 from ..i18n import _
15 from ..i18n import _
16 from ..node import hex, short
16 from ..node import hex, short
17 from ..pycompat import getattr
17 from ..pycompat import getattr
18
18
19 from .common import (
19 from .common import (
20 ErrorResponse,
20 ErrorResponse,
21 HTTP_FORBIDDEN,
21 HTTP_FORBIDDEN,
22 HTTP_NOT_FOUND,
22 HTTP_NOT_FOUND,
23 get_contact,
23 get_contact,
24 paritygen,
24 paritygen,
25 staticfile,
25 staticfile,
26 )
26 )
27
27
28 from .. import (
28 from .. import (
29 archival,
29 archival,
30 dagop,
30 dagop,
31 encoding,
31 encoding,
32 error,
32 error,
33 graphmod,
33 graphmod,
34 pycompat,
34 pycompat,
35 revset,
35 revset,
36 revsetlang,
36 revsetlang,
37 scmutil,
37 scmutil,
38 smartset,
38 smartset,
39 templater,
39 templater,
40 templateutil,
40 templateutil,
41 )
41 )
42
42
43 from ..utils import stringutil
43 from ..utils import stringutil
44
44
45 from . import webutil
45 from . import webutil
46
46
47 __all__ = []
47 __all__ = []
48 commands = {}
48 commands = {}
49
49
50
50
51 class webcommand(object):
51 class webcommand(object):
52 """Decorator used to register a web command handler.
52 """Decorator used to register a web command handler.
53
53
54 The decorator takes as its positional arguments the name/path the
54 The decorator takes as its positional arguments the name/path the
55 command should be accessible under.
55 command should be accessible under.
56
56
57 When called, functions receive as arguments a ``requestcontext``,
57 When called, functions receive as arguments a ``requestcontext``,
58 ``wsgirequest``, and a templater instance for generatoring output.
58 ``wsgirequest``, and a templater instance for generatoring output.
59 The functions should populate the ``rctx.res`` object with details
59 The functions should populate the ``rctx.res`` object with details
60 about the HTTP response.
60 about the HTTP response.
61
61
62 The function returns a generator to be consumed by the WSGI application.
62 The function returns a generator to be consumed by the WSGI application.
63 For most commands, this should be the result from
63 For most commands, this should be the result from
64 ``web.res.sendresponse()``. Many commands will call ``web.sendtemplate()``
64 ``web.res.sendresponse()``. Many commands will call ``web.sendtemplate()``
65 to render a template.
65 to render a template.
66
66
67 Usage:
67 Usage:
68
68
69 @webcommand('mycommand')
69 @webcommand('mycommand')
70 def mycommand(web):
70 def mycommand(web):
71 pass
71 pass
72 """
72 """
73
73
74 def __init__(self, name):
74 def __init__(self, name):
75 self.name = name
75 self.name = name
76
76
77 def __call__(self, func):
77 def __call__(self, func):
78 __all__.append(self.name)
78 __all__.append(self.name)
79 commands[self.name] = func
79 commands[self.name] = func
80 return func
80 return func
81
81
82
82
83 @webcommand(b'log')
83 @webcommand(b'log')
84 def log(web):
84 def log(web):
85 """
85 """
86 /log[/{revision}[/{path}]]
86 /log[/{revision}[/{path}]]
87 --------------------------
87 --------------------------
88
88
89 Show repository or file history.
89 Show repository or file history.
90
90
91 For URLs of the form ``/log/{revision}``, a list of changesets starting at
91 For URLs of the form ``/log/{revision}``, a list of changesets starting at
92 the specified changeset identifier is shown. If ``{revision}`` is not
92 the specified changeset identifier is shown. If ``{revision}`` is not
93 defined, the default is ``tip``. This form is equivalent to the
93 defined, the default is ``tip``. This form is equivalent to the
94 ``changelog`` handler.
94 ``changelog`` handler.
95
95
96 For URLs of the form ``/log/{revision}/{file}``, the history for a specific
96 For URLs of the form ``/log/{revision}/{file}``, the history for a specific
97 file will be shown. This form is equivalent to the ``filelog`` handler.
97 file will be shown. This form is equivalent to the ``filelog`` handler.
98 """
98 """
99
99
100 if web.req.qsparams.get(b'file'):
100 if web.req.qsparams.get(b'file'):
101 return filelog(web)
101 return filelog(web)
102 else:
102 else:
103 return changelog(web)
103 return changelog(web)
104
104
105
105
106 @webcommand(b'rawfile')
106 @webcommand(b'rawfile')
107 def rawfile(web):
107 def rawfile(web):
108 guessmime = web.configbool(b'web', b'guessmime')
108 guessmime = web.configbool(b'web', b'guessmime')
109
109
110 path = webutil.cleanpath(web.repo, web.req.qsparams.get(b'file', b''))
110 path = webutil.cleanpath(web.repo, web.req.qsparams.get(b'file', b''))
111 if not path:
111 if not path:
112 return manifest(web)
112 return manifest(web)
113
113
114 try:
114 try:
115 fctx = webutil.filectx(web.repo, web.req)
115 fctx = webutil.filectx(web.repo, web.req)
116 except error.LookupError as inst:
116 except error.LookupError as inst:
117 try:
117 try:
118 return manifest(web)
118 return manifest(web)
119 except ErrorResponse:
119 except ErrorResponse:
120 raise inst
120 raise inst
121
121
122 path = fctx.path()
122 path = fctx.path()
123 text = fctx.data()
123 text = fctx.data()
124 mt = b'application/binary'
124 mt = b'application/binary'
125 if guessmime:
125 if guessmime:
126 mt = mimetypes.guess_type(pycompat.fsdecode(path))[0]
126 mt = mimetypes.guess_type(pycompat.fsdecode(path))[0]
127 if mt is None:
127 if mt is None:
128 if stringutil.binary(text):
128 if stringutil.binary(text):
129 mt = b'application/binary'
129 mt = b'application/binary'
130 else:
130 else:
131 mt = b'text/plain'
131 mt = b'text/plain'
132 else:
132 else:
133 mt = pycompat.sysbytes(mt)
133 mt = pycompat.sysbytes(mt)
134
134
135 if mt.startswith(b'text/'):
135 if mt.startswith(b'text/'):
136 mt += b'; charset="%s"' % encoding.encoding
136 mt += b'; charset="%s"' % encoding.encoding
137
137
138 web.res.headers[b'Content-Type'] = mt
138 web.res.headers[b'Content-Type'] = mt
139 filename = (
139 filename = (
140 path.rpartition(b'/')[-1].replace(b'\\', b'\\\\').replace(b'"', b'\\"')
140 path.rpartition(b'/')[-1].replace(b'\\', b'\\\\').replace(b'"', b'\\"')
141 )
141 )
142 web.res.headers[b'Content-Disposition'] = (
142 web.res.headers[b'Content-Disposition'] = (
143 b'inline; filename="%s"' % filename
143 b'inline; filename="%s"' % filename
144 )
144 )
145 web.res.setbodybytes(text)
145 web.res.setbodybytes(text)
146 return web.res.sendresponse()
146 return web.res.sendresponse()
147
147
148
148
149 def _filerevision(web, fctx):
149 def _filerevision(web, fctx):
150 f = fctx.path()
150 f = fctx.path()
151 text = fctx.data()
151 text = fctx.data()
152 parity = paritygen(web.stripecount)
152 parity = paritygen(web.stripecount)
153 ishead = fctx.filenode() in fctx.filelog().heads()
153 ishead = fctx.filenode() in fctx.filelog().heads()
154
154
155 if stringutil.binary(text):
155 if stringutil.binary(text):
156 mt = pycompat.sysbytes(
156 mt = pycompat.sysbytes(
157 mimetypes.guess_type(pycompat.fsdecode(f))[0]
157 mimetypes.guess_type(pycompat.fsdecode(f))[0]
158 or r'application/octet-stream'
158 or r'application/octet-stream'
159 )
159 )
160 text = b'(binary:%s)' % mt
160 text = b'(binary:%s)' % mt
161
161
162 def lines(context):
162 def lines(context):
163 for lineno, t in enumerate(text.splitlines(True)):
163 for lineno, t in enumerate(text.splitlines(True)):
164 yield {
164 yield {
165 b"line": t,
165 b"line": t,
166 b"lineid": b"l%d" % (lineno + 1),
166 b"lineid": b"l%d" % (lineno + 1),
167 b"linenumber": b"% 6d" % (lineno + 1),
167 b"linenumber": b"% 6d" % (lineno + 1),
168 b"parity": next(parity),
168 b"parity": next(parity),
169 }
169 }
170
170
171 return web.sendtemplate(
171 return web.sendtemplate(
172 b'filerevision',
172 b'filerevision',
173 file=f,
173 file=f,
174 path=webutil.up(f),
174 path=webutil.up(f),
175 text=templateutil.mappinggenerator(lines),
175 text=templateutil.mappinggenerator(lines),
176 symrev=webutil.symrevorshortnode(web.req, fctx),
176 symrev=webutil.symrevorshortnode(web.req, fctx),
177 rename=webutil.renamelink(fctx),
177 rename=webutil.renamelink(fctx),
178 permissions=fctx.manifest().flags(f),
178 permissions=fctx.manifest().flags(f),
179 ishead=int(ishead),
179 ishead=int(ishead),
180 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx))
180 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx))
181 )
181 )
182
182
183
183
184 @webcommand(b'file')
184 @webcommand(b'file')
185 def file(web):
185 def file(web):
186 """
186 """
187 /file/{revision}[/{path}]
187 /file/{revision}[/{path}]
188 -------------------------
188 -------------------------
189
189
190 Show information about a directory or file in the repository.
190 Show information about a directory or file in the repository.
191
191
192 Info about the ``path`` given as a URL parameter will be rendered.
192 Info about the ``path`` given as a URL parameter will be rendered.
193
193
194 If ``path`` is a directory, information about the entries in that
194 If ``path`` is a directory, information about the entries in that
195 directory will be rendered. This form is equivalent to the ``manifest``
195 directory will be rendered. This form is equivalent to the ``manifest``
196 handler.
196 handler.
197
197
198 If ``path`` is a file, information about that file will be shown via
198 If ``path`` is a file, information about that file will be shown via
199 the ``filerevision`` template.
199 the ``filerevision`` template.
200
200
201 If ``path`` is not defined, information about the root directory will
201 If ``path`` is not defined, information about the root directory will
202 be rendered.
202 be rendered.
203 """
203 """
204 if web.req.qsparams.get(b'style') == b'raw':
204 if web.req.qsparams.get(b'style') == b'raw':
205 return rawfile(web)
205 return rawfile(web)
206
206
207 path = webutil.cleanpath(web.repo, web.req.qsparams.get(b'file', b''))
207 path = webutil.cleanpath(web.repo, web.req.qsparams.get(b'file', b''))
208 if not path:
208 if not path:
209 return manifest(web)
209 return manifest(web)
210 try:
210 try:
211 return _filerevision(web, webutil.filectx(web.repo, web.req))
211 return _filerevision(web, webutil.filectx(web.repo, web.req))
212 except error.LookupError as inst:
212 except error.LookupError as inst:
213 try:
213 try:
214 return manifest(web)
214 return manifest(web)
215 except ErrorResponse:
215 except ErrorResponse:
216 raise inst
216 raise inst
217
217
218
218
219 def _search(web):
219 def _search(web):
220 MODE_REVISION = b'rev'
220 MODE_REVISION = b'rev'
221 MODE_KEYWORD = b'keyword'
221 MODE_KEYWORD = b'keyword'
222 MODE_REVSET = b'revset'
222 MODE_REVSET = b'revset'
223
223
224 def revsearch(ctx):
224 def revsearch(ctx):
225 yield ctx
225 yield ctx
226
226
227 def keywordsearch(query):
227 def keywordsearch(query):
228 lower = encoding.lower
228 lower = encoding.lower
229 qw = lower(query).split()
229 qw = lower(query).split()
230
230
231 def revgen():
231 def revgen():
232 cl = web.repo.changelog
232 cl = web.repo.changelog
233 for i in pycompat.xrange(len(web.repo) - 1, 0, -100):
233 for i in pycompat.xrange(len(web.repo) - 1, 0, -100):
234 l = []
234 l = []
235 for j in cl.revs(max(0, i - 99), i):
235 for j in cl.revs(max(0, i - 99), i):
236 ctx = web.repo[j]
236 ctx = web.repo[j]
237 l.append(ctx)
237 l.append(ctx)
238 l.reverse()
238 l.reverse()
239 for e in l:
239 for e in l:
240 yield e
240 yield e
241
241
242 for ctx in revgen():
242 for ctx in revgen():
243 miss = 0
243 miss = 0
244 for q in qw:
244 for q in qw:
245 if not (
245 if not (
246 q in lower(ctx.user())
246 q in lower(ctx.user())
247 or q in lower(ctx.description())
247 or q in lower(ctx.description())
248 or q in lower(b" ".join(ctx.files()))
248 or q in lower(b" ".join(ctx.files()))
249 ):
249 ):
250 miss = 1
250 miss = 1
251 break
251 break
252 if miss:
252 if miss:
253 continue
253 continue
254
254
255 yield ctx
255 yield ctx
256
256
257 def revsetsearch(revs):
257 def revsetsearch(revs):
258 for r in revs:
258 for r in revs:
259 yield web.repo[r]
259 yield web.repo[r]
260
260
261 searchfuncs = {
261 searchfuncs = {
262 MODE_REVISION: (revsearch, b'exact revision search'),
262 MODE_REVISION: (revsearch, b'exact revision search'),
263 MODE_KEYWORD: (keywordsearch, b'literal keyword search'),
263 MODE_KEYWORD: (keywordsearch, b'literal keyword search'),
264 MODE_REVSET: (revsetsearch, b'revset expression search'),
264 MODE_REVSET: (revsetsearch, b'revset expression search'),
265 }
265 }
266
266
267 def getsearchmode(query):
267 def getsearchmode(query):
268 try:
268 try:
269 ctx = scmutil.revsymbol(web.repo, query)
269 ctx = scmutil.revsymbol(web.repo, query)
270 except (error.RepoError, error.LookupError):
270 except (error.RepoError, error.LookupError):
271 # query is not an exact revision pointer, need to
271 # query is not an exact revision pointer, need to
272 # decide if it's a revset expression or keywords
272 # decide if it's a revset expression or keywords
273 pass
273 pass
274 else:
274 else:
275 return MODE_REVISION, ctx
275 return MODE_REVISION, ctx
276
276
277 revdef = b'reverse(%s)' % query
277 revdef = b'reverse(%s)' % query
278 try:
278 try:
279 tree = revsetlang.parse(revdef)
279 tree = revsetlang.parse(revdef)
280 except error.ParseError:
280 except error.ParseError:
281 # can't parse to a revset tree
281 # can't parse to a revset tree
282 return MODE_KEYWORD, query
282 return MODE_KEYWORD, query
283
283
284 if revsetlang.depth(tree) <= 2:
284 if revsetlang.depth(tree) <= 2:
285 # no revset syntax used
285 # no revset syntax used
286 return MODE_KEYWORD, query
286 return MODE_KEYWORD, query
287
287
288 if any(
288 if any(
289 (token, (value or b'')[:3]) == (b'string', b're:')
289 (token, (value or b'')[:3]) == (b'string', b're:')
290 for token, value, pos in revsetlang.tokenize(revdef)
290 for token, value, pos in revsetlang.tokenize(revdef)
291 ):
291 ):
292 return MODE_KEYWORD, query
292 return MODE_KEYWORD, query
293
293
294 funcsused = revsetlang.funcsused(tree)
294 funcsused = revsetlang.funcsused(tree)
295 if not funcsused.issubset(revset.safesymbols):
295 if not funcsused.issubset(revset.safesymbols):
296 return MODE_KEYWORD, query
296 return MODE_KEYWORD, query
297
297
298 try:
298 try:
299 mfunc = revset.match(
299 mfunc = revset.match(
300 web.repo.ui, revdef, lookup=revset.lookupfn(web.repo)
300 web.repo.ui, revdef, lookup=revset.lookupfn(web.repo)
301 )
301 )
302 revs = mfunc(web.repo)
302 revs = mfunc(web.repo)
303 return MODE_REVSET, revs
303 return MODE_REVSET, revs
304 # ParseError: wrongly placed tokens, wrongs arguments, etc
304 # ParseError: wrongly placed tokens, wrongs arguments, etc
305 # RepoLookupError: no such revision, e.g. in 'revision:'
305 # RepoLookupError: no such revision, e.g. in 'revision:'
306 # Abort: bookmark/tag not exists
306 # Abort: bookmark/tag not exists
307 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
307 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
308 except (
308 except (
309 error.ParseError,
309 error.ParseError,
310 error.RepoLookupError,
310 error.RepoLookupError,
311 error.Abort,
311 error.Abort,
312 LookupError,
312 LookupError,
313 ):
313 ):
314 return MODE_KEYWORD, query
314 return MODE_KEYWORD, query
315
315
316 def changelist(context):
316 def changelist(context):
317 count = 0
317 count = 0
318
318
319 for ctx in searchfunc[0](funcarg):
319 for ctx in searchfunc[0](funcarg):
320 count += 1
320 count += 1
321 n = scmutil.binnode(ctx)
321 n = scmutil.binnode(ctx)
322 showtags = webutil.showtag(web.repo, b'changelogtag', n)
322 showtags = webutil.showtag(web.repo, b'changelogtag', n)
323 files = webutil.listfilediffs(ctx.files(), n, web.maxfiles)
323 files = webutil.listfilediffs(ctx.files(), n, web.maxfiles)
324
324
325 lm = webutil.commonentry(web.repo, ctx)
325 lm = webutil.commonentry(web.repo, ctx)
326 lm.update(
326 lm.update(
327 {
327 {
328 b'parity': next(parity),
328 b'parity': next(parity),
329 b'changelogtag': showtags,
329 b'changelogtag': showtags,
330 b'files': files,
330 b'files': files,
331 }
331 }
332 )
332 )
333 yield lm
333 yield lm
334
334
335 if count >= revcount:
335 if count >= revcount:
336 break
336 break
337
337
338 query = web.req.qsparams[b'rev']
338 query = web.req.qsparams[b'rev']
339 revcount = web.maxchanges
339 revcount = web.maxchanges
340 if b'revcount' in web.req.qsparams:
340 if b'revcount' in web.req.qsparams:
341 try:
341 try:
342 revcount = int(web.req.qsparams.get(b'revcount', revcount))
342 revcount = int(web.req.qsparams.get(b'revcount', revcount))
343 revcount = max(revcount, 1)
343 revcount = max(revcount, 1)
344 web.tmpl.defaults[b'sessionvars'][b'revcount'] = revcount
344 web.tmpl.defaults[b'sessionvars'][b'revcount'] = revcount
345 except ValueError:
345 except ValueError:
346 pass
346 pass
347
347
348 lessvars = copy.copy(web.tmpl.defaults[b'sessionvars'])
348 lessvars = copy.copy(web.tmpl.defaults[b'sessionvars'])
349 lessvars[b'revcount'] = max(revcount // 2, 1)
349 lessvars[b'revcount'] = max(revcount // 2, 1)
350 lessvars[b'rev'] = query
350 lessvars[b'rev'] = query
351 morevars = copy.copy(web.tmpl.defaults[b'sessionvars'])
351 morevars = copy.copy(web.tmpl.defaults[b'sessionvars'])
352 morevars[b'revcount'] = revcount * 2
352 morevars[b'revcount'] = revcount * 2
353 morevars[b'rev'] = query
353 morevars[b'rev'] = query
354
354
355 mode, funcarg = getsearchmode(query)
355 mode, funcarg = getsearchmode(query)
356
356
357 if b'forcekw' in web.req.qsparams:
357 if b'forcekw' in web.req.qsparams:
358 showforcekw = b''
358 showforcekw = b''
359 showunforcekw = searchfuncs[mode][1]
359 showunforcekw = searchfuncs[mode][1]
360 mode = MODE_KEYWORD
360 mode = MODE_KEYWORD
361 funcarg = query
361 funcarg = query
362 else:
362 else:
363 if mode != MODE_KEYWORD:
363 if mode != MODE_KEYWORD:
364 showforcekw = searchfuncs[MODE_KEYWORD][1]
364 showforcekw = searchfuncs[MODE_KEYWORD][1]
365 else:
365 else:
366 showforcekw = b''
366 showforcekw = b''
367 showunforcekw = b''
367 showunforcekw = b''
368
368
369 searchfunc = searchfuncs[mode]
369 searchfunc = searchfuncs[mode]
370
370
371 tip = web.repo[b'tip']
371 tip = web.repo[b'tip']
372 parity = paritygen(web.stripecount)
372 parity = paritygen(web.stripecount)
373
373
374 return web.sendtemplate(
374 return web.sendtemplate(
375 b'search',
375 b'search',
376 query=query,
376 query=query,
377 node=tip.hex(),
377 node=tip.hex(),
378 symrev=b'tip',
378 symrev=b'tip',
379 entries=templateutil.mappinggenerator(changelist, name=b'searchentry'),
379 entries=templateutil.mappinggenerator(changelist, name=b'searchentry'),
380 archives=web.archivelist(b'tip'),
380 archives=web.archivelist(b'tip'),
381 morevars=morevars,
381 morevars=morevars,
382 lessvars=lessvars,
382 lessvars=lessvars,
383 modedesc=searchfunc[1],
383 modedesc=searchfunc[1],
384 showforcekw=showforcekw,
384 showforcekw=showforcekw,
385 showunforcekw=showunforcekw,
385 showunforcekw=showunforcekw,
386 )
386 )
387
387
388
388
389 @webcommand(b'changelog')
389 @webcommand(b'changelog')
390 def changelog(web, shortlog=False):
390 def changelog(web, shortlog=False):
391 """
391 """
392 /changelog[/{revision}]
392 /changelog[/{revision}]
393 -----------------------
393 -----------------------
394
394
395 Show information about multiple changesets.
395 Show information about multiple changesets.
396
396
397 If the optional ``revision`` URL argument is absent, information about
397 If the optional ``revision`` URL argument is absent, information about
398 all changesets starting at ``tip`` will be rendered. If the ``revision``
398 all changesets starting at ``tip`` will be rendered. If the ``revision``
399 argument is present, changesets will be shown starting from the specified
399 argument is present, changesets will be shown starting from the specified
400 revision.
400 revision.
401
401
402 If ``revision`` is absent, the ``rev`` query string argument may be
402 If ``revision`` is absent, the ``rev`` query string argument may be
403 defined. This will perform a search for changesets.
403 defined. This will perform a search for changesets.
404
404
405 The argument for ``rev`` can be a single revision, a revision set,
405 The argument for ``rev`` can be a single revision, a revision set,
406 or a literal keyword to search for in changeset data (equivalent to
406 or a literal keyword to search for in changeset data (equivalent to
407 :hg:`log -k`).
407 :hg:`log -k`).
408
408
409 The ``revcount`` query string argument defines the maximum numbers of
409 The ``revcount`` query string argument defines the maximum numbers of
410 changesets to render.
410 changesets to render.
411
411
412 For non-searches, the ``changelog`` template will be rendered.
412 For non-searches, the ``changelog`` template will be rendered.
413 """
413 """
414
414
415 query = b''
415 query = b''
416 if b'node' in web.req.qsparams:
416 if b'node' in web.req.qsparams:
417 ctx = webutil.changectx(web.repo, web.req)
417 ctx = webutil.changectx(web.repo, web.req)
418 symrev = webutil.symrevorshortnode(web.req, ctx)
418 symrev = webutil.symrevorshortnode(web.req, ctx)
419 elif b'rev' in web.req.qsparams:
419 elif b'rev' in web.req.qsparams:
420 return _search(web)
420 return _search(web)
421 else:
421 else:
422 ctx = web.repo[b'tip']
422 ctx = web.repo[b'tip']
423 symrev = b'tip'
423 symrev = b'tip'
424
424
425 def changelist(maxcount):
425 def changelist(maxcount):
426 revs = []
426 revs = []
427 if pos != -1:
427 if pos != -1:
428 revs = web.repo.changelog.revs(pos, 0)
428 revs = web.repo.changelog.revs(pos, 0)
429
429
430 for entry in webutil.changelistentries(web, revs, maxcount, parity):
430 for entry in webutil.changelistentries(web, revs, maxcount, parity):
431 yield entry
431 yield entry
432
432
433 if shortlog:
433 if shortlog:
434 revcount = web.maxshortchanges
434 revcount = web.maxshortchanges
435 else:
435 else:
436 revcount = web.maxchanges
436 revcount = web.maxchanges
437
437
438 if b'revcount' in web.req.qsparams:
438 if b'revcount' in web.req.qsparams:
439 try:
439 try:
440 revcount = int(web.req.qsparams.get(b'revcount', revcount))
440 revcount = int(web.req.qsparams.get(b'revcount', revcount))
441 revcount = max(revcount, 1)
441 revcount = max(revcount, 1)
442 web.tmpl.defaults[b'sessionvars'][b'revcount'] = revcount
442 web.tmpl.defaults[b'sessionvars'][b'revcount'] = revcount
443 except ValueError:
443 except ValueError:
444 pass
444 pass
445
445
446 lessvars = copy.copy(web.tmpl.defaults[b'sessionvars'])
446 lessvars = copy.copy(web.tmpl.defaults[b'sessionvars'])
447 lessvars[b'revcount'] = max(revcount // 2, 1)
447 lessvars[b'revcount'] = max(revcount // 2, 1)
448 morevars = copy.copy(web.tmpl.defaults[b'sessionvars'])
448 morevars = copy.copy(web.tmpl.defaults[b'sessionvars'])
449 morevars[b'revcount'] = revcount * 2
449 morevars[b'revcount'] = revcount * 2
450
450
451 count = len(web.repo)
451 count = len(web.repo)
452 pos = ctx.rev()
452 pos = ctx.rev()
453 parity = paritygen(web.stripecount)
453 parity = paritygen(web.stripecount)
454
454
455 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
455 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
456
456
457 entries = list(changelist(revcount + 1))
457 entries = list(changelist(revcount + 1))
458 latestentry = entries[:1]
458 latestentry = entries[:1]
459 if len(entries) > revcount:
459 if len(entries) > revcount:
460 nextentry = entries[-1:]
460 nextentry = entries[-1:]
461 entries = entries[:-1]
461 entries = entries[:-1]
462 else:
462 else:
463 nextentry = []
463 nextentry = []
464
464
465 return web.sendtemplate(
465 return web.sendtemplate(
466 b'shortlog' if shortlog else b'changelog',
466 b'shortlog' if shortlog else b'changelog',
467 changenav=changenav,
467 changenav=changenav,
468 node=ctx.hex(),
468 node=ctx.hex(),
469 rev=pos,
469 rev=pos,
470 symrev=symrev,
470 symrev=symrev,
471 changesets=count,
471 changesets=count,
472 entries=templateutil.mappinglist(entries),
472 entries=templateutil.mappinglist(entries),
473 latestentry=templateutil.mappinglist(latestentry),
473 latestentry=templateutil.mappinglist(latestentry),
474 nextentry=templateutil.mappinglist(nextentry),
474 nextentry=templateutil.mappinglist(nextentry),
475 archives=web.archivelist(b'tip'),
475 archives=web.archivelist(b'tip'),
476 revcount=revcount,
476 revcount=revcount,
477 morevars=morevars,
477 morevars=morevars,
478 lessvars=lessvars,
478 lessvars=lessvars,
479 query=query,
479 query=query,
480 )
480 )
481
481
482
482
483 @webcommand(b'shortlog')
483 @webcommand(b'shortlog')
484 def shortlog(web):
484 def shortlog(web):
485 """
485 """
486 /shortlog
486 /shortlog
487 ---------
487 ---------
488
488
489 Show basic information about a set of changesets.
489 Show basic information about a set of changesets.
490
490
491 This accepts the same parameters as the ``changelog`` handler. The only
491 This accepts the same parameters as the ``changelog`` handler. The only
492 difference is the ``shortlog`` template will be rendered instead of the
492 difference is the ``shortlog`` template will be rendered instead of the
493 ``changelog`` template.
493 ``changelog`` template.
494 """
494 """
495 return changelog(web, shortlog=True)
495 return changelog(web, shortlog=True)
496
496
497
497
498 @webcommand(b'changeset')
498 @webcommand(b'changeset')
499 def changeset(web):
499 def changeset(web):
500 """
500 """
501 /changeset[/{revision}]
501 /changeset[/{revision}]
502 -----------------------
502 -----------------------
503
503
504 Show information about a single changeset.
504 Show information about a single changeset.
505
505
506 A URL path argument is the changeset identifier to show. See ``hg help
506 A URL path argument is the changeset identifier to show. See ``hg help
507 revisions`` for possible values. If not defined, the ``tip`` changeset
507 revisions`` for possible values. If not defined, the ``tip`` changeset
508 will be shown.
508 will be shown.
509
509
510 The ``changeset`` template is rendered. Contents of the ``changesettag``,
510 The ``changeset`` template is rendered. Contents of the ``changesettag``,
511 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
511 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
512 templates related to diffs may all be used to produce the output.
512 templates related to diffs may all be used to produce the output.
513 """
513 """
514 ctx = webutil.changectx(web.repo, web.req)
514 ctx = webutil.changectx(web.repo, web.req)
515
515
516 return web.sendtemplate(b'changeset', **webutil.changesetentry(web, ctx))
516 return web.sendtemplate(b'changeset', **webutil.changesetentry(web, ctx))
517
517
518
518
519 rev = webcommand(b'rev')(changeset)
519 rev = webcommand(b'rev')(changeset)
520
520
521
521
522 def decodepath(path):
522 def decodepath(path):
523 """Hook for mapping a path in the repository to a path in the
523 """Hook for mapping a path in the repository to a path in the
524 working copy.
524 working copy.
525
525
526 Extensions (e.g., largefiles) can override this to remap files in
526 Extensions (e.g., largefiles) can override this to remap files in
527 the virtual file system presented by the manifest command below."""
527 the virtual file system presented by the manifest command below."""
528 return path
528 return path
529
529
530
530
531 @webcommand(b'manifest')
531 @webcommand(b'manifest')
532 def manifest(web):
532 def manifest(web):
533 """
533 """
534 /manifest[/{revision}[/{path}]]
534 /manifest[/{revision}[/{path}]]
535 -------------------------------
535 -------------------------------
536
536
537 Show information about a directory.
537 Show information about a directory.
538
538
539 If the URL path arguments are omitted, information about the root
539 If the URL path arguments are omitted, information about the root
540 directory for the ``tip`` changeset will be shown.
540 directory for the ``tip`` changeset will be shown.
541
541
542 Because this handler can only show information for directories, it
542 Because this handler can only show information for directories, it
543 is recommended to use the ``file`` handler instead, as it can handle both
543 is recommended to use the ``file`` handler instead, as it can handle both
544 directories and files.
544 directories and files.
545
545
546 The ``manifest`` template will be rendered for this handler.
546 The ``manifest`` template will be rendered for this handler.
547 """
547 """
548 if b'node' in web.req.qsparams:
548 if b'node' in web.req.qsparams:
549 ctx = webutil.changectx(web.repo, web.req)
549 ctx = webutil.changectx(web.repo, web.req)
550 symrev = webutil.symrevorshortnode(web.req, ctx)
550 symrev = webutil.symrevorshortnode(web.req, ctx)
551 else:
551 else:
552 ctx = web.repo[b'tip']
552 ctx = web.repo[b'tip']
553 symrev = b'tip'
553 symrev = b'tip'
554 path = webutil.cleanpath(web.repo, web.req.qsparams.get(b'file', b''))
554 path = webutil.cleanpath(web.repo, web.req.qsparams.get(b'file', b''))
555 mf = ctx.manifest()
555 mf = ctx.manifest()
556 node = scmutil.binnode(ctx)
556 node = scmutil.binnode(ctx)
557
557
558 files = {}
558 files = {}
559 dirs = {}
559 dirs = {}
560 parity = paritygen(web.stripecount)
560 parity = paritygen(web.stripecount)
561
561
562 if path and path[-1:] != b"/":
562 if path and path[-1:] != b"/":
563 path += b"/"
563 path += b"/"
564 l = len(path)
564 l = len(path)
565 abspath = b"/" + path
565 abspath = b"/" + path
566
566
567 for full, n in pycompat.iteritems(mf):
567 for full, n in pycompat.iteritems(mf):
568 # the virtual path (working copy path) used for the full
568 # the virtual path (working copy path) used for the full
569 # (repository) path
569 # (repository) path
570 f = decodepath(full)
570 f = decodepath(full)
571
571
572 if f[:l] != path:
572 if f[:l] != path:
573 continue
573 continue
574 remain = f[l:]
574 remain = f[l:]
575 elements = remain.split(b'/')
575 elements = remain.split(b'/')
576 if len(elements) == 1:
576 if len(elements) == 1:
577 files[remain] = full
577 files[remain] = full
578 else:
578 else:
579 h = dirs # need to retain ref to dirs (root)
579 h = dirs # need to retain ref to dirs (root)
580 for elem in elements[0:-1]:
580 for elem in elements[0:-1]:
581 if elem not in h:
581 if elem not in h:
582 h[elem] = {}
582 h[elem] = {}
583 h = h[elem]
583 h = h[elem]
584 if len(h) > 1:
584 if len(h) > 1:
585 break
585 break
586 h[None] = None # denotes files present
586 h[None] = None # denotes files present
587
587
588 if mf and not files and not dirs:
588 if mf and not files and not dirs:
589 raise ErrorResponse(HTTP_NOT_FOUND, b'path not found: ' + path)
589 raise ErrorResponse(HTTP_NOT_FOUND, b'path not found: ' + path)
590
590
591 def filelist(context):
591 def filelist(context):
592 for f in sorted(files):
592 for f in sorted(files):
593 full = files[f]
593 full = files[f]
594
594
595 fctx = ctx.filectx(full)
595 fctx = ctx.filectx(full)
596 yield {
596 yield {
597 b"file": full,
597 b"file": full,
598 b"parity": next(parity),
598 b"parity": next(parity),
599 b"basename": f,
599 b"basename": f,
600 b"date": fctx.date(),
600 b"date": fctx.date(),
601 b"size": fctx.size(),
601 b"size": fctx.size(),
602 b"permissions": mf.flags(full),
602 b"permissions": mf.flags(full),
603 }
603 }
604
604
605 def dirlist(context):
605 def dirlist(context):
606 for d in sorted(dirs):
606 for d in sorted(dirs):
607
607
608 emptydirs = []
608 emptydirs = []
609 h = dirs[d]
609 h = dirs[d]
610 while isinstance(h, dict) and len(h) == 1:
610 while isinstance(h, dict) and len(h) == 1:
611 k, v = next(iter(h.items()))
611 k, v = next(iter(h.items()))
612 if v:
612 if v:
613 emptydirs.append(k)
613 emptydirs.append(k)
614 h = v
614 h = v
615
615
616 path = b"%s%s" % (abspath, d)
616 path = b"%s%s" % (abspath, d)
617 yield {
617 yield {
618 b"parity": next(parity),
618 b"parity": next(parity),
619 b"path": path,
619 b"path": path,
620 b"emptydirs": b"/".join(emptydirs),
620 b"emptydirs": b"/".join(emptydirs),
621 b"basename": d,
621 b"basename": d,
622 }
622 }
623
623
624 return web.sendtemplate(
624 return web.sendtemplate(
625 b'manifest',
625 b'manifest',
626 symrev=symrev,
626 symrev=symrev,
627 path=abspath,
627 path=abspath,
628 up=webutil.up(abspath),
628 up=webutil.up(abspath),
629 upparity=next(parity),
629 upparity=next(parity),
630 fentries=templateutil.mappinggenerator(filelist),
630 fentries=templateutil.mappinggenerator(filelist),
631 dentries=templateutil.mappinggenerator(dirlist),
631 dentries=templateutil.mappinggenerator(dirlist),
632 archives=web.archivelist(hex(node)),
632 archives=web.archivelist(hex(node)),
633 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx))
633 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx))
634 )
634 )
635
635
636
636
637 @webcommand(b'tags')
637 @webcommand(b'tags')
638 def tags(web):
638 def tags(web):
639 """
639 """
640 /tags
640 /tags
641 -----
641 -----
642
642
643 Show information about tags.
643 Show information about tags.
644
644
645 No arguments are accepted.
645 No arguments are accepted.
646
646
647 The ``tags`` template is rendered.
647 The ``tags`` template is rendered.
648 """
648 """
649 i = list(reversed(web.repo.tagslist()))
649 i = list(reversed(web.repo.tagslist()))
650 parity = paritygen(web.stripecount)
650 parity = paritygen(web.stripecount)
651
651
652 def entries(context, notip, latestonly):
652 def entries(context, notip, latestonly):
653 t = i
653 t = i
654 if notip:
654 if notip:
655 t = [(k, n) for k, n in i if k != b"tip"]
655 t = [(k, n) for k, n in i if k != b"tip"]
656 if latestonly:
656 if latestonly:
657 t = t[:1]
657 t = t[:1]
658 for k, n in t:
658 for k, n in t:
659 yield {
659 yield {
660 b"parity": next(parity),
660 b"parity": next(parity),
661 b"tag": k,
661 b"tag": k,
662 b"date": web.repo[n].date(),
662 b"date": web.repo[n].date(),
663 b"node": hex(n),
663 b"node": hex(n),
664 }
664 }
665
665
666 return web.sendtemplate(
666 return web.sendtemplate(
667 b'tags',
667 b'tags',
668 node=hex(web.repo.changelog.tip()),
668 node=hex(web.repo.changelog.tip()),
669 entries=templateutil.mappinggenerator(entries, args=(False, False)),
669 entries=templateutil.mappinggenerator(entries, args=(False, False)),
670 entriesnotip=templateutil.mappinggenerator(entries, args=(True, False)),
670 entriesnotip=templateutil.mappinggenerator(entries, args=(True, False)),
671 latestentry=templateutil.mappinggenerator(entries, args=(True, True)),
671 latestentry=templateutil.mappinggenerator(entries, args=(True, True)),
672 )
672 )
673
673
674
674
675 @webcommand(b'bookmarks')
675 @webcommand(b'bookmarks')
676 def bookmarks(web):
676 def bookmarks(web):
677 """
677 """
678 /bookmarks
678 /bookmarks
679 ----------
679 ----------
680
680
681 Show information about bookmarks.
681 Show information about bookmarks.
682
682
683 No arguments are accepted.
683 No arguments are accepted.
684
684
685 The ``bookmarks`` template is rendered.
685 The ``bookmarks`` template is rendered.
686 """
686 """
687 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
687 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
688 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
688 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
689 i = sorted(i, key=sortkey, reverse=True)
689 i = sorted(i, key=sortkey, reverse=True)
690 parity = paritygen(web.stripecount)
690 parity = paritygen(web.stripecount)
691
691
692 def entries(context, latestonly):
692 def entries(context, latestonly):
693 t = i
693 t = i
694 if latestonly:
694 if latestonly:
695 t = i[:1]
695 t = i[:1]
696 for k, n in t:
696 for k, n in t:
697 yield {
697 yield {
698 b"parity": next(parity),
698 b"parity": next(parity),
699 b"bookmark": k,
699 b"bookmark": k,
700 b"date": web.repo[n].date(),
700 b"date": web.repo[n].date(),
701 b"node": hex(n),
701 b"node": hex(n),
702 }
702 }
703
703
704 if i:
704 if i:
705 latestrev = i[0][1]
705 latestrev = i[0][1]
706 else:
706 else:
707 latestrev = -1
707 latestrev = -1
708 lastdate = web.repo[latestrev].date()
708 lastdate = web.repo[latestrev].date()
709
709
710 return web.sendtemplate(
710 return web.sendtemplate(
711 b'bookmarks',
711 b'bookmarks',
712 node=hex(web.repo.changelog.tip()),
712 node=hex(web.repo.changelog.tip()),
713 lastchange=templateutil.mappinglist([{b'date': lastdate}]),
713 lastchange=templateutil.mappinglist([{b'date': lastdate}]),
714 entries=templateutil.mappinggenerator(entries, args=(False,)),
714 entries=templateutil.mappinggenerator(entries, args=(False,)),
715 latestentry=templateutil.mappinggenerator(entries, args=(True,)),
715 latestentry=templateutil.mappinggenerator(entries, args=(True,)),
716 )
716 )
717
717
718
718
719 @webcommand(b'branches')
719 @webcommand(b'branches')
720 def branches(web):
720 def branches(web):
721 """
721 """
722 /branches
722 /branches
723 ---------
723 ---------
724
724
725 Show information about branches.
725 Show information about branches.
726
726
727 All known branches are contained in the output, even closed branches.
727 All known branches are contained in the output, even closed branches.
728
728
729 No arguments are accepted.
729 No arguments are accepted.
730
730
731 The ``branches`` template is rendered.
731 The ``branches`` template is rendered.
732 """
732 """
733 entries = webutil.branchentries(web.repo, web.stripecount)
733 entries = webutil.branchentries(web.repo, web.stripecount)
734 latestentry = webutil.branchentries(web.repo, web.stripecount, 1)
734 latestentry = webutil.branchentries(web.repo, web.stripecount, 1)
735
735
736 return web.sendtemplate(
736 return web.sendtemplate(
737 b'branches',
737 b'branches',
738 node=hex(web.repo.changelog.tip()),
738 node=hex(web.repo.changelog.tip()),
739 entries=entries,
739 entries=entries,
740 latestentry=latestentry,
740 latestentry=latestentry,
741 )
741 )
742
742
743
743
744 @webcommand(b'summary')
744 @webcommand(b'summary')
745 def summary(web):
745 def summary(web):
746 """
746 """
747 /summary
747 /summary
748 --------
748 --------
749
749
750 Show a summary of repository state.
750 Show a summary of repository state.
751
751
752 Information about the latest changesets, bookmarks, tags, and branches
752 Information about the latest changesets, bookmarks, tags, and branches
753 is captured by this handler.
753 is captured by this handler.
754
754
755 The ``summary`` template is rendered.
755 The ``summary`` template is rendered.
756 """
756 """
757 i = reversed(web.repo.tagslist())
757 i = reversed(web.repo.tagslist())
758
758
759 def tagentries(context):
759 def tagentries(context):
760 parity = paritygen(web.stripecount)
760 parity = paritygen(web.stripecount)
761 count = 0
761 count = 0
762 for k, n in i:
762 for k, n in i:
763 if k == b"tip": # skip tip
763 if k == b"tip": # skip tip
764 continue
764 continue
765
765
766 count += 1
766 count += 1
767 if count > 10: # limit to 10 tags
767 if count > 10: # limit to 10 tags
768 break
768 break
769
769
770 yield {
770 yield {
771 b'parity': next(parity),
771 b'parity': next(parity),
772 b'tag': k,
772 b'tag': k,
773 b'node': hex(n),
773 b'node': hex(n),
774 b'date': web.repo[n].date(),
774 b'date': web.repo[n].date(),
775 }
775 }
776
776
777 def bookmarks(context):
777 def bookmarks(context):
778 parity = paritygen(web.stripecount)
778 parity = paritygen(web.stripecount)
779 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
779 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
780 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
780 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
781 marks = sorted(marks, key=sortkey, reverse=True)
781 marks = sorted(marks, key=sortkey, reverse=True)
782 for k, n in marks[:10]: # limit to 10 bookmarks
782 for k, n in marks[:10]: # limit to 10 bookmarks
783 yield {
783 yield {
784 b'parity': next(parity),
784 b'parity': next(parity),
785 b'bookmark': k,
785 b'bookmark': k,
786 b'date': web.repo[n].date(),
786 b'date': web.repo[n].date(),
787 b'node': hex(n),
787 b'node': hex(n),
788 }
788 }
789
789
790 def changelist(context):
790 def changelist(context):
791 parity = paritygen(web.stripecount, offset=start - end)
791 parity = paritygen(web.stripecount, offset=start - end)
792 l = [] # build a list in forward order for efficiency
792 l = [] # build a list in forward order for efficiency
793 revs = []
793 revs = []
794 if start < end:
794 if start < end:
795 revs = web.repo.changelog.revs(start, end - 1)
795 revs = web.repo.changelog.revs(start, end - 1)
796 for i in revs:
796 for i in revs:
797 ctx = web.repo[i]
797 ctx = web.repo[i]
798 lm = webutil.commonentry(web.repo, ctx)
798 lm = webutil.commonentry(web.repo, ctx)
799 lm[b'parity'] = next(parity)
799 lm[b'parity'] = next(parity)
800 l.append(lm)
800 l.append(lm)
801
801
802 for entry in reversed(l):
802 for entry in reversed(l):
803 yield entry
803 yield entry
804
804
805 tip = web.repo[b'tip']
805 tip = web.repo[b'tip']
806 count = len(web.repo)
806 count = len(web.repo)
807 start = max(0, count - web.maxchanges)
807 start = max(0, count - web.maxchanges)
808 end = min(count, start + web.maxchanges)
808 end = min(count, start + web.maxchanges)
809
809
810 desc = web.config(b"web", b"description")
810 desc = web.config(b"web", b"description")
811 if not desc:
811 if not desc:
812 desc = b'unknown'
812 desc = b'unknown'
813 labels = web.configlist(b'web', b'labels')
813 labels = web.configlist(b'web', b'labels')
814
814
815 return web.sendtemplate(
815 return web.sendtemplate(
816 b'summary',
816 b'summary',
817 desc=desc,
817 desc=desc,
818 owner=get_contact(web.config) or b'unknown',
818 owner=get_contact(web.config) or b'unknown',
819 lastchange=tip.date(),
819 lastchange=tip.date(),
820 tags=templateutil.mappinggenerator(tagentries, name=b'tagentry'),
820 tags=templateutil.mappinggenerator(tagentries, name=b'tagentry'),
821 bookmarks=templateutil.mappinggenerator(bookmarks),
821 bookmarks=templateutil.mappinggenerator(bookmarks),
822 branches=webutil.branchentries(web.repo, web.stripecount, 10),
822 branches=webutil.branchentries(web.repo, web.stripecount, 10),
823 shortlog=templateutil.mappinggenerator(
823 shortlog=templateutil.mappinggenerator(
824 changelist, name=b'shortlogentry'
824 changelist, name=b'shortlogentry'
825 ),
825 ),
826 node=tip.hex(),
826 node=tip.hex(),
827 symrev=b'tip',
827 symrev=b'tip',
828 archives=web.archivelist(b'tip'),
828 archives=web.archivelist(b'tip'),
829 labels=templateutil.hybridlist(labels, name=b'label'),
829 labels=templateutil.hybridlist(labels, name=b'label'),
830 )
830 )
831
831
832
832
833 @webcommand(b'filediff')
833 @webcommand(b'filediff')
834 def filediff(web):
834 def filediff(web):
835 """
835 """
836 /diff/{revision}/{path}
836 /diff/{revision}/{path}
837 -----------------------
837 -----------------------
838
838
839 Show how a file changed in a particular commit.
839 Show how a file changed in a particular commit.
840
840
841 The ``filediff`` template is rendered.
841 The ``filediff`` template is rendered.
842
842
843 This handler is registered under both the ``/diff`` and ``/filediff``
843 This handler is registered under both the ``/diff`` and ``/filediff``
844 paths. ``/diff`` is used in modern code.
844 paths. ``/diff`` is used in modern code.
845 """
845 """
846 fctx, ctx = None, None
846 fctx, ctx = None, None
847 try:
847 try:
848 fctx = webutil.filectx(web.repo, web.req)
848 fctx = webutil.filectx(web.repo, web.req)
849 except LookupError:
849 except LookupError:
850 ctx = webutil.changectx(web.repo, web.req)
850 ctx = webutil.changectx(web.repo, web.req)
851 path = webutil.cleanpath(web.repo, web.req.qsparams[b'file'])
851 path = webutil.cleanpath(web.repo, web.req.qsparams[b'file'])
852 if path not in ctx.files():
852 if path not in ctx.files():
853 raise
853 raise
854
854
855 if fctx is not None:
855 if fctx is not None:
856 path = fctx.path()
856 path = fctx.path()
857 ctx = fctx.changectx()
857 ctx = fctx.changectx()
858 basectx = ctx.p1()
858 basectx = ctx.p1()
859
859
860 style = web.config(b'web', b'style')
860 style = web.config(b'web', b'style')
861 if b'style' in web.req.qsparams:
861 if b'style' in web.req.qsparams:
862 style = web.req.qsparams[b'style']
862 style = web.req.qsparams[b'style']
863
863
864 diffs = webutil.diffs(web, ctx, basectx, [path], style)
864 diffs = webutil.diffs(web, ctx, basectx, [path], style)
865 if fctx is not None:
865 if fctx is not None:
866 rename = webutil.renamelink(fctx)
866 rename = webutil.renamelink(fctx)
867 ctx = fctx
867 ctx = fctx
868 else:
868 else:
869 rename = templateutil.mappinglist([])
869 rename = templateutil.mappinglist([])
870 ctx = ctx
870 ctx = ctx
871
871
872 return web.sendtemplate(
872 return web.sendtemplate(
873 b'filediff',
873 b'filediff',
874 file=path,
874 file=path,
875 symrev=webutil.symrevorshortnode(web.req, ctx),
875 symrev=webutil.symrevorshortnode(web.req, ctx),
876 rename=rename,
876 rename=rename,
877 diff=diffs,
877 diff=diffs,
878 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx))
878 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx))
879 )
879 )
880
880
881
881
882 diff = webcommand(b'diff')(filediff)
882 diff = webcommand(b'diff')(filediff)
883
883
884
884
885 @webcommand(b'comparison')
885 @webcommand(b'comparison')
886 def comparison(web):
886 def comparison(web):
887 """
887 """
888 /comparison/{revision}/{path}
888 /comparison/{revision}/{path}
889 -----------------------------
889 -----------------------------
890
890
891 Show a comparison between the old and new versions of a file from changes
891 Show a comparison between the old and new versions of a file from changes
892 made on a particular revision.
892 made on a particular revision.
893
893
894 This is similar to the ``diff`` handler. However, this form features
894 This is similar to the ``diff`` handler. However, this form features
895 a split or side-by-side diff rather than a unified diff.
895 a split or side-by-side diff rather than a unified diff.
896
896
897 The ``context`` query string argument can be used to control the lines of
897 The ``context`` query string argument can be used to control the lines of
898 context in the diff.
898 context in the diff.
899
899
900 The ``filecomparison`` template is rendered.
900 The ``filecomparison`` template is rendered.
901 """
901 """
902 ctx = webutil.changectx(web.repo, web.req)
902 ctx = webutil.changectx(web.repo, web.req)
903 if b'file' not in web.req.qsparams:
903 if b'file' not in web.req.qsparams:
904 raise ErrorResponse(HTTP_NOT_FOUND, b'file not given')
904 raise ErrorResponse(HTTP_NOT_FOUND, b'file not given')
905 path = webutil.cleanpath(web.repo, web.req.qsparams[b'file'])
905 path = webutil.cleanpath(web.repo, web.req.qsparams[b'file'])
906
906
907 parsecontext = lambda v: v == b'full' and -1 or int(v)
907 parsecontext = lambda v: v == b'full' and -1 or int(v)
908 if b'context' in web.req.qsparams:
908 if b'context' in web.req.qsparams:
909 context = parsecontext(web.req.qsparams[b'context'])
909 context = parsecontext(web.req.qsparams[b'context'])
910 else:
910 else:
911 context = parsecontext(web.config(b'web', b'comparisoncontext'))
911 context = parsecontext(web.config(b'web', b'comparisoncontext'))
912
912
913 def filelines(f):
913 def filelines(f):
914 if f.isbinary():
914 if f.isbinary():
915 mt = pycompat.sysbytes(
915 mt = pycompat.sysbytes(
916 mimetypes.guess_type(pycompat.fsdecode(f.path()))[0]
916 mimetypes.guess_type(pycompat.fsdecode(f.path()))[0]
917 or r'application/octet-stream'
917 or r'application/octet-stream'
918 )
918 )
919 return [_(b'(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
919 return [_(b'(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
920 return f.data().splitlines()
920 return f.data().splitlines()
921
921
922 fctx = None
922 fctx = None
923 parent = ctx.p1()
923 parent = ctx.p1()
924 leftrev = parent.rev()
924 leftrev = parent.rev()
925 leftnode = parent.node()
925 leftnode = parent.node()
926 rightrev = ctx.rev()
926 rightrev = ctx.rev()
927 rightnode = scmutil.binnode(ctx)
927 rightnode = scmutil.binnode(ctx)
928 if path in ctx:
928 if path in ctx:
929 fctx = ctx[path]
929 fctx = ctx[path]
930 rightlines = filelines(fctx)
930 rightlines = filelines(fctx)
931 if path not in parent:
931 if path not in parent:
932 leftlines = ()
932 leftlines = ()
933 else:
933 else:
934 pfctx = parent[path]
934 pfctx = parent[path]
935 leftlines = filelines(pfctx)
935 leftlines = filelines(pfctx)
936 else:
936 else:
937 rightlines = ()
937 rightlines = ()
938 pfctx = ctx.p1()[path]
938 pfctx = ctx.p1()[path]
939 leftlines = filelines(pfctx)
939 leftlines = filelines(pfctx)
940
940
941 comparison = webutil.compare(context, leftlines, rightlines)
941 comparison = webutil.compare(context, leftlines, rightlines)
942 if fctx is not None:
942 if fctx is not None:
943 rename = webutil.renamelink(fctx)
943 rename = webutil.renamelink(fctx)
944 ctx = fctx
944 ctx = fctx
945 else:
945 else:
946 rename = templateutil.mappinglist([])
946 rename = templateutil.mappinglist([])
947 ctx = ctx
947 ctx = ctx
948
948
949 return web.sendtemplate(
949 return web.sendtemplate(
950 b'filecomparison',
950 b'filecomparison',
951 file=path,
951 file=path,
952 symrev=webutil.symrevorshortnode(web.req, ctx),
952 symrev=webutil.symrevorshortnode(web.req, ctx),
953 rename=rename,
953 rename=rename,
954 leftrev=leftrev,
954 leftrev=leftrev,
955 leftnode=hex(leftnode),
955 leftnode=hex(leftnode),
956 rightrev=rightrev,
956 rightrev=rightrev,
957 rightnode=hex(rightnode),
957 rightnode=hex(rightnode),
958 comparison=comparison,
958 comparison=comparison,
959 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx))
959 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx))
960 )
960 )
961
961
962
962
963 @webcommand(b'annotate')
963 @webcommand(b'annotate')
964 def annotate(web):
964 def annotate(web):
965 """
965 """
966 /annotate/{revision}/{path}
966 /annotate/{revision}/{path}
967 ---------------------------
967 ---------------------------
968
968
969 Show changeset information for each line in a file.
969 Show changeset information for each line in a file.
970
970
971 The ``ignorews``, ``ignorewsamount``, ``ignorewseol``, and
971 The ``ignorews``, ``ignorewsamount``, ``ignorewseol``, and
972 ``ignoreblanklines`` query string arguments have the same meaning as
972 ``ignoreblanklines`` query string arguments have the same meaning as
973 their ``[annotate]`` config equivalents. It uses the hgrc boolean
973 their ``[annotate]`` config equivalents. It uses the hgrc boolean
974 parsing logic to interpret the value. e.g. ``0`` and ``false`` are
974 parsing logic to interpret the value. e.g. ``0`` and ``false`` are
975 false and ``1`` and ``true`` are true. If not defined, the server
975 false and ``1`` and ``true`` are true. If not defined, the server
976 default settings are used.
976 default settings are used.
977
977
978 The ``fileannotate`` template is rendered.
978 The ``fileannotate`` template is rendered.
979 """
979 """
980 fctx = webutil.filectx(web.repo, web.req)
980 fctx = webutil.filectx(web.repo, web.req)
981 f = fctx.path()
981 f = fctx.path()
982 parity = paritygen(web.stripecount)
982 parity = paritygen(web.stripecount)
983 ishead = fctx.filenode() in fctx.filelog().heads()
983 ishead = fctx.filenode() in fctx.filelog().heads()
984
984
985 # parents() is called once per line and several lines likely belong to
985 # parents() is called once per line and several lines likely belong to
986 # same revision. So it is worth caching.
986 # same revision. So it is worth caching.
987 # TODO there are still redundant operations within basefilectx.parents()
987 # TODO there are still redundant operations within basefilectx.parents()
988 # and from the fctx.annotate() call itself that could be cached.
988 # and from the fctx.annotate() call itself that could be cached.
989 parentscache = {}
989 parentscache = {}
990
990
991 def parents(context, f):
991 def parents(context, f):
992 rev = f.rev()
992 rev = f.rev()
993 if rev not in parentscache:
993 if rev not in parentscache:
994 parentscache[rev] = []
994 parentscache[rev] = []
995 for p in f.parents():
995 for p in f.parents():
996 entry = {
996 entry = {
997 b'node': p.hex(),
997 b'node': p.hex(),
998 b'rev': p.rev(),
998 b'rev': p.rev(),
999 }
999 }
1000 parentscache[rev].append(entry)
1000 parentscache[rev].append(entry)
1001
1001
1002 for p in parentscache[rev]:
1002 for p in parentscache[rev]:
1003 yield p
1003 yield p
1004
1004
1005 def annotate(context):
1005 def annotate(context):
1006 if fctx.isbinary():
1006 if fctx.isbinary():
1007 mt = pycompat.sysbytes(
1007 mt = pycompat.sysbytes(
1008 mimetypes.guess_type(pycompat.fsdecode(fctx.path()))[0]
1008 mimetypes.guess_type(pycompat.fsdecode(fctx.path()))[0]
1009 or r'application/octet-stream'
1009 or r'application/octet-stream'
1010 )
1010 )
1011 lines = [
1011 lines = [
1012 dagop.annotateline(
1012 dagop.annotateline(
1013 fctx=fctx.filectx(fctx.filerev()),
1013 fctx=fctx.filectx(fctx.filerev()),
1014 lineno=1,
1014 lineno=1,
1015 text=b'(binary:%s)' % mt,
1015 text=b'(binary:%s)' % mt,
1016 )
1016 )
1017 ]
1017 ]
1018 else:
1018 else:
1019 lines = webutil.annotate(web.req, fctx, web.repo.ui)
1019 lines = webutil.annotate(web.req, fctx, web.repo.ui)
1020
1020
1021 previousrev = None
1021 previousrev = None
1022 blockparitygen = paritygen(1)
1022 blockparitygen = paritygen(1)
1023 for lineno, aline in enumerate(lines):
1023 for lineno, aline in enumerate(lines):
1024 f = aline.fctx
1024 f = aline.fctx
1025 rev = f.rev()
1025 rev = f.rev()
1026 if rev != previousrev:
1026 if rev != previousrev:
1027 blockhead = True
1027 blockhead = True
1028 blockparity = next(blockparitygen)
1028 blockparity = next(blockparitygen)
1029 else:
1029 else:
1030 blockhead = None
1030 blockhead = None
1031 previousrev = rev
1031 previousrev = rev
1032 yield {
1032 yield {
1033 b"parity": next(parity),
1033 b"parity": next(parity),
1034 b"node": f.hex(),
1034 b"node": f.hex(),
1035 b"rev": rev,
1035 b"rev": rev,
1036 b"author": f.user(),
1036 b"author": f.user(),
1037 b"parents": templateutil.mappinggenerator(parents, args=(f,)),
1037 b"parents": templateutil.mappinggenerator(parents, args=(f,)),
1038 b"desc": f.description(),
1038 b"desc": f.description(),
1039 b"extra": f.extra(),
1039 b"extra": f.extra(),
1040 b"file": f.path(),
1040 b"file": f.path(),
1041 b"blockhead": blockhead,
1041 b"blockhead": blockhead,
1042 b"blockparity": blockparity,
1042 b"blockparity": blockparity,
1043 b"targetline": aline.lineno,
1043 b"targetline": aline.lineno,
1044 b"line": aline.text,
1044 b"line": aline.text,
1045 b"lineno": lineno + 1,
1045 b"lineno": lineno + 1,
1046 b"lineid": b"l%d" % (lineno + 1),
1046 b"lineid": b"l%d" % (lineno + 1),
1047 b"linenumber": b"% 6d" % (lineno + 1),
1047 b"linenumber": b"% 6d" % (lineno + 1),
1048 b"revdate": f.date(),
1048 b"revdate": f.date(),
1049 }
1049 }
1050
1050
1051 diffopts = webutil.difffeatureopts(web.req, web.repo.ui, b'annotate')
1051 diffopts = webutil.difffeatureopts(web.req, web.repo.ui, b'annotate')
1052 diffopts = {k: getattr(diffopts, k) for k in diffopts.defaults}
1052 diffopts = {k: getattr(diffopts, k) for k in diffopts.defaults}
1053
1053
1054 return web.sendtemplate(
1054 return web.sendtemplate(
1055 b'fileannotate',
1055 b'fileannotate',
1056 file=f,
1056 file=f,
1057 annotate=templateutil.mappinggenerator(annotate),
1057 annotate=templateutil.mappinggenerator(annotate),
1058 path=webutil.up(f),
1058 path=webutil.up(f),
1059 symrev=webutil.symrevorshortnode(web.req, fctx),
1059 symrev=webutil.symrevorshortnode(web.req, fctx),
1060 rename=webutil.renamelink(fctx),
1060 rename=webutil.renamelink(fctx),
1061 permissions=fctx.manifest().flags(f),
1061 permissions=fctx.manifest().flags(f),
1062 ishead=int(ishead),
1062 ishead=int(ishead),
1063 diffopts=templateutil.hybriddict(diffopts),
1063 diffopts=templateutil.hybriddict(diffopts),
1064 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx))
1064 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx))
1065 )
1065 )
1066
1066
1067
1067
1068 @webcommand(b'filelog')
1068 @webcommand(b'filelog')
1069 def filelog(web):
1069 def filelog(web):
1070 """
1070 """
1071 /filelog/{revision}/{path}
1071 /filelog/{revision}/{path}
1072 --------------------------
1072 --------------------------
1073
1073
1074 Show information about the history of a file in the repository.
1074 Show information about the history of a file in the repository.
1075
1075
1076 The ``revcount`` query string argument can be defined to control the
1076 The ``revcount`` query string argument can be defined to control the
1077 maximum number of entries to show.
1077 maximum number of entries to show.
1078
1078
1079 The ``filelog`` template will be rendered.
1079 The ``filelog`` template will be rendered.
1080 """
1080 """
1081
1081
1082 try:
1082 try:
1083 fctx = webutil.filectx(web.repo, web.req)
1083 fctx = webutil.filectx(web.repo, web.req)
1084 f = fctx.path()
1084 f = fctx.path()
1085 fl = fctx.filelog()
1085 fl = fctx.filelog()
1086 except error.LookupError:
1086 except error.LookupError:
1087 f = webutil.cleanpath(web.repo, web.req.qsparams[b'file'])
1087 f = webutil.cleanpath(web.repo, web.req.qsparams[b'file'])
1088 fl = web.repo.file(f)
1088 fl = web.repo.file(f)
1089 numrevs = len(fl)
1089 numrevs = len(fl)
1090 if not numrevs: # file doesn't exist at all
1090 if not numrevs: # file doesn't exist at all
1091 raise
1091 raise
1092 rev = webutil.changectx(web.repo, web.req).rev()
1092 rev = webutil.changectx(web.repo, web.req).rev()
1093 first = fl.linkrev(0)
1093 first = fl.linkrev(0)
1094 if rev < first: # current rev is from before file existed
1094 if rev < first: # current rev is from before file existed
1095 raise
1095 raise
1096 frev = numrevs - 1
1096 frev = numrevs - 1
1097 while fl.linkrev(frev) > rev:
1097 while fl.linkrev(frev) > rev:
1098 frev -= 1
1098 frev -= 1
1099 fctx = web.repo.filectx(f, fl.linkrev(frev))
1099 fctx = web.repo.filectx(f, fl.linkrev(frev))
1100
1100
1101 revcount = web.maxshortchanges
1101 revcount = web.maxshortchanges
1102 if b'revcount' in web.req.qsparams:
1102 if b'revcount' in web.req.qsparams:
1103 try:
1103 try:
1104 revcount = int(web.req.qsparams.get(b'revcount', revcount))
1104 revcount = int(web.req.qsparams.get(b'revcount', revcount))
1105 revcount = max(revcount, 1)
1105 revcount = max(revcount, 1)
1106 web.tmpl.defaults[b'sessionvars'][b'revcount'] = revcount
1106 web.tmpl.defaults[b'sessionvars'][b'revcount'] = revcount
1107 except ValueError:
1107 except ValueError:
1108 pass
1108 pass
1109
1109
1110 lrange = webutil.linerange(web.req)
1110 lrange = webutil.linerange(web.req)
1111
1111
1112 lessvars = copy.copy(web.tmpl.defaults[b'sessionvars'])
1112 lessvars = copy.copy(web.tmpl.defaults[b'sessionvars'])
1113 lessvars[b'revcount'] = max(revcount // 2, 1)
1113 lessvars[b'revcount'] = max(revcount // 2, 1)
1114 morevars = copy.copy(web.tmpl.defaults[b'sessionvars'])
1114 morevars = copy.copy(web.tmpl.defaults[b'sessionvars'])
1115 morevars[b'revcount'] = revcount * 2
1115 morevars[b'revcount'] = revcount * 2
1116
1116
1117 patch = b'patch' in web.req.qsparams
1117 patch = b'patch' in web.req.qsparams
1118 if patch:
1118 if patch:
1119 lessvars[b'patch'] = morevars[b'patch'] = web.req.qsparams[b'patch']
1119 lessvars[b'patch'] = morevars[b'patch'] = web.req.qsparams[b'patch']
1120 descend = b'descend' in web.req.qsparams
1120 descend = b'descend' in web.req.qsparams
1121 if descend:
1121 if descend:
1122 lessvars[b'descend'] = morevars[b'descend'] = web.req.qsparams[
1122 lessvars[b'descend'] = morevars[b'descend'] = web.req.qsparams[
1123 b'descend'
1123 b'descend'
1124 ]
1124 ]
1125
1125
1126 count = fctx.filerev() + 1
1126 count = fctx.filerev() + 1
1127 start = max(0, count - revcount) # first rev on this page
1127 start = max(0, count - revcount) # first rev on this page
1128 end = min(count, start + revcount) # last rev on this page
1128 end = min(count, start + revcount) # last rev on this page
1129 parity = paritygen(web.stripecount, offset=start - end)
1129 parity = paritygen(web.stripecount, offset=start - end)
1130
1130
1131 repo = web.repo
1131 repo = web.repo
1132 filelog = fctx.filelog()
1132 filelog = fctx.filelog()
1133 revs = [
1133 revs = [
1134 filerev
1134 filerev
1135 for filerev in filelog.revs(start, end - 1)
1135 for filerev in filelog.revs(start, end - 1)
1136 if filelog.linkrev(filerev) in repo
1136 if filelog.linkrev(filerev) in repo
1137 ]
1137 ]
1138 entries = []
1138 entries = []
1139
1139
1140 diffstyle = web.config(b'web', b'style')
1140 diffstyle = web.config(b'web', b'style')
1141 if b'style' in web.req.qsparams:
1141 if b'style' in web.req.qsparams:
1142 diffstyle = web.req.qsparams[b'style']
1142 diffstyle = web.req.qsparams[b'style']
1143
1143
1144 def diff(fctx, linerange=None):
1144 def diff(fctx, linerange=None):
1145 ctx = fctx.changectx()
1145 ctx = fctx.changectx()
1146 basectx = ctx.p1()
1146 basectx = ctx.p1()
1147 path = fctx.path()
1147 path = fctx.path()
1148 return webutil.diffs(
1148 return webutil.diffs(
1149 web,
1149 web,
1150 ctx,
1150 ctx,
1151 basectx,
1151 basectx,
1152 [path],
1152 [path],
1153 diffstyle,
1153 diffstyle,
1154 linerange=linerange,
1154 linerange=linerange,
1155 lineidprefix=b'%s-' % ctx.hex()[:12],
1155 lineidprefix=b'%s-' % ctx.hex()[:12],
1156 )
1156 )
1157
1157
1158 linerange = None
1158 linerange = None
1159 if lrange is not None:
1159 if lrange is not None:
1160 linerange = webutil.formatlinerange(*lrange)
1160 linerange = webutil.formatlinerange(*lrange)
1161 # deactivate numeric nav links when linerange is specified as this
1161 # deactivate numeric nav links when linerange is specified as this
1162 # would required a dedicated "revnav" class
1162 # would required a dedicated "revnav" class
1163 nav = templateutil.mappinglist([])
1163 nav = templateutil.mappinglist([])
1164 if descend:
1164 if descend:
1165 it = dagop.blockdescendants(fctx, *lrange)
1165 it = dagop.blockdescendants(fctx, *lrange)
1166 else:
1166 else:
1167 it = dagop.blockancestors(fctx, *lrange)
1167 it = dagop.blockancestors(fctx, *lrange)
1168 for i, (c, lr) in enumerate(it, 1):
1168 for i, (c, lr) in enumerate(it, 1):
1169 diffs = None
1169 diffs = None
1170 if patch:
1170 if patch:
1171 diffs = diff(c, linerange=lr)
1171 diffs = diff(c, linerange=lr)
1172 # follow renames accross filtered (not in range) revisions
1172 # follow renames accross filtered (not in range) revisions
1173 path = c.path()
1173 path = c.path()
1174 lm = webutil.commonentry(repo, c)
1174 lm = webutil.commonentry(repo, c)
1175 lm.update(
1175 lm.update(
1176 {
1176 {
1177 b'parity': next(parity),
1177 b'parity': next(parity),
1178 b'filerev': c.rev(),
1178 b'filerev': c.rev(),
1179 b'file': path,
1179 b'file': path,
1180 b'diff': diffs,
1180 b'diff': diffs,
1181 b'linerange': webutil.formatlinerange(*lr),
1181 b'linerange': webutil.formatlinerange(*lr),
1182 b'rename': templateutil.mappinglist([]),
1182 b'rename': templateutil.mappinglist([]),
1183 }
1183 }
1184 )
1184 )
1185 entries.append(lm)
1185 entries.append(lm)
1186 if i == revcount:
1186 if i == revcount:
1187 break
1187 break
1188 lessvars[b'linerange'] = webutil.formatlinerange(*lrange)
1188 lessvars[b'linerange'] = webutil.formatlinerange(*lrange)
1189 morevars[b'linerange'] = lessvars[b'linerange']
1189 morevars[b'linerange'] = lessvars[b'linerange']
1190 else:
1190 else:
1191 for i in revs:
1191 for i in revs:
1192 iterfctx = fctx.filectx(i)
1192 iterfctx = fctx.filectx(i)
1193 diffs = None
1193 diffs = None
1194 if patch:
1194 if patch:
1195 diffs = diff(iterfctx)
1195 diffs = diff(iterfctx)
1196 lm = webutil.commonentry(repo, iterfctx)
1196 lm = webutil.commonentry(repo, iterfctx)
1197 lm.update(
1197 lm.update(
1198 {
1198 {
1199 b'parity': next(parity),
1199 b'parity': next(parity),
1200 b'filerev': i,
1200 b'filerev': i,
1201 b'file': f,
1201 b'file': f,
1202 b'diff': diffs,
1202 b'diff': diffs,
1203 b'rename': webutil.renamelink(iterfctx),
1203 b'rename': webutil.renamelink(iterfctx),
1204 }
1204 }
1205 )
1205 )
1206 entries.append(lm)
1206 entries.append(lm)
1207 entries.reverse()
1207 entries.reverse()
1208 revnav = webutil.filerevnav(web.repo, fctx.path())
1208 revnav = webutil.filerevnav(web.repo, fctx.path())
1209 nav = revnav.gen(end - 1, revcount, count)
1209 nav = revnav.gen(end - 1, revcount, count)
1210
1210
1211 latestentry = entries[:1]
1211 latestentry = entries[:1]
1212
1212
1213 return web.sendtemplate(
1213 return web.sendtemplate(
1214 b'filelog',
1214 b'filelog',
1215 file=f,
1215 file=f,
1216 nav=nav,
1216 nav=nav,
1217 symrev=webutil.symrevorshortnode(web.req, fctx),
1217 symrev=webutil.symrevorshortnode(web.req, fctx),
1218 entries=templateutil.mappinglist(entries),
1218 entries=templateutil.mappinglist(entries),
1219 descend=descend,
1219 descend=descend,
1220 patch=patch,
1220 patch=patch,
1221 latestentry=templateutil.mappinglist(latestentry),
1221 latestentry=templateutil.mappinglist(latestentry),
1222 linerange=linerange,
1222 linerange=linerange,
1223 revcount=revcount,
1223 revcount=revcount,
1224 morevars=morevars,
1224 morevars=morevars,
1225 lessvars=lessvars,
1225 lessvars=lessvars,
1226 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx))
1226 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx))
1227 )
1227 )
1228
1228
1229
1229
1230 @webcommand(b'archive')
1230 @webcommand(b'archive')
1231 def archive(web):
1231 def archive(web):
1232 """
1232 """
1233 /archive/{revision}.{format}[/{path}]
1233 /archive/{revision}.{format}[/{path}]
1234 -------------------------------------
1234 -------------------------------------
1235
1235
1236 Obtain an archive of repository content.
1236 Obtain an archive of repository content.
1237
1237
1238 The content and type of the archive is defined by a URL path parameter.
1238 The content and type of the archive is defined by a URL path parameter.
1239 ``format`` is the file extension of the archive type to be generated. e.g.
1239 ``format`` is the file extension of the archive type to be generated. e.g.
1240 ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
1240 ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
1241 server configuration.
1241 server configuration.
1242
1242
1243 The optional ``path`` URL parameter controls content to include in the
1243 The optional ``path`` URL parameter controls content to include in the
1244 archive. If omitted, every file in the specified revision is present in the
1244 archive. If omitted, every file in the specified revision is present in the
1245 archive. If included, only the specified file or contents of the specified
1245 archive. If included, only the specified file or contents of the specified
1246 directory will be included in the archive.
1246 directory will be included in the archive.
1247
1247
1248 No template is used for this handler. Raw, binary content is generated.
1248 No template is used for this handler. Raw, binary content is generated.
1249 """
1249 """
1250
1250
1251 type_ = web.req.qsparams.get(b'type')
1251 type_ = web.req.qsparams.get(b'type')
1252 allowed = web.configlist(b"web", b"allow-archive")
1252 allowed = web.configlist(b"web", b"allow-archive")
1253 key = web.req.qsparams[b'node']
1253 key = web.req.qsparams[b'node']
1254
1254
1255 if type_ not in webutil.archivespecs:
1255 if type_ not in webutil.archivespecs:
1256 msg = b'Unsupported archive type: %s' % stringutil.pprint(type_)
1256 msg = b'Unsupported archive type: %s' % stringutil.pprint(type_)
1257 raise ErrorResponse(HTTP_NOT_FOUND, msg)
1257 raise ErrorResponse(HTTP_NOT_FOUND, msg)
1258
1258
1259 if not ((type_ in allowed or web.configbool(b"web", b"allow" + type_))):
1259 if not ((type_ in allowed or web.configbool(b"web", b"allow" + type_))):
1260 msg = b'Archive type not allowed: %s' % type_
1260 msg = b'Archive type not allowed: %s' % type_
1261 raise ErrorResponse(HTTP_FORBIDDEN, msg)
1261 raise ErrorResponse(HTTP_FORBIDDEN, msg)
1262
1262
1263 reponame = re.sub(br"\W+", b"-", os.path.basename(web.reponame))
1263 reponame = re.sub(br"\W+", b"-", os.path.basename(web.reponame))
1264 cnode = web.repo.lookup(key)
1264 cnode = web.repo.lookup(key)
1265 arch_version = key
1265 arch_version = key
1266 if cnode == key or key == b'tip':
1266 if cnode == key or key == b'tip':
1267 arch_version = short(cnode)
1267 arch_version = short(cnode)
1268 name = b"%s-%s" % (reponame, arch_version)
1268 name = b"%s-%s" % (reponame, arch_version)
1269
1269
1270 ctx = webutil.changectx(web.repo, web.req)
1270 ctx = webutil.changectx(web.repo, web.req)
1271 pats = []
1271 pats = []
1272 match = scmutil.match(ctx, [])
1272 match = scmutil.match(ctx, [])
1273 file = web.req.qsparams.get(b'file')
1273 file = web.req.qsparams.get(b'file')
1274 if file:
1274 if file:
1275 pats = [b'path:' + file]
1275 pats = [b'path:' + file]
1276 match = scmutil.match(ctx, pats, default=b'path')
1276 match = scmutil.match(ctx, pats, default=b'path')
1277 if pats:
1277 if pats:
1278 files = [f for f in ctx.manifest().keys() if match(f)]
1278 files = [f for f in ctx.manifest().keys() if match(f)]
1279 if not files:
1279 if not files:
1280 raise ErrorResponse(
1280 raise ErrorResponse(
1281 HTTP_NOT_FOUND, b'file(s) not found: %s' % file
1281 HTTP_NOT_FOUND, b'file(s) not found: %s' % file
1282 )
1282 )
1283
1283
1284 mimetype, artype, extension, encoding = webutil.archivespecs[type_]
1284 mimetype, artype, extension, encoding = webutil.archivespecs[type_]
1285
1285
1286 web.res.headers[b'Content-Type'] = mimetype
1286 web.res.headers[b'Content-Type'] = mimetype
1287 web.res.headers[b'Content-Disposition'] = b'attachment; filename=%s%s' % (
1287 web.res.headers[b'Content-Disposition'] = b'attachment; filename=%s%s' % (
1288 name,
1288 name,
1289 extension,
1289 extension,
1290 )
1290 )
1291
1291
1292 if encoding:
1292 if encoding:
1293 web.res.headers[b'Content-Encoding'] = encoding
1293 web.res.headers[b'Content-Encoding'] = encoding
1294
1294
1295 web.res.setbodywillwrite()
1295 web.res.setbodywillwrite()
1296 if list(web.res.sendresponse()):
1296 if list(web.res.sendresponse()):
1297 raise error.ProgrammingError(
1297 raise error.ProgrammingError(
1298 b'sendresponse() should not emit data if writing later'
1298 b'sendresponse() should not emit data if writing later'
1299 )
1299 )
1300
1300
1301 bodyfh = web.res.getbodyfile()
1301 bodyfh = web.res.getbodyfile()
1302
1302
1303 archival.archive(
1303 archival.archive(
1304 web.repo,
1304 web.repo,
1305 bodyfh,
1305 bodyfh,
1306 cnode,
1306 cnode,
1307 artype,
1307 artype,
1308 prefix=name,
1308 prefix=name,
1309 match=match,
1309 match=match,
1310 subrepos=web.configbool(b"web", b"archivesubrepos"),
1310 subrepos=web.configbool(b"web", b"archivesubrepos"),
1311 )
1311 )
1312
1312
1313 return []
1313 return []
1314
1314
1315
1315
1316 @webcommand(b'static')
1316 @webcommand(b'static')
1317 def static(web):
1317 def static(web):
1318 fname = web.req.qsparams[b'file']
1318 fname = web.req.qsparams[b'file']
1319 # a repo owner may set web.static in .hg/hgrc to get any file
1319 # a repo owner may set web.static in .hg/hgrc to get any file
1320 # readable by the user running the CGI script
1320 # readable by the user running the CGI script
1321 static = web.config(b"web", b"static", untrusted=False)
1321 static = web.config(b"web", b"static", untrusted=False)
1322 if not static:
1322 if not static:
1323 tp = web.templatepath or templater.templatepaths()
1323 tp = web.templatepath or templater.templatepaths()
1324 if isinstance(tp, str):
1324 if isinstance(tp, bytes):
1325 tp = [tp]
1325 tp = [tp]
1326 static = [os.path.join(p, b'static') for p in tp]
1326 static = [os.path.join(p, b'static') for p in tp]
1327
1327
1328 staticfile(static, fname, web.res)
1328 staticfile(static, fname, web.res)
1329 return web.res.sendresponse()
1329 return web.res.sendresponse()
1330
1330
1331
1331
1332 @webcommand(b'graph')
1332 @webcommand(b'graph')
1333 def graph(web):
1333 def graph(web):
1334 """
1334 """
1335 /graph[/{revision}]
1335 /graph[/{revision}]
1336 -------------------
1336 -------------------
1337
1337
1338 Show information about the graphical topology of the repository.
1338 Show information about the graphical topology of the repository.
1339
1339
1340 Information rendered by this handler can be used to create visual
1340 Information rendered by this handler can be used to create visual
1341 representations of repository topology.
1341 representations of repository topology.
1342
1342
1343 The ``revision`` URL parameter controls the starting changeset. If it's
1343 The ``revision`` URL parameter controls the starting changeset. If it's
1344 absent, the default is ``tip``.
1344 absent, the default is ``tip``.
1345
1345
1346 The ``revcount`` query string argument can define the number of changesets
1346 The ``revcount`` query string argument can define the number of changesets
1347 to show information for.
1347 to show information for.
1348
1348
1349 The ``graphtop`` query string argument can specify the starting changeset
1349 The ``graphtop`` query string argument can specify the starting changeset
1350 for producing ``jsdata`` variable that is used for rendering graph in
1350 for producing ``jsdata`` variable that is used for rendering graph in
1351 JavaScript. By default it has the same value as ``revision``.
1351 JavaScript. By default it has the same value as ``revision``.
1352
1352
1353 This handler will render the ``graph`` template.
1353 This handler will render the ``graph`` template.
1354 """
1354 """
1355
1355
1356 if b'node' in web.req.qsparams:
1356 if b'node' in web.req.qsparams:
1357 ctx = webutil.changectx(web.repo, web.req)
1357 ctx = webutil.changectx(web.repo, web.req)
1358 symrev = webutil.symrevorshortnode(web.req, ctx)
1358 symrev = webutil.symrevorshortnode(web.req, ctx)
1359 else:
1359 else:
1360 ctx = web.repo[b'tip']
1360 ctx = web.repo[b'tip']
1361 symrev = b'tip'
1361 symrev = b'tip'
1362 rev = ctx.rev()
1362 rev = ctx.rev()
1363
1363
1364 bg_height = 39
1364 bg_height = 39
1365 revcount = web.maxshortchanges
1365 revcount = web.maxshortchanges
1366 if b'revcount' in web.req.qsparams:
1366 if b'revcount' in web.req.qsparams:
1367 try:
1367 try:
1368 revcount = int(web.req.qsparams.get(b'revcount', revcount))
1368 revcount = int(web.req.qsparams.get(b'revcount', revcount))
1369 revcount = max(revcount, 1)
1369 revcount = max(revcount, 1)
1370 web.tmpl.defaults[b'sessionvars'][b'revcount'] = revcount
1370 web.tmpl.defaults[b'sessionvars'][b'revcount'] = revcount
1371 except ValueError:
1371 except ValueError:
1372 pass
1372 pass
1373
1373
1374 lessvars = copy.copy(web.tmpl.defaults[b'sessionvars'])
1374 lessvars = copy.copy(web.tmpl.defaults[b'sessionvars'])
1375 lessvars[b'revcount'] = max(revcount // 2, 1)
1375 lessvars[b'revcount'] = max(revcount // 2, 1)
1376 morevars = copy.copy(web.tmpl.defaults[b'sessionvars'])
1376 morevars = copy.copy(web.tmpl.defaults[b'sessionvars'])
1377 morevars[b'revcount'] = revcount * 2
1377 morevars[b'revcount'] = revcount * 2
1378
1378
1379 graphtop = web.req.qsparams.get(b'graphtop', ctx.hex())
1379 graphtop = web.req.qsparams.get(b'graphtop', ctx.hex())
1380 graphvars = copy.copy(web.tmpl.defaults[b'sessionvars'])
1380 graphvars = copy.copy(web.tmpl.defaults[b'sessionvars'])
1381 graphvars[b'graphtop'] = graphtop
1381 graphvars[b'graphtop'] = graphtop
1382
1382
1383 count = len(web.repo)
1383 count = len(web.repo)
1384 pos = rev
1384 pos = rev
1385
1385
1386 uprev = min(max(0, count - 1), rev + revcount)
1386 uprev = min(max(0, count - 1), rev + revcount)
1387 downrev = max(0, rev - revcount)
1387 downrev = max(0, rev - revcount)
1388 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
1388 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
1389
1389
1390 tree = []
1390 tree = []
1391 nextentry = []
1391 nextentry = []
1392 lastrev = 0
1392 lastrev = 0
1393 if pos != -1:
1393 if pos != -1:
1394 allrevs = web.repo.changelog.revs(pos, 0)
1394 allrevs = web.repo.changelog.revs(pos, 0)
1395 revs = []
1395 revs = []
1396 for i in allrevs:
1396 for i in allrevs:
1397 revs.append(i)
1397 revs.append(i)
1398 if len(revs) >= revcount + 1:
1398 if len(revs) >= revcount + 1:
1399 break
1399 break
1400
1400
1401 if len(revs) > revcount:
1401 if len(revs) > revcount:
1402 nextentry = [webutil.commonentry(web.repo, web.repo[revs[-1]])]
1402 nextentry = [webutil.commonentry(web.repo, web.repo[revs[-1]])]
1403 revs = revs[:-1]
1403 revs = revs[:-1]
1404
1404
1405 lastrev = revs[-1]
1405 lastrev = revs[-1]
1406
1406
1407 # We have to feed a baseset to dagwalker as it is expecting smartset
1407 # We have to feed a baseset to dagwalker as it is expecting smartset
1408 # object. This does not have a big impact on hgweb performance itself
1408 # object. This does not have a big impact on hgweb performance itself
1409 # since hgweb graphing code is not itself lazy yet.
1409 # since hgweb graphing code is not itself lazy yet.
1410 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs))
1410 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs))
1411 # As we said one line above... not lazy.
1411 # As we said one line above... not lazy.
1412 tree = list(
1412 tree = list(
1413 item
1413 item
1414 for item in graphmod.colored(dag, web.repo)
1414 for item in graphmod.colored(dag, web.repo)
1415 if item[1] == graphmod.CHANGESET
1415 if item[1] == graphmod.CHANGESET
1416 )
1416 )
1417
1417
1418 def fulltree():
1418 def fulltree():
1419 pos = web.repo[graphtop].rev()
1419 pos = web.repo[graphtop].rev()
1420 tree = []
1420 tree = []
1421 if pos != -1:
1421 if pos != -1:
1422 revs = web.repo.changelog.revs(pos, lastrev)
1422 revs = web.repo.changelog.revs(pos, lastrev)
1423 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs))
1423 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs))
1424 tree = list(
1424 tree = list(
1425 item
1425 item
1426 for item in graphmod.colored(dag, web.repo)
1426 for item in graphmod.colored(dag, web.repo)
1427 if item[1] == graphmod.CHANGESET
1427 if item[1] == graphmod.CHANGESET
1428 )
1428 )
1429 return tree
1429 return tree
1430
1430
1431 def jsdata(context):
1431 def jsdata(context):
1432 for (id, type, ctx, vtx, edges) in fulltree():
1432 for (id, type, ctx, vtx, edges) in fulltree():
1433 yield {
1433 yield {
1434 b'node': pycompat.bytestr(ctx),
1434 b'node': pycompat.bytestr(ctx),
1435 b'graphnode': webutil.getgraphnode(web.repo, ctx),
1435 b'graphnode': webutil.getgraphnode(web.repo, ctx),
1436 b'vertex': vtx,
1436 b'vertex': vtx,
1437 b'edges': edges,
1437 b'edges': edges,
1438 }
1438 }
1439
1439
1440 def nodes(context):
1440 def nodes(context):
1441 parity = paritygen(web.stripecount)
1441 parity = paritygen(web.stripecount)
1442 for row, (id, type, ctx, vtx, edges) in enumerate(tree):
1442 for row, (id, type, ctx, vtx, edges) in enumerate(tree):
1443 entry = webutil.commonentry(web.repo, ctx)
1443 entry = webutil.commonentry(web.repo, ctx)
1444 edgedata = [
1444 edgedata = [
1445 {
1445 {
1446 b'col': edge[0],
1446 b'col': edge[0],
1447 b'nextcol': edge[1],
1447 b'nextcol': edge[1],
1448 b'color': (edge[2] - 1) % 6 + 1,
1448 b'color': (edge[2] - 1) % 6 + 1,
1449 b'width': edge[3],
1449 b'width': edge[3],
1450 b'bcolor': edge[4],
1450 b'bcolor': edge[4],
1451 }
1451 }
1452 for edge in edges
1452 for edge in edges
1453 ]
1453 ]
1454
1454
1455 entry.update(
1455 entry.update(
1456 {
1456 {
1457 b'col': vtx[0],
1457 b'col': vtx[0],
1458 b'color': (vtx[1] - 1) % 6 + 1,
1458 b'color': (vtx[1] - 1) % 6 + 1,
1459 b'parity': next(parity),
1459 b'parity': next(parity),
1460 b'edges': templateutil.mappinglist(edgedata),
1460 b'edges': templateutil.mappinglist(edgedata),
1461 b'row': row,
1461 b'row': row,
1462 b'nextrow': row + 1,
1462 b'nextrow': row + 1,
1463 }
1463 }
1464 )
1464 )
1465
1465
1466 yield entry
1466 yield entry
1467
1467
1468 rows = len(tree)
1468 rows = len(tree)
1469
1469
1470 return web.sendtemplate(
1470 return web.sendtemplate(
1471 b'graph',
1471 b'graph',
1472 rev=rev,
1472 rev=rev,
1473 symrev=symrev,
1473 symrev=symrev,
1474 revcount=revcount,
1474 revcount=revcount,
1475 uprev=uprev,
1475 uprev=uprev,
1476 lessvars=lessvars,
1476 lessvars=lessvars,
1477 morevars=morevars,
1477 morevars=morevars,
1478 downrev=downrev,
1478 downrev=downrev,
1479 graphvars=graphvars,
1479 graphvars=graphvars,
1480 rows=rows,
1480 rows=rows,
1481 bg_height=bg_height,
1481 bg_height=bg_height,
1482 changesets=count,
1482 changesets=count,
1483 nextentry=templateutil.mappinglist(nextentry),
1483 nextentry=templateutil.mappinglist(nextentry),
1484 jsdata=templateutil.mappinggenerator(jsdata),
1484 jsdata=templateutil.mappinggenerator(jsdata),
1485 nodes=templateutil.mappinggenerator(nodes),
1485 nodes=templateutil.mappinggenerator(nodes),
1486 node=ctx.hex(),
1486 node=ctx.hex(),
1487 archives=web.archivelist(b'tip'),
1487 archives=web.archivelist(b'tip'),
1488 changenav=changenav,
1488 changenav=changenav,
1489 )
1489 )
1490
1490
1491
1491
1492 def _getdoc(e):
1492 def _getdoc(e):
1493 doc = e[0].__doc__
1493 doc = e[0].__doc__
1494 if doc:
1494 if doc:
1495 doc = _(doc).partition(b'\n')[0]
1495 doc = _(doc).partition(b'\n')[0]
1496 else:
1496 else:
1497 doc = _(b'(no help text available)')
1497 doc = _(b'(no help text available)')
1498 return doc
1498 return doc
1499
1499
1500
1500
1501 @webcommand(b'help')
1501 @webcommand(b'help')
1502 def help(web):
1502 def help(web):
1503 """
1503 """
1504 /help[/{topic}]
1504 /help[/{topic}]
1505 ---------------
1505 ---------------
1506
1506
1507 Render help documentation.
1507 Render help documentation.
1508
1508
1509 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1509 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1510 is defined, that help topic will be rendered. If not, an index of
1510 is defined, that help topic will be rendered. If not, an index of
1511 available help topics will be rendered.
1511 available help topics will be rendered.
1512
1512
1513 The ``help`` template will be rendered when requesting help for a topic.
1513 The ``help`` template will be rendered when requesting help for a topic.
1514 ``helptopics`` will be rendered for the index of help topics.
1514 ``helptopics`` will be rendered for the index of help topics.
1515 """
1515 """
1516 from .. import commands, help as helpmod # avoid cycle
1516 from .. import commands, help as helpmod # avoid cycle
1517
1517
1518 topicname = web.req.qsparams.get(b'node')
1518 topicname = web.req.qsparams.get(b'node')
1519 if not topicname:
1519 if not topicname:
1520
1520
1521 def topics(context):
1521 def topics(context):
1522 for h in helpmod.helptable:
1522 for h in helpmod.helptable:
1523 entries, summary, _doc = h[0:3]
1523 entries, summary, _doc = h[0:3]
1524 yield {b'topic': entries[0], b'summary': summary}
1524 yield {b'topic': entries[0], b'summary': summary}
1525
1525
1526 early, other = [], []
1526 early, other = [], []
1527 primary = lambda s: s.partition(b'|')[0]
1527 primary = lambda s: s.partition(b'|')[0]
1528 for c, e in pycompat.iteritems(commands.table):
1528 for c, e in pycompat.iteritems(commands.table):
1529 doc = _getdoc(e)
1529 doc = _getdoc(e)
1530 if b'DEPRECATED' in doc or c.startswith(b'debug'):
1530 if b'DEPRECATED' in doc or c.startswith(b'debug'):
1531 continue
1531 continue
1532 cmd = primary(c)
1532 cmd = primary(c)
1533 if getattr(e[0], 'helpbasic', False):
1533 if getattr(e[0], 'helpbasic', False):
1534 early.append((cmd, doc))
1534 early.append((cmd, doc))
1535 else:
1535 else:
1536 other.append((cmd, doc))
1536 other.append((cmd, doc))
1537
1537
1538 early.sort()
1538 early.sort()
1539 other.sort()
1539 other.sort()
1540
1540
1541 def earlycommands(context):
1541 def earlycommands(context):
1542 for c, doc in early:
1542 for c, doc in early:
1543 yield {b'topic': c, b'summary': doc}
1543 yield {b'topic': c, b'summary': doc}
1544
1544
1545 def othercommands(context):
1545 def othercommands(context):
1546 for c, doc in other:
1546 for c, doc in other:
1547 yield {b'topic': c, b'summary': doc}
1547 yield {b'topic': c, b'summary': doc}
1548
1548
1549 return web.sendtemplate(
1549 return web.sendtemplate(
1550 b'helptopics',
1550 b'helptopics',
1551 topics=templateutil.mappinggenerator(topics),
1551 topics=templateutil.mappinggenerator(topics),
1552 earlycommands=templateutil.mappinggenerator(earlycommands),
1552 earlycommands=templateutil.mappinggenerator(earlycommands),
1553 othercommands=templateutil.mappinggenerator(othercommands),
1553 othercommands=templateutil.mappinggenerator(othercommands),
1554 title=b'Index',
1554 title=b'Index',
1555 )
1555 )
1556
1556
1557 # Render an index of sub-topics.
1557 # Render an index of sub-topics.
1558 if topicname in helpmod.subtopics:
1558 if topicname in helpmod.subtopics:
1559 topics = []
1559 topics = []
1560 for entries, summary, _doc in helpmod.subtopics[topicname]:
1560 for entries, summary, _doc in helpmod.subtopics[topicname]:
1561 topics.append(
1561 topics.append(
1562 {
1562 {
1563 b'topic': b'%s.%s' % (topicname, entries[0]),
1563 b'topic': b'%s.%s' % (topicname, entries[0]),
1564 b'basename': entries[0],
1564 b'basename': entries[0],
1565 b'summary': summary,
1565 b'summary': summary,
1566 }
1566 }
1567 )
1567 )
1568
1568
1569 return web.sendtemplate(
1569 return web.sendtemplate(
1570 b'helptopics',
1570 b'helptopics',
1571 topics=templateutil.mappinglist(topics),
1571 topics=templateutil.mappinglist(topics),
1572 title=topicname,
1572 title=topicname,
1573 subindex=True,
1573 subindex=True,
1574 )
1574 )
1575
1575
1576 u = webutil.wsgiui.load()
1576 u = webutil.wsgiui.load()
1577 u.verbose = True
1577 u.verbose = True
1578
1578
1579 # Render a page from a sub-topic.
1579 # Render a page from a sub-topic.
1580 if b'.' in topicname:
1580 if b'.' in topicname:
1581 # TODO implement support for rendering sections, like
1581 # TODO implement support for rendering sections, like
1582 # `hg help` works.
1582 # `hg help` works.
1583 topic, subtopic = topicname.split(b'.', 1)
1583 topic, subtopic = topicname.split(b'.', 1)
1584 if topic not in helpmod.subtopics:
1584 if topic not in helpmod.subtopics:
1585 raise ErrorResponse(HTTP_NOT_FOUND)
1585 raise ErrorResponse(HTTP_NOT_FOUND)
1586 else:
1586 else:
1587 topic = topicname
1587 topic = topicname
1588 subtopic = None
1588 subtopic = None
1589
1589
1590 try:
1590 try:
1591 doc = helpmod.help_(u, commands, topic, subtopic=subtopic)
1591 doc = helpmod.help_(u, commands, topic, subtopic=subtopic)
1592 except error.Abort:
1592 except error.Abort:
1593 raise ErrorResponse(HTTP_NOT_FOUND)
1593 raise ErrorResponse(HTTP_NOT_FOUND)
1594
1594
1595 return web.sendtemplate(b'help', topic=topicname, doc=doc)
1595 return web.sendtemplate(b'help', topic=topicname, doc=doc)
1596
1596
1597
1597
1598 # tell hggettext to extract docstrings from these functions:
1598 # tell hggettext to extract docstrings from these functions:
1599 i18nfunctions = commands.values()
1599 i18nfunctions = commands.values()
@@ -1,940 +1,940 b''
1 # hgweb/webutil.py - utility library for the web interface.
1 # hgweb/webutil.py - utility library for the web interface.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import copy
11 import copy
12 import difflib
12 import difflib
13 import os
13 import os
14 import re
14 import re
15
15
16 from ..i18n import _
16 from ..i18n import _
17 from ..node import hex, nullid, short
17 from ..node import hex, nullid, short
18 from ..pycompat import setattr
18 from ..pycompat import setattr
19
19
20 from .common import (
20 from .common import (
21 ErrorResponse,
21 ErrorResponse,
22 HTTP_BAD_REQUEST,
22 HTTP_BAD_REQUEST,
23 HTTP_NOT_FOUND,
23 HTTP_NOT_FOUND,
24 paritygen,
24 paritygen,
25 )
25 )
26
26
27 from .. import (
27 from .. import (
28 context,
28 context,
29 diffutil,
29 diffutil,
30 error,
30 error,
31 match,
31 match,
32 mdiff,
32 mdiff,
33 obsutil,
33 obsutil,
34 patch,
34 patch,
35 pathutil,
35 pathutil,
36 pycompat,
36 pycompat,
37 scmutil,
37 scmutil,
38 templatefilters,
38 templatefilters,
39 templatekw,
39 templatekw,
40 templateutil,
40 templateutil,
41 ui as uimod,
41 ui as uimod,
42 util,
42 util,
43 )
43 )
44
44
45 from ..utils import stringutil
45 from ..utils import stringutil
46
46
47 archivespecs = util.sortdict(
47 archivespecs = util.sortdict(
48 (
48 (
49 (b'zip', (b'application/zip', b'zip', b'.zip', None)),
49 (b'zip', (b'application/zip', b'zip', b'.zip', None)),
50 (b'gz', (b'application/x-gzip', b'tgz', b'.tar.gz', None)),
50 (b'gz', (b'application/x-gzip', b'tgz', b'.tar.gz', None)),
51 (b'bz2', (b'application/x-bzip2', b'tbz2', b'.tar.bz2', None)),
51 (b'bz2', (b'application/x-bzip2', b'tbz2', b'.tar.bz2', None)),
52 )
52 )
53 )
53 )
54
54
55
55
56 def archivelist(ui, nodeid, url=None):
56 def archivelist(ui, nodeid, url=None):
57 allowed = ui.configlist(b'web', b'allow-archive', untrusted=True)
57 allowed = ui.configlist(b'web', b'allow-archive', untrusted=True)
58 archives = []
58 archives = []
59
59
60 for typ, spec in pycompat.iteritems(archivespecs):
60 for typ, spec in pycompat.iteritems(archivespecs):
61 if typ in allowed or ui.configbool(
61 if typ in allowed or ui.configbool(
62 b'web', b'allow' + typ, untrusted=True
62 b'web', b'allow' + typ, untrusted=True
63 ):
63 ):
64 archives.append(
64 archives.append(
65 {
65 {
66 b'type': typ,
66 b'type': typ,
67 b'extension': spec[2],
67 b'extension': spec[2],
68 b'node': nodeid,
68 b'node': nodeid,
69 b'url': url,
69 b'url': url,
70 }
70 }
71 )
71 )
72
72
73 return templateutil.mappinglist(archives)
73 return templateutil.mappinglist(archives)
74
74
75
75
76 def up(p):
76 def up(p):
77 if p[0:1] != b"/":
77 if p[0:1] != b"/":
78 p = b"/" + p
78 p = b"/" + p
79 if p[-1:] == b"/":
79 if p[-1:] == b"/":
80 p = p[:-1]
80 p = p[:-1]
81 up = os.path.dirname(p)
81 up = os.path.dirname(p)
82 if up == b"/":
82 if up == b"/":
83 return b"/"
83 return b"/"
84 return up + b"/"
84 return up + b"/"
85
85
86
86
87 def _navseq(step, firststep=None):
87 def _navseq(step, firststep=None):
88 if firststep:
88 if firststep:
89 yield firststep
89 yield firststep
90 if firststep >= 20 and firststep <= 40:
90 if firststep >= 20 and firststep <= 40:
91 firststep = 50
91 firststep = 50
92 yield firststep
92 yield firststep
93 assert step > 0
93 assert step > 0
94 assert firststep > 0
94 assert firststep > 0
95 while step <= firststep:
95 while step <= firststep:
96 step *= 10
96 step *= 10
97 while True:
97 while True:
98 yield 1 * step
98 yield 1 * step
99 yield 3 * step
99 yield 3 * step
100 step *= 10
100 step *= 10
101
101
102
102
103 class revnav(object):
103 class revnav(object):
104 def __init__(self, repo):
104 def __init__(self, repo):
105 """Navigation generation object
105 """Navigation generation object
106
106
107 :repo: repo object we generate nav for
107 :repo: repo object we generate nav for
108 """
108 """
109 # used for hex generation
109 # used for hex generation
110 self._revlog = repo.changelog
110 self._revlog = repo.changelog
111
111
112 def __nonzero__(self):
112 def __nonzero__(self):
113 """return True if any revision to navigate over"""
113 """return True if any revision to navigate over"""
114 return self._first() is not None
114 return self._first() is not None
115
115
116 __bool__ = __nonzero__
116 __bool__ = __nonzero__
117
117
118 def _first(self):
118 def _first(self):
119 """return the minimum non-filtered changeset or None"""
119 """return the minimum non-filtered changeset or None"""
120 try:
120 try:
121 return next(iter(self._revlog))
121 return next(iter(self._revlog))
122 except StopIteration:
122 except StopIteration:
123 return None
123 return None
124
124
125 def hex(self, rev):
125 def hex(self, rev):
126 return hex(self._revlog.node(rev))
126 return hex(self._revlog.node(rev))
127
127
128 def gen(self, pos, pagelen, limit):
128 def gen(self, pos, pagelen, limit):
129 """computes label and revision id for navigation link
129 """computes label and revision id for navigation link
130
130
131 :pos: is the revision relative to which we generate navigation.
131 :pos: is the revision relative to which we generate navigation.
132 :pagelen: the size of each navigation page
132 :pagelen: the size of each navigation page
133 :limit: how far shall we link
133 :limit: how far shall we link
134
134
135 The return is:
135 The return is:
136 - a single element mappinglist
136 - a single element mappinglist
137 - containing a dictionary with a `before` and `after` key
137 - containing a dictionary with a `before` and `after` key
138 - values are dictionaries with `label` and `node` keys
138 - values are dictionaries with `label` and `node` keys
139 """
139 """
140 if not self:
140 if not self:
141 # empty repo
141 # empty repo
142 return templateutil.mappinglist(
142 return templateutil.mappinglist(
143 [
143 [
144 {
144 {
145 b'before': templateutil.mappinglist([]),
145 b'before': templateutil.mappinglist([]),
146 b'after': templateutil.mappinglist([]),
146 b'after': templateutil.mappinglist([]),
147 },
147 },
148 ]
148 ]
149 )
149 )
150
150
151 targets = []
151 targets = []
152 for f in _navseq(1, pagelen):
152 for f in _navseq(1, pagelen):
153 if f > limit:
153 if f > limit:
154 break
154 break
155 targets.append(pos + f)
155 targets.append(pos + f)
156 targets.append(pos - f)
156 targets.append(pos - f)
157 targets.sort()
157 targets.sort()
158
158
159 first = self._first()
159 first = self._first()
160 navbefore = [{b'label': b'(%i)' % first, b'node': self.hex(first)}]
160 navbefore = [{b'label': b'(%i)' % first, b'node': self.hex(first)}]
161 navafter = []
161 navafter = []
162 for rev in targets:
162 for rev in targets:
163 if rev not in self._revlog:
163 if rev not in self._revlog:
164 continue
164 continue
165 if pos < rev < limit:
165 if pos < rev < limit:
166 navafter.append(
166 navafter.append(
167 {b'label': b'+%d' % abs(rev - pos), b'node': self.hex(rev)}
167 {b'label': b'+%d' % abs(rev - pos), b'node': self.hex(rev)}
168 )
168 )
169 if 0 < rev < pos:
169 if 0 < rev < pos:
170 navbefore.append(
170 navbefore.append(
171 {b'label': b'-%d' % abs(rev - pos), b'node': self.hex(rev)}
171 {b'label': b'-%d' % abs(rev - pos), b'node': self.hex(rev)}
172 )
172 )
173
173
174 navafter.append({b'label': b'tip', b'node': b'tip'})
174 navafter.append({b'label': b'tip', b'node': b'tip'})
175
175
176 # TODO: maybe this can be a scalar object supporting tomap()
176 # TODO: maybe this can be a scalar object supporting tomap()
177 return templateutil.mappinglist(
177 return templateutil.mappinglist(
178 [
178 [
179 {
179 {
180 b'before': templateutil.mappinglist(navbefore),
180 b'before': templateutil.mappinglist(navbefore),
181 b'after': templateutil.mappinglist(navafter),
181 b'after': templateutil.mappinglist(navafter),
182 },
182 },
183 ]
183 ]
184 )
184 )
185
185
186
186
187 class filerevnav(revnav):
187 class filerevnav(revnav):
188 def __init__(self, repo, path):
188 def __init__(self, repo, path):
189 """Navigation generation object
189 """Navigation generation object
190
190
191 :repo: repo object we generate nav for
191 :repo: repo object we generate nav for
192 :path: path of the file we generate nav for
192 :path: path of the file we generate nav for
193 """
193 """
194 # used for iteration
194 # used for iteration
195 self._changelog = repo.unfiltered().changelog
195 self._changelog = repo.unfiltered().changelog
196 # used for hex generation
196 # used for hex generation
197 self._revlog = repo.file(path)
197 self._revlog = repo.file(path)
198
198
199 def hex(self, rev):
199 def hex(self, rev):
200 return hex(self._changelog.node(self._revlog.linkrev(rev)))
200 return hex(self._changelog.node(self._revlog.linkrev(rev)))
201
201
202
202
203 # TODO: maybe this can be a wrapper class for changectx/filectx list, which
203 # TODO: maybe this can be a wrapper class for changectx/filectx list, which
204 # yields {'ctx': ctx}
204 # yields {'ctx': ctx}
205 def _ctxsgen(context, ctxs):
205 def _ctxsgen(context, ctxs):
206 for s in ctxs:
206 for s in ctxs:
207 d = {
207 d = {
208 b'node': s.hex(),
208 b'node': s.hex(),
209 b'rev': s.rev(),
209 b'rev': s.rev(),
210 b'user': s.user(),
210 b'user': s.user(),
211 b'date': s.date(),
211 b'date': s.date(),
212 b'description': s.description(),
212 b'description': s.description(),
213 b'branch': s.branch(),
213 b'branch': s.branch(),
214 }
214 }
215 if util.safehasattr(s, b'path'):
215 if util.safehasattr(s, b'path'):
216 d[b'file'] = s.path()
216 d[b'file'] = s.path()
217 yield d
217 yield d
218
218
219
219
220 def _siblings(siblings=None, hiderev=None):
220 def _siblings(siblings=None, hiderev=None):
221 if siblings is None:
221 if siblings is None:
222 siblings = []
222 siblings = []
223 siblings = [s for s in siblings if s.node() != nullid]
223 siblings = [s for s in siblings if s.node() != nullid]
224 if len(siblings) == 1 and siblings[0].rev() == hiderev:
224 if len(siblings) == 1 and siblings[0].rev() == hiderev:
225 siblings = []
225 siblings = []
226 return templateutil.mappinggenerator(_ctxsgen, args=(siblings,))
226 return templateutil.mappinggenerator(_ctxsgen, args=(siblings,))
227
227
228
228
229 def difffeatureopts(req, ui, section):
229 def difffeatureopts(req, ui, section):
230 diffopts = diffutil.difffeatureopts(
230 diffopts = diffutil.difffeatureopts(
231 ui, untrusted=True, section=section, whitespace=True
231 ui, untrusted=True, section=section, whitespace=True
232 )
232 )
233
233
234 for k in (
234 for k in (
235 b'ignorews',
235 b'ignorews',
236 b'ignorewsamount',
236 b'ignorewsamount',
237 b'ignorewseol',
237 b'ignorewseol',
238 b'ignoreblanklines',
238 b'ignoreblanklines',
239 ):
239 ):
240 v = req.qsparams.get(k)
240 v = req.qsparams.get(k)
241 if v is not None:
241 if v is not None:
242 v = stringutil.parsebool(v)
242 v = stringutil.parsebool(v)
243 setattr(diffopts, k, v if v is not None else True)
243 setattr(diffopts, k, v if v is not None else True)
244
244
245 return diffopts
245 return diffopts
246
246
247
247
248 def annotate(req, fctx, ui):
248 def annotate(req, fctx, ui):
249 diffopts = difffeatureopts(req, ui, b'annotate')
249 diffopts = difffeatureopts(req, ui, b'annotate')
250 return fctx.annotate(follow=True, diffopts=diffopts)
250 return fctx.annotate(follow=True, diffopts=diffopts)
251
251
252
252
253 def parents(ctx, hide=None):
253 def parents(ctx, hide=None):
254 if isinstance(ctx, context.basefilectx):
254 if isinstance(ctx, context.basefilectx):
255 introrev = ctx.introrev()
255 introrev = ctx.introrev()
256 if ctx.changectx().rev() != introrev:
256 if ctx.changectx().rev() != introrev:
257 return _siblings([ctx.repo()[introrev]], hide)
257 return _siblings([ctx.repo()[introrev]], hide)
258 return _siblings(ctx.parents(), hide)
258 return _siblings(ctx.parents(), hide)
259
259
260
260
261 def children(ctx, hide=None):
261 def children(ctx, hide=None):
262 return _siblings(ctx.children(), hide)
262 return _siblings(ctx.children(), hide)
263
263
264
264
265 def renamelink(fctx):
265 def renamelink(fctx):
266 r = fctx.renamed()
266 r = fctx.renamed()
267 if r:
267 if r:
268 return templateutil.mappinglist([{b'file': r[0], b'node': hex(r[1])}])
268 return templateutil.mappinglist([{b'file': r[0], b'node': hex(r[1])}])
269 return templateutil.mappinglist([])
269 return templateutil.mappinglist([])
270
270
271
271
272 def nodetagsdict(repo, node):
272 def nodetagsdict(repo, node):
273 return templateutil.hybridlist(repo.nodetags(node), name=b'name')
273 return templateutil.hybridlist(repo.nodetags(node), name=b'name')
274
274
275
275
276 def nodebookmarksdict(repo, node):
276 def nodebookmarksdict(repo, node):
277 return templateutil.hybridlist(repo.nodebookmarks(node), name=b'name')
277 return templateutil.hybridlist(repo.nodebookmarks(node), name=b'name')
278
278
279
279
280 def nodebranchdict(repo, ctx):
280 def nodebranchdict(repo, ctx):
281 branches = []
281 branches = []
282 branch = ctx.branch()
282 branch = ctx.branch()
283 # If this is an empty repo, ctx.node() == nullid,
283 # If this is an empty repo, ctx.node() == nullid,
284 # ctx.branch() == 'default'.
284 # ctx.branch() == 'default'.
285 try:
285 try:
286 branchnode = repo.branchtip(branch)
286 branchnode = repo.branchtip(branch)
287 except error.RepoLookupError:
287 except error.RepoLookupError:
288 branchnode = None
288 branchnode = None
289 if branchnode == ctx.node():
289 if branchnode == ctx.node():
290 branches.append(branch)
290 branches.append(branch)
291 return templateutil.hybridlist(branches, name=b'name')
291 return templateutil.hybridlist(branches, name=b'name')
292
292
293
293
294 def nodeinbranch(repo, ctx):
294 def nodeinbranch(repo, ctx):
295 branches = []
295 branches = []
296 branch = ctx.branch()
296 branch = ctx.branch()
297 try:
297 try:
298 branchnode = repo.branchtip(branch)
298 branchnode = repo.branchtip(branch)
299 except error.RepoLookupError:
299 except error.RepoLookupError:
300 branchnode = None
300 branchnode = None
301 if branch != b'default' and branchnode != ctx.node():
301 if branch != b'default' and branchnode != ctx.node():
302 branches.append(branch)
302 branches.append(branch)
303 return templateutil.hybridlist(branches, name=b'name')
303 return templateutil.hybridlist(branches, name=b'name')
304
304
305
305
306 def nodebranchnodefault(ctx):
306 def nodebranchnodefault(ctx):
307 branches = []
307 branches = []
308 branch = ctx.branch()
308 branch = ctx.branch()
309 if branch != b'default':
309 if branch != b'default':
310 branches.append(branch)
310 branches.append(branch)
311 return templateutil.hybridlist(branches, name=b'name')
311 return templateutil.hybridlist(branches, name=b'name')
312
312
313
313
314 def _nodenamesgen(context, f, node, name):
314 def _nodenamesgen(context, f, node, name):
315 for t in f(node):
315 for t in f(node):
316 yield {name: t}
316 yield {name: t}
317
317
318
318
319 def showtag(repo, t1, node=nullid):
319 def showtag(repo, t1, node=nullid):
320 args = (repo.nodetags, node, b'tag')
320 args = (repo.nodetags, node, b'tag')
321 return templateutil.mappinggenerator(_nodenamesgen, args=args, name=t1)
321 return templateutil.mappinggenerator(_nodenamesgen, args=args, name=t1)
322
322
323
323
324 def showbookmark(repo, t1, node=nullid):
324 def showbookmark(repo, t1, node=nullid):
325 args = (repo.nodebookmarks, node, b'bookmark')
325 args = (repo.nodebookmarks, node, b'bookmark')
326 return templateutil.mappinggenerator(_nodenamesgen, args=args, name=t1)
326 return templateutil.mappinggenerator(_nodenamesgen, args=args, name=t1)
327
327
328
328
329 def branchentries(repo, stripecount, limit=0):
329 def branchentries(repo, stripecount, limit=0):
330 tips = []
330 tips = []
331 heads = repo.heads()
331 heads = repo.heads()
332 parity = paritygen(stripecount)
332 parity = paritygen(stripecount)
333 sortkey = lambda item: (not item[1], item[0].rev())
333 sortkey = lambda item: (not item[1], item[0].rev())
334
334
335 def entries(context):
335 def entries(context):
336 count = 0
336 count = 0
337 if not tips:
337 if not tips:
338 for tag, hs, tip, closed in repo.branchmap().iterbranches():
338 for tag, hs, tip, closed in repo.branchmap().iterbranches():
339 tips.append((repo[tip], closed))
339 tips.append((repo[tip], closed))
340 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
340 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
341 if limit > 0 and count >= limit:
341 if limit > 0 and count >= limit:
342 return
342 return
343 count += 1
343 count += 1
344 if closed:
344 if closed:
345 status = b'closed'
345 status = b'closed'
346 elif ctx.node() not in heads:
346 elif ctx.node() not in heads:
347 status = b'inactive'
347 status = b'inactive'
348 else:
348 else:
349 status = b'open'
349 status = b'open'
350 yield {
350 yield {
351 b'parity': next(parity),
351 b'parity': next(parity),
352 b'branch': ctx.branch(),
352 b'branch': ctx.branch(),
353 b'status': status,
353 b'status': status,
354 b'node': ctx.hex(),
354 b'node': ctx.hex(),
355 b'date': ctx.date(),
355 b'date': ctx.date(),
356 }
356 }
357
357
358 return templateutil.mappinggenerator(entries)
358 return templateutil.mappinggenerator(entries)
359
359
360
360
361 def cleanpath(repo, path):
361 def cleanpath(repo, path):
362 path = path.lstrip(b'/')
362 path = path.lstrip(b'/')
363 auditor = pathutil.pathauditor(repo.root, realfs=False)
363 auditor = pathutil.pathauditor(repo.root, realfs=False)
364 return pathutil.canonpath(repo.root, b'', path, auditor=auditor)
364 return pathutil.canonpath(repo.root, b'', path, auditor=auditor)
365
365
366
366
367 def changectx(repo, req):
367 def changectx(repo, req):
368 changeid = b"tip"
368 changeid = b"tip"
369 if b'node' in req.qsparams:
369 if b'node' in req.qsparams:
370 changeid = req.qsparams[b'node']
370 changeid = req.qsparams[b'node']
371 ipos = changeid.find(b':')
371 ipos = changeid.find(b':')
372 if ipos != -1:
372 if ipos != -1:
373 changeid = changeid[(ipos + 1) :]
373 changeid = changeid[(ipos + 1) :]
374
374
375 return scmutil.revsymbol(repo, changeid)
375 return scmutil.revsymbol(repo, changeid)
376
376
377
377
378 def basechangectx(repo, req):
378 def basechangectx(repo, req):
379 if b'node' in req.qsparams:
379 if b'node' in req.qsparams:
380 changeid = req.qsparams[b'node']
380 changeid = req.qsparams[b'node']
381 ipos = changeid.find(b':')
381 ipos = changeid.find(b':')
382 if ipos != -1:
382 if ipos != -1:
383 changeid = changeid[:ipos]
383 changeid = changeid[:ipos]
384 return scmutil.revsymbol(repo, changeid)
384 return scmutil.revsymbol(repo, changeid)
385
385
386 return None
386 return None
387
387
388
388
389 def filectx(repo, req):
389 def filectx(repo, req):
390 if b'file' not in req.qsparams:
390 if b'file' not in req.qsparams:
391 raise ErrorResponse(HTTP_NOT_FOUND, b'file not given')
391 raise ErrorResponse(HTTP_NOT_FOUND, b'file not given')
392 path = cleanpath(repo, req.qsparams[b'file'])
392 path = cleanpath(repo, req.qsparams[b'file'])
393 if b'node' in req.qsparams:
393 if b'node' in req.qsparams:
394 changeid = req.qsparams[b'node']
394 changeid = req.qsparams[b'node']
395 elif b'filenode' in req.qsparams:
395 elif b'filenode' in req.qsparams:
396 changeid = req.qsparams[b'filenode']
396 changeid = req.qsparams[b'filenode']
397 else:
397 else:
398 raise ErrorResponse(HTTP_NOT_FOUND, b'node or filenode not given')
398 raise ErrorResponse(HTTP_NOT_FOUND, b'node or filenode not given')
399 try:
399 try:
400 fctx = scmutil.revsymbol(repo, changeid)[path]
400 fctx = scmutil.revsymbol(repo, changeid)[path]
401 except error.RepoError:
401 except error.RepoError:
402 fctx = repo.filectx(path, fileid=changeid)
402 fctx = repo.filectx(path, fileid=changeid)
403
403
404 return fctx
404 return fctx
405
405
406
406
407 def linerange(req):
407 def linerange(req):
408 linerange = req.qsparams.getall(b'linerange')
408 linerange = req.qsparams.getall(b'linerange')
409 if not linerange:
409 if not linerange:
410 return None
410 return None
411 if len(linerange) > 1:
411 if len(linerange) > 1:
412 raise ErrorResponse(HTTP_BAD_REQUEST, b'redundant linerange parameter')
412 raise ErrorResponse(HTTP_BAD_REQUEST, b'redundant linerange parameter')
413 try:
413 try:
414 fromline, toline = map(int, linerange[0].split(b':', 1))
414 fromline, toline = map(int, linerange[0].split(b':', 1))
415 except ValueError:
415 except ValueError:
416 raise ErrorResponse(HTTP_BAD_REQUEST, b'invalid linerange parameter')
416 raise ErrorResponse(HTTP_BAD_REQUEST, b'invalid linerange parameter')
417 try:
417 try:
418 return util.processlinerange(fromline, toline)
418 return util.processlinerange(fromline, toline)
419 except error.ParseError as exc:
419 except error.ParseError as exc:
420 raise ErrorResponse(HTTP_BAD_REQUEST, pycompat.bytestr(exc))
420 raise ErrorResponse(HTTP_BAD_REQUEST, pycompat.bytestr(exc))
421
421
422
422
423 def formatlinerange(fromline, toline):
423 def formatlinerange(fromline, toline):
424 return b'%d:%d' % (fromline + 1, toline)
424 return b'%d:%d' % (fromline + 1, toline)
425
425
426
426
427 def _succsandmarkersgen(context, mapping):
427 def _succsandmarkersgen(context, mapping):
428 repo = context.resource(mapping, b'repo')
428 repo = context.resource(mapping, b'repo')
429 itemmappings = templatekw.showsuccsandmarkers(context, mapping)
429 itemmappings = templatekw.showsuccsandmarkers(context, mapping)
430 for item in itemmappings.tovalue(context, mapping):
430 for item in itemmappings.tovalue(context, mapping):
431 item[b'successors'] = _siblings(
431 item[b'successors'] = _siblings(
432 repo[successor] for successor in item[b'successors']
432 repo[successor] for successor in item[b'successors']
433 )
433 )
434 yield item
434 yield item
435
435
436
436
437 def succsandmarkers(context, mapping):
437 def succsandmarkers(context, mapping):
438 return templateutil.mappinggenerator(_succsandmarkersgen, args=(mapping,))
438 return templateutil.mappinggenerator(_succsandmarkersgen, args=(mapping,))
439
439
440
440
441 # teach templater succsandmarkers is switched to (context, mapping) API
441 # teach templater succsandmarkers is switched to (context, mapping) API
442 succsandmarkers._requires = {b'repo', b'ctx'}
442 succsandmarkers._requires = {b'repo', b'ctx'}
443
443
444
444
445 def _whyunstablegen(context, mapping):
445 def _whyunstablegen(context, mapping):
446 repo = context.resource(mapping, b'repo')
446 repo = context.resource(mapping, b'repo')
447 ctx = context.resource(mapping, b'ctx')
447 ctx = context.resource(mapping, b'ctx')
448
448
449 entries = obsutil.whyunstable(repo, ctx)
449 entries = obsutil.whyunstable(repo, ctx)
450 for entry in entries:
450 for entry in entries:
451 if entry.get(b'divergentnodes'):
451 if entry.get(b'divergentnodes'):
452 entry[b'divergentnodes'] = _siblings(entry[b'divergentnodes'])
452 entry[b'divergentnodes'] = _siblings(entry[b'divergentnodes'])
453 yield entry
453 yield entry
454
454
455
455
456 def whyunstable(context, mapping):
456 def whyunstable(context, mapping):
457 return templateutil.mappinggenerator(_whyunstablegen, args=(mapping,))
457 return templateutil.mappinggenerator(_whyunstablegen, args=(mapping,))
458
458
459
459
460 whyunstable._requires = {b'repo', b'ctx'}
460 whyunstable._requires = {b'repo', b'ctx'}
461
461
462
462
463 def commonentry(repo, ctx):
463 def commonentry(repo, ctx):
464 node = scmutil.binnode(ctx)
464 node = scmutil.binnode(ctx)
465 return {
465 return {
466 # TODO: perhaps ctx.changectx() should be assigned if ctx is a
466 # TODO: perhaps ctx.changectx() should be assigned if ctx is a
467 # filectx, but I'm not pretty sure if that would always work because
467 # filectx, but I'm not pretty sure if that would always work because
468 # fctx.parents() != fctx.changectx.parents() for example.
468 # fctx.parents() != fctx.changectx.parents() for example.
469 b'ctx': ctx,
469 b'ctx': ctx,
470 b'rev': ctx.rev(),
470 b'rev': ctx.rev(),
471 b'node': hex(node),
471 b'node': hex(node),
472 b'author': ctx.user(),
472 b'author': ctx.user(),
473 b'desc': ctx.description(),
473 b'desc': ctx.description(),
474 b'date': ctx.date(),
474 b'date': ctx.date(),
475 b'extra': ctx.extra(),
475 b'extra': ctx.extra(),
476 b'phase': ctx.phasestr(),
476 b'phase': ctx.phasestr(),
477 b'obsolete': ctx.obsolete(),
477 b'obsolete': ctx.obsolete(),
478 b'succsandmarkers': succsandmarkers,
478 b'succsandmarkers': succsandmarkers,
479 b'instabilities': templateutil.hybridlist(
479 b'instabilities': templateutil.hybridlist(
480 ctx.instabilities(), name=b'instability'
480 ctx.instabilities(), name=b'instability'
481 ),
481 ),
482 b'whyunstable': whyunstable,
482 b'whyunstable': whyunstable,
483 b'branch': nodebranchnodefault(ctx),
483 b'branch': nodebranchnodefault(ctx),
484 b'inbranch': nodeinbranch(repo, ctx),
484 b'inbranch': nodeinbranch(repo, ctx),
485 b'branches': nodebranchdict(repo, ctx),
485 b'branches': nodebranchdict(repo, ctx),
486 b'tags': nodetagsdict(repo, node),
486 b'tags': nodetagsdict(repo, node),
487 b'bookmarks': nodebookmarksdict(repo, node),
487 b'bookmarks': nodebookmarksdict(repo, node),
488 b'parent': lambda context, mapping: parents(ctx),
488 b'parent': lambda context, mapping: parents(ctx),
489 b'child': lambda context, mapping: children(ctx),
489 b'child': lambda context, mapping: children(ctx),
490 }
490 }
491
491
492
492
493 def changelistentry(web, ctx):
493 def changelistentry(web, ctx):
494 '''Obtain a dictionary to be used for entries in a changelist.
494 '''Obtain a dictionary to be used for entries in a changelist.
495
495
496 This function is called when producing items for the "entries" list passed
496 This function is called when producing items for the "entries" list passed
497 to the "shortlog" and "changelog" templates.
497 to the "shortlog" and "changelog" templates.
498 '''
498 '''
499 repo = web.repo
499 repo = web.repo
500 rev = ctx.rev()
500 rev = ctx.rev()
501 n = scmutil.binnode(ctx)
501 n = scmutil.binnode(ctx)
502 showtags = showtag(repo, b'changelogtag', n)
502 showtags = showtag(repo, b'changelogtag', n)
503 files = listfilediffs(ctx.files(), n, web.maxfiles)
503 files = listfilediffs(ctx.files(), n, web.maxfiles)
504
504
505 entry = commonentry(repo, ctx)
505 entry = commonentry(repo, ctx)
506 entry.update(
506 entry.update(
507 {
507 {
508 b'allparents': lambda context, mapping: parents(ctx),
508 b'allparents': lambda context, mapping: parents(ctx),
509 b'parent': lambda context, mapping: parents(ctx, rev - 1),
509 b'parent': lambda context, mapping: parents(ctx, rev - 1),
510 b'child': lambda context, mapping: children(ctx, rev + 1),
510 b'child': lambda context, mapping: children(ctx, rev + 1),
511 b'changelogtag': showtags,
511 b'changelogtag': showtags,
512 b'files': files,
512 b'files': files,
513 }
513 }
514 )
514 )
515 return entry
515 return entry
516
516
517
517
518 def changelistentries(web, revs, maxcount, parityfn):
518 def changelistentries(web, revs, maxcount, parityfn):
519 """Emit up to N records for an iterable of revisions."""
519 """Emit up to N records for an iterable of revisions."""
520 repo = web.repo
520 repo = web.repo
521
521
522 count = 0
522 count = 0
523 for rev in revs:
523 for rev in revs:
524 if count >= maxcount:
524 if count >= maxcount:
525 break
525 break
526
526
527 count += 1
527 count += 1
528
528
529 entry = changelistentry(web, repo[rev])
529 entry = changelistentry(web, repo[rev])
530 entry[b'parity'] = next(parityfn)
530 entry[b'parity'] = next(parityfn)
531
531
532 yield entry
532 yield entry
533
533
534
534
535 def symrevorshortnode(req, ctx):
535 def symrevorshortnode(req, ctx):
536 if b'node' in req.qsparams:
536 if b'node' in req.qsparams:
537 return templatefilters.revescape(req.qsparams[b'node'])
537 return templatefilters.revescape(req.qsparams[b'node'])
538 else:
538 else:
539 return short(scmutil.binnode(ctx))
539 return short(scmutil.binnode(ctx))
540
540
541
541
542 def _listfilesgen(context, ctx, stripecount):
542 def _listfilesgen(context, ctx, stripecount):
543 parity = paritygen(stripecount)
543 parity = paritygen(stripecount)
544 filesadded = ctx.filesadded()
544 filesadded = ctx.filesadded()
545 for blockno, f in enumerate(ctx.files()):
545 for blockno, f in enumerate(ctx.files()):
546 if f not in ctx:
546 if f not in ctx:
547 status = b'removed'
547 status = b'removed'
548 elif f in filesadded:
548 elif f in filesadded:
549 status = b'added'
549 status = b'added'
550 else:
550 else:
551 status = b'modified'
551 status = b'modified'
552 template = b'filenolink' if status == b'removed' else b'filenodelink'
552 template = b'filenolink' if status == b'removed' else b'filenodelink'
553 yield context.process(
553 yield context.process(
554 template,
554 template,
555 {
555 {
556 b'node': ctx.hex(),
556 b'node': ctx.hex(),
557 b'file': f,
557 b'file': f,
558 b'blockno': blockno + 1,
558 b'blockno': blockno + 1,
559 b'parity': next(parity),
559 b'parity': next(parity),
560 b'status': status,
560 b'status': status,
561 },
561 },
562 )
562 )
563
563
564
564
565 def changesetentry(web, ctx):
565 def changesetentry(web, ctx):
566 '''Obtain a dictionary to be used to render the "changeset" template.'''
566 '''Obtain a dictionary to be used to render the "changeset" template.'''
567
567
568 showtags = showtag(web.repo, b'changesettag', scmutil.binnode(ctx))
568 showtags = showtag(web.repo, b'changesettag', scmutil.binnode(ctx))
569 showbookmarks = showbookmark(
569 showbookmarks = showbookmark(
570 web.repo, b'changesetbookmark', scmutil.binnode(ctx)
570 web.repo, b'changesetbookmark', scmutil.binnode(ctx)
571 )
571 )
572 showbranch = nodebranchnodefault(ctx)
572 showbranch = nodebranchnodefault(ctx)
573
573
574 basectx = basechangectx(web.repo, web.req)
574 basectx = basechangectx(web.repo, web.req)
575 if basectx is None:
575 if basectx is None:
576 basectx = ctx.p1()
576 basectx = ctx.p1()
577
577
578 style = web.config(b'web', b'style')
578 style = web.config(b'web', b'style')
579 if b'style' in web.req.qsparams:
579 if b'style' in web.req.qsparams:
580 style = web.req.qsparams[b'style']
580 style = web.req.qsparams[b'style']
581
581
582 diff = diffs(web, ctx, basectx, None, style)
582 diff = diffs(web, ctx, basectx, None, style)
583
583
584 parity = paritygen(web.stripecount)
584 parity = paritygen(web.stripecount)
585 diffstatsgen = diffstatgen(web.repo.ui, ctx, basectx)
585 diffstatsgen = diffstatgen(web.repo.ui, ctx, basectx)
586 diffstats = diffstat(ctx, diffstatsgen, parity)
586 diffstats = diffstat(ctx, diffstatsgen, parity)
587
587
588 return dict(
588 return dict(
589 diff=diff,
589 diff=diff,
590 symrev=symrevorshortnode(web.req, ctx),
590 symrev=symrevorshortnode(web.req, ctx),
591 basenode=basectx.hex(),
591 basenode=basectx.hex(),
592 changesettag=showtags,
592 changesettag=showtags,
593 changesetbookmark=showbookmarks,
593 changesetbookmark=showbookmarks,
594 changesetbranch=showbranch,
594 changesetbranch=showbranch,
595 files=templateutil.mappedgenerator(
595 files=templateutil.mappedgenerator(
596 _listfilesgen, args=(ctx, web.stripecount)
596 _listfilesgen, args=(ctx, web.stripecount)
597 ),
597 ),
598 diffsummary=lambda context, mapping: diffsummary(diffstatsgen),
598 diffsummary=lambda context, mapping: diffsummary(diffstatsgen),
599 diffstat=diffstats,
599 diffstat=diffstats,
600 archives=web.archivelist(ctx.hex()),
600 archives=web.archivelist(ctx.hex()),
601 **pycompat.strkwargs(commonentry(web.repo, ctx))
601 **pycompat.strkwargs(commonentry(web.repo, ctx))
602 )
602 )
603
603
604
604
605 def _listfilediffsgen(context, files, node, max):
605 def _listfilediffsgen(context, files, node, max):
606 for f in files[:max]:
606 for f in files[:max]:
607 yield context.process(b'filedifflink', {b'node': hex(node), b'file': f})
607 yield context.process(b'filedifflink', {b'node': hex(node), b'file': f})
608 if len(files) > max:
608 if len(files) > max:
609 yield context.process(b'fileellipses', {})
609 yield context.process(b'fileellipses', {})
610
610
611
611
612 def listfilediffs(files, node, max):
612 def listfilediffs(files, node, max):
613 return templateutil.mappedgenerator(
613 return templateutil.mappedgenerator(
614 _listfilediffsgen, args=(files, node, max)
614 _listfilediffsgen, args=(files, node, max)
615 )
615 )
616
616
617
617
618 def _prettyprintdifflines(context, lines, blockno, lineidprefix):
618 def _prettyprintdifflines(context, lines, blockno, lineidprefix):
619 for lineno, l in enumerate(lines, 1):
619 for lineno, l in enumerate(lines, 1):
620 difflineno = b"%d.%d" % (blockno, lineno)
620 difflineno = b"%d.%d" % (blockno, lineno)
621 if l.startswith(b'+'):
621 if l.startswith(b'+'):
622 ltype = b"difflineplus"
622 ltype = b"difflineplus"
623 elif l.startswith(b'-'):
623 elif l.startswith(b'-'):
624 ltype = b"difflineminus"
624 ltype = b"difflineminus"
625 elif l.startswith(b'@'):
625 elif l.startswith(b'@'):
626 ltype = b"difflineat"
626 ltype = b"difflineat"
627 else:
627 else:
628 ltype = b"diffline"
628 ltype = b"diffline"
629 yield context.process(
629 yield context.process(
630 ltype,
630 ltype,
631 {
631 {
632 b'line': l,
632 b'line': l,
633 b'lineno': lineno,
633 b'lineno': lineno,
634 b'lineid': lineidprefix + b"l%s" % difflineno,
634 b'lineid': lineidprefix + b"l%s" % difflineno,
635 b'linenumber': b"% 8s" % difflineno,
635 b'linenumber': b"% 8s" % difflineno,
636 },
636 },
637 )
637 )
638
638
639
639
640 def _diffsgen(
640 def _diffsgen(
641 context,
641 context,
642 repo,
642 repo,
643 ctx,
643 ctx,
644 basectx,
644 basectx,
645 files,
645 files,
646 style,
646 style,
647 stripecount,
647 stripecount,
648 linerange,
648 linerange,
649 lineidprefix,
649 lineidprefix,
650 ):
650 ):
651 if files:
651 if files:
652 m = match.exact(files)
652 m = match.exact(files)
653 else:
653 else:
654 m = match.always()
654 m = match.always()
655
655
656 diffopts = patch.diffopts(repo.ui, untrusted=True)
656 diffopts = patch.diffopts(repo.ui, untrusted=True)
657 parity = paritygen(stripecount)
657 parity = paritygen(stripecount)
658
658
659 diffhunks = patch.diffhunks(repo, basectx, ctx, m, opts=diffopts)
659 diffhunks = patch.diffhunks(repo, basectx, ctx, m, opts=diffopts)
660 for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
660 for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
661 if style != b'raw':
661 if style != b'raw':
662 header = header[1:]
662 header = header[1:]
663 lines = [h + b'\n' for h in header]
663 lines = [h + b'\n' for h in header]
664 for hunkrange, hunklines in hunks:
664 for hunkrange, hunklines in hunks:
665 if linerange is not None and hunkrange is not None:
665 if linerange is not None and hunkrange is not None:
666 s1, l1, s2, l2 = hunkrange
666 s1, l1, s2, l2 = hunkrange
667 if not mdiff.hunkinrange((s2, l2), linerange):
667 if not mdiff.hunkinrange((s2, l2), linerange):
668 continue
668 continue
669 lines.extend(hunklines)
669 lines.extend(hunklines)
670 if lines:
670 if lines:
671 l = templateutil.mappedgenerator(
671 l = templateutil.mappedgenerator(
672 _prettyprintdifflines, args=(lines, blockno, lineidprefix)
672 _prettyprintdifflines, args=(lines, blockno, lineidprefix)
673 )
673 )
674 yield {
674 yield {
675 b'parity': next(parity),
675 b'parity': next(parity),
676 b'blockno': blockno,
676 b'blockno': blockno,
677 b'lines': l,
677 b'lines': l,
678 }
678 }
679
679
680
680
681 def diffs(web, ctx, basectx, files, style, linerange=None, lineidprefix=b''):
681 def diffs(web, ctx, basectx, files, style, linerange=None, lineidprefix=b''):
682 args = (
682 args = (
683 web.repo,
683 web.repo,
684 ctx,
684 ctx,
685 basectx,
685 basectx,
686 files,
686 files,
687 style,
687 style,
688 web.stripecount,
688 web.stripecount,
689 linerange,
689 linerange,
690 lineidprefix,
690 lineidprefix,
691 )
691 )
692 return templateutil.mappinggenerator(
692 return templateutil.mappinggenerator(
693 _diffsgen, args=args, name=b'diffblock'
693 _diffsgen, args=args, name=b'diffblock'
694 )
694 )
695
695
696
696
697 def _compline(type, leftlineno, leftline, rightlineno, rightline):
697 def _compline(type, leftlineno, leftline, rightlineno, rightline):
698 lineid = leftlineno and (b"l%d" % leftlineno) or b''
698 lineid = leftlineno and (b"l%d" % leftlineno) or b''
699 lineid += rightlineno and (b"r%d" % rightlineno) or b''
699 lineid += rightlineno and (b"r%d" % rightlineno) or b''
700 llno = b'%d' % leftlineno if leftlineno else b''
700 llno = b'%d' % leftlineno if leftlineno else b''
701 rlno = b'%d' % rightlineno if rightlineno else b''
701 rlno = b'%d' % rightlineno if rightlineno else b''
702 return {
702 return {
703 b'type': type,
703 b'type': type,
704 b'lineid': lineid,
704 b'lineid': lineid,
705 b'leftlineno': leftlineno,
705 b'leftlineno': leftlineno,
706 b'leftlinenumber': b"% 6s" % llno,
706 b'leftlinenumber': b"% 6s" % llno,
707 b'leftline': leftline or b'',
707 b'leftline': leftline or b'',
708 b'rightlineno': rightlineno,
708 b'rightlineno': rightlineno,
709 b'rightlinenumber': b"% 6s" % rlno,
709 b'rightlinenumber': b"% 6s" % rlno,
710 b'rightline': rightline or b'',
710 b'rightline': rightline or b'',
711 }
711 }
712
712
713
713
714 def _getcompblockgen(context, leftlines, rightlines, opcodes):
714 def _getcompblockgen(context, leftlines, rightlines, opcodes):
715 for type, llo, lhi, rlo, rhi in opcodes:
715 for type, llo, lhi, rlo, rhi in opcodes:
716 type = pycompat.sysbytes(type)
716 type = pycompat.sysbytes(type)
717 len1 = lhi - llo
717 len1 = lhi - llo
718 len2 = rhi - rlo
718 len2 = rhi - rlo
719 count = min(len1, len2)
719 count = min(len1, len2)
720 for i in pycompat.xrange(count):
720 for i in pycompat.xrange(count):
721 yield _compline(
721 yield _compline(
722 type=type,
722 type=type,
723 leftlineno=llo + i + 1,
723 leftlineno=llo + i + 1,
724 leftline=leftlines[llo + i],
724 leftline=leftlines[llo + i],
725 rightlineno=rlo + i + 1,
725 rightlineno=rlo + i + 1,
726 rightline=rightlines[rlo + i],
726 rightline=rightlines[rlo + i],
727 )
727 )
728 if len1 > len2:
728 if len1 > len2:
729 for i in pycompat.xrange(llo + count, lhi):
729 for i in pycompat.xrange(llo + count, lhi):
730 yield _compline(
730 yield _compline(
731 type=type,
731 type=type,
732 leftlineno=i + 1,
732 leftlineno=i + 1,
733 leftline=leftlines[i],
733 leftline=leftlines[i],
734 rightlineno=None,
734 rightlineno=None,
735 rightline=None,
735 rightline=None,
736 )
736 )
737 elif len2 > len1:
737 elif len2 > len1:
738 for i in pycompat.xrange(rlo + count, rhi):
738 for i in pycompat.xrange(rlo + count, rhi):
739 yield _compline(
739 yield _compline(
740 type=type,
740 type=type,
741 leftlineno=None,
741 leftlineno=None,
742 leftline=None,
742 leftline=None,
743 rightlineno=i + 1,
743 rightlineno=i + 1,
744 rightline=rightlines[i],
744 rightline=rightlines[i],
745 )
745 )
746
746
747
747
748 def _getcompblock(leftlines, rightlines, opcodes):
748 def _getcompblock(leftlines, rightlines, opcodes):
749 args = (leftlines, rightlines, opcodes)
749 args = (leftlines, rightlines, opcodes)
750 return templateutil.mappinggenerator(
750 return templateutil.mappinggenerator(
751 _getcompblockgen, args=args, name=b'comparisonline'
751 _getcompblockgen, args=args, name=b'comparisonline'
752 )
752 )
753
753
754
754
755 def _comparegen(context, contextnum, leftlines, rightlines):
755 def _comparegen(context, contextnum, leftlines, rightlines):
756 '''Generator function that provides side-by-side comparison data.'''
756 '''Generator function that provides side-by-side comparison data.'''
757 s = difflib.SequenceMatcher(None, leftlines, rightlines)
757 s = difflib.SequenceMatcher(None, leftlines, rightlines)
758 if contextnum < 0:
758 if contextnum < 0:
759 l = _getcompblock(leftlines, rightlines, s.get_opcodes())
759 l = _getcompblock(leftlines, rightlines, s.get_opcodes())
760 yield {b'lines': l}
760 yield {b'lines': l}
761 else:
761 else:
762 for oc in s.get_grouped_opcodes(n=contextnum):
762 for oc in s.get_grouped_opcodes(n=contextnum):
763 l = _getcompblock(leftlines, rightlines, oc)
763 l = _getcompblock(leftlines, rightlines, oc)
764 yield {b'lines': l}
764 yield {b'lines': l}
765
765
766
766
767 def compare(contextnum, leftlines, rightlines):
767 def compare(contextnum, leftlines, rightlines):
768 args = (contextnum, leftlines, rightlines)
768 args = (contextnum, leftlines, rightlines)
769 return templateutil.mappinggenerator(
769 return templateutil.mappinggenerator(
770 _comparegen, args=args, name=b'comparisonblock'
770 _comparegen, args=args, name=b'comparisonblock'
771 )
771 )
772
772
773
773
774 def diffstatgen(ui, ctx, basectx):
774 def diffstatgen(ui, ctx, basectx):
775 '''Generator function that provides the diffstat data.'''
775 '''Generator function that provides the diffstat data.'''
776
776
777 diffopts = patch.diffopts(ui, {b'noprefix': False})
777 diffopts = patch.diffopts(ui, {b'noprefix': False})
778 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx, opts=diffopts)))
778 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx, opts=diffopts)))
779 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
779 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
780 while True:
780 while True:
781 yield stats, maxname, maxtotal, addtotal, removetotal, binary
781 yield stats, maxname, maxtotal, addtotal, removetotal, binary
782
782
783
783
784 def diffsummary(statgen):
784 def diffsummary(statgen):
785 '''Return a short summary of the diff.'''
785 '''Return a short summary of the diff.'''
786
786
787 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
787 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
788 return _(b' %d files changed, %d insertions(+), %d deletions(-)\n') % (
788 return _(b' %d files changed, %d insertions(+), %d deletions(-)\n') % (
789 len(stats),
789 len(stats),
790 addtotal,
790 addtotal,
791 removetotal,
791 removetotal,
792 )
792 )
793
793
794
794
795 def _diffstattmplgen(context, ctx, statgen, parity):
795 def _diffstattmplgen(context, ctx, statgen, parity):
796 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
796 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
797 files = ctx.files()
797 files = ctx.files()
798
798
799 def pct(i):
799 def pct(i):
800 if maxtotal == 0:
800 if maxtotal == 0:
801 return 0
801 return 0
802 return (float(i) / maxtotal) * 100
802 return (float(i) / maxtotal) * 100
803
803
804 fileno = 0
804 fileno = 0
805 for filename, adds, removes, isbinary in stats:
805 for filename, adds, removes, isbinary in stats:
806 template = b'diffstatlink' if filename in files else b'diffstatnolink'
806 template = b'diffstatlink' if filename in files else b'diffstatnolink'
807 total = adds + removes
807 total = adds + removes
808 fileno += 1
808 fileno += 1
809 yield context.process(
809 yield context.process(
810 template,
810 template,
811 {
811 {
812 b'node': ctx.hex(),
812 b'node': ctx.hex(),
813 b'file': filename,
813 b'file': filename,
814 b'fileno': fileno,
814 b'fileno': fileno,
815 b'total': total,
815 b'total': total,
816 b'addpct': pct(adds),
816 b'addpct': pct(adds),
817 b'removepct': pct(removes),
817 b'removepct': pct(removes),
818 b'parity': next(parity),
818 b'parity': next(parity),
819 },
819 },
820 )
820 )
821
821
822
822
823 def diffstat(ctx, statgen, parity):
823 def diffstat(ctx, statgen, parity):
824 '''Return a diffstat template for each file in the diff.'''
824 '''Return a diffstat template for each file in the diff.'''
825 args = (ctx, statgen, parity)
825 args = (ctx, statgen, parity)
826 return templateutil.mappedgenerator(_diffstattmplgen, args=args)
826 return templateutil.mappedgenerator(_diffstattmplgen, args=args)
827
827
828
828
829 class sessionvars(templateutil.wrapped):
829 class sessionvars(templateutil.wrapped):
830 def __init__(self, vars, start=b'?'):
830 def __init__(self, vars, start=b'?'):
831 self._start = start
831 self._start = start
832 self._vars = vars
832 self._vars = vars
833
833
834 def __getitem__(self, key):
834 def __getitem__(self, key):
835 return self._vars[key]
835 return self._vars[key]
836
836
837 def __setitem__(self, key, value):
837 def __setitem__(self, key, value):
838 self._vars[key] = value
838 self._vars[key] = value
839
839
840 def __copy__(self):
840 def __copy__(self):
841 return sessionvars(copy.copy(self._vars), self._start)
841 return sessionvars(copy.copy(self._vars), self._start)
842
842
843 def contains(self, context, mapping, item):
843 def contains(self, context, mapping, item):
844 item = templateutil.unwrapvalue(context, mapping, item)
844 item = templateutil.unwrapvalue(context, mapping, item)
845 return item in self._vars
845 return item in self._vars
846
846
847 def getmember(self, context, mapping, key):
847 def getmember(self, context, mapping, key):
848 key = templateutil.unwrapvalue(context, mapping, key)
848 key = templateutil.unwrapvalue(context, mapping, key)
849 return self._vars.get(key)
849 return self._vars.get(key)
850
850
851 def getmin(self, context, mapping):
851 def getmin(self, context, mapping):
852 raise error.ParseError(_(b'not comparable'))
852 raise error.ParseError(_(b'not comparable'))
853
853
854 def getmax(self, context, mapping):
854 def getmax(self, context, mapping):
855 raise error.ParseError(_(b'not comparable'))
855 raise error.ParseError(_(b'not comparable'))
856
856
857 def filter(self, context, mapping, select):
857 def filter(self, context, mapping, select):
858 # implement if necessary
858 # implement if necessary
859 raise error.ParseError(_(b'not filterable'))
859 raise error.ParseError(_(b'not filterable'))
860
860
861 def itermaps(self, context):
861 def itermaps(self, context):
862 separator = self._start
862 separator = self._start
863 for key, value in sorted(pycompat.iteritems(self._vars)):
863 for key, value in sorted(pycompat.iteritems(self._vars)):
864 yield {
864 yield {
865 b'name': key,
865 b'name': key,
866 b'value': pycompat.bytestr(value),
866 b'value': pycompat.bytestr(value),
867 b'separator': separator,
867 b'separator': separator,
868 }
868 }
869 separator = b'&'
869 separator = b'&'
870
870
871 def join(self, context, mapping, sep):
871 def join(self, context, mapping, sep):
872 # could be '{separator}{name}={value|urlescape}'
872 # could be '{separator}{name}={value|urlescape}'
873 raise error.ParseError(_(b'not displayable without template'))
873 raise error.ParseError(_(b'not displayable without template'))
874
874
875 def show(self, context, mapping):
875 def show(self, context, mapping):
876 return self.join(context, b'')
876 return self.join(context, mapping, b'')
877
877
878 def tobool(self, context, mapping):
878 def tobool(self, context, mapping):
879 return bool(self._vars)
879 return bool(self._vars)
880
880
881 def tovalue(self, context, mapping):
881 def tovalue(self, context, mapping):
882 return self._vars
882 return self._vars
883
883
884
884
885 class wsgiui(uimod.ui):
885 class wsgiui(uimod.ui):
886 # default termwidth breaks under mod_wsgi
886 # default termwidth breaks under mod_wsgi
887 def termwidth(self):
887 def termwidth(self):
888 return 80
888 return 80
889
889
890
890
891 def getwebsubs(repo):
891 def getwebsubs(repo):
892 websubtable = []
892 websubtable = []
893 websubdefs = repo.ui.configitems(b'websub')
893 websubdefs = repo.ui.configitems(b'websub')
894 # we must maintain interhg backwards compatibility
894 # we must maintain interhg backwards compatibility
895 websubdefs += repo.ui.configitems(b'interhg')
895 websubdefs += repo.ui.configitems(b'interhg')
896 for key, pattern in websubdefs:
896 for key, pattern in websubdefs:
897 # grab the delimiter from the character after the "s"
897 # grab the delimiter from the character after the "s"
898 unesc = pattern[1:2]
898 unesc = pattern[1:2]
899 delim = stringutil.reescape(unesc)
899 delim = stringutil.reescape(unesc)
900
900
901 # identify portions of the pattern, taking care to avoid escaped
901 # identify portions of the pattern, taking care to avoid escaped
902 # delimiters. the replace format and flags are optional, but
902 # delimiters. the replace format and flags are optional, but
903 # delimiters are required.
903 # delimiters are required.
904 match = re.match(
904 match = re.match(
905 br'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
905 br'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
906 % (delim, delim, delim),
906 % (delim, delim, delim),
907 pattern,
907 pattern,
908 )
908 )
909 if not match:
909 if not match:
910 repo.ui.warn(
910 repo.ui.warn(
911 _(b"websub: invalid pattern for %s: %s\n") % (key, pattern)
911 _(b"websub: invalid pattern for %s: %s\n") % (key, pattern)
912 )
912 )
913 continue
913 continue
914
914
915 # we need to unescape the delimiter for regexp and format
915 # we need to unescape the delimiter for regexp and format
916 delim_re = re.compile(br'(?<!\\)\\%s' % delim)
916 delim_re = re.compile(br'(?<!\\)\\%s' % delim)
917 regexp = delim_re.sub(unesc, match.group(1))
917 regexp = delim_re.sub(unesc, match.group(1))
918 format = delim_re.sub(unesc, match.group(2))
918 format = delim_re.sub(unesc, match.group(2))
919
919
920 # the pattern allows for 6 regexp flags, so set them if necessary
920 # the pattern allows for 6 regexp flags, so set them if necessary
921 flagin = match.group(3)
921 flagin = match.group(3)
922 flags = 0
922 flags = 0
923 if flagin:
923 if flagin:
924 for flag in pycompat.sysstr(flagin.upper()):
924 for flag in pycompat.sysstr(flagin.upper()):
925 flags |= re.__dict__[flag]
925 flags |= re.__dict__[flag]
926
926
927 try:
927 try:
928 regexp = re.compile(regexp, flags)
928 regexp = re.compile(regexp, flags)
929 websubtable.append((regexp, format))
929 websubtable.append((regexp, format))
930 except re.error:
930 except re.error:
931 repo.ui.warn(
931 repo.ui.warn(
932 _(b"websub: invalid regexp for %s: %s\n") % (key, regexp)
932 _(b"websub: invalid regexp for %s: %s\n") % (key, regexp)
933 )
933 )
934 return websubtable
934 return websubtable
935
935
936
936
937 def getgraphnode(repo, ctx):
937 def getgraphnode(repo, ctx):
938 return templatekw.getgraphnodecurrent(
938 return templatekw.getgraphnodecurrent(
939 repo, ctx
939 repo, ctx
940 ) + templatekw.getgraphnodesymbol(ctx)
940 ) + templatekw.getgraphnodesymbol(ctx)
@@ -1,1085 +1,1087 b''
1 # logcmdutil.py - utility for log-like commands
1 # logcmdutil.py - utility for log-like commands
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import itertools
10 import itertools
11 import os
11 import os
12 import posixpath
12 import posixpath
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 nullid,
16 nullid,
17 wdirid,
17 wdirid,
18 wdirrev,
18 wdirrev,
19 )
19 )
20
20
21 from . import (
21 from . import (
22 dagop,
22 dagop,
23 error,
23 error,
24 formatter,
24 formatter,
25 graphmod,
25 graphmod,
26 match as matchmod,
26 match as matchmod,
27 mdiff,
27 mdiff,
28 patch,
28 patch,
29 pathutil,
29 pathutil,
30 pycompat,
30 pycompat,
31 revset,
31 revset,
32 revsetlang,
32 revsetlang,
33 scmutil,
33 scmutil,
34 smartset,
34 smartset,
35 templatekw,
35 templatekw,
36 templater,
36 templater,
37 util,
37 util,
38 )
38 )
39 from .utils import (
39 from .utils import (
40 dateutil,
40 dateutil,
41 stringutil,
41 stringutil,
42 )
42 )
43
43
44
44
45 if pycompat.TYPE_CHECKING:
45 if pycompat.TYPE_CHECKING:
46 from typing import (
46 from typing import (
47 Any,
47 Any,
48 Optional,
48 Optional,
49 Tuple,
49 Tuple,
50 )
50 )
51
51
52 for t in (Any, Optional, Tuple):
52 for t in (Any, Optional, Tuple):
53 assert t
53 assert t
54
54
55
55
56 def getlimit(opts):
56 def getlimit(opts):
57 """get the log limit according to option -l/--limit"""
57 """get the log limit according to option -l/--limit"""
58 limit = opts.get(b'limit')
58 limit = opts.get(b'limit')
59 if limit:
59 if limit:
60 try:
60 try:
61 limit = int(limit)
61 limit = int(limit)
62 except ValueError:
62 except ValueError:
63 raise error.Abort(_(b'limit must be a positive integer'))
63 raise error.Abort(_(b'limit must be a positive integer'))
64 if limit <= 0:
64 if limit <= 0:
65 raise error.Abort(_(b'limit must be positive'))
65 raise error.Abort(_(b'limit must be positive'))
66 else:
66 else:
67 limit = None
67 limit = None
68 return limit
68 return limit
69
69
70
70
71 def diffordiffstat(
71 def diffordiffstat(
72 ui,
72 ui,
73 repo,
73 repo,
74 diffopts,
74 diffopts,
75 node1,
75 node1,
76 node2,
76 node2,
77 match,
77 match,
78 changes=None,
78 changes=None,
79 stat=False,
79 stat=False,
80 fp=None,
80 fp=None,
81 graphwidth=0,
81 graphwidth=0,
82 prefix=b'',
82 prefix=b'',
83 root=b'',
83 root=b'',
84 listsubrepos=False,
84 listsubrepos=False,
85 hunksfilterfn=None,
85 hunksfilterfn=None,
86 ):
86 ):
87 '''show diff or diffstat.'''
87 '''show diff or diffstat.'''
88 ctx1 = repo[node1]
88 ctx1 = repo[node1]
89 ctx2 = repo[node2]
89 ctx2 = repo[node2]
90 if root:
90 if root:
91 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
91 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
92 else:
92 else:
93 relroot = b''
93 relroot = b''
94 copysourcematch = None
94 copysourcematch = None
95
95
96 def compose(f, g):
96 def compose(f, g):
97 return lambda x: f(g(x))
97 return lambda x: f(g(x))
98
98
99 def pathfn(f):
99 def pathfn(f):
100 return posixpath.join(prefix, f)
100 return posixpath.join(prefix, f)
101
101
102 if relroot != b'':
102 if relroot != b'':
103 # XXX relative roots currently don't work if the root is within a
103 # XXX relative roots currently don't work if the root is within a
104 # subrepo
104 # subrepo
105 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
105 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
106 uirelroot = uipathfn(pathfn(relroot))
106 uirelroot = uipathfn(pathfn(relroot))
107 relroot += b'/'
107 relroot += b'/'
108 for matchroot in match.files():
108 for matchroot in match.files():
109 if not matchroot.startswith(relroot):
109 if not matchroot.startswith(relroot):
110 ui.warn(
110 ui.warn(
111 _(b'warning: %s not inside relative root %s\n')
111 _(b'warning: %s not inside relative root %s\n')
112 % (uipathfn(pathfn(matchroot)), uirelroot)
112 % (uipathfn(pathfn(matchroot)), uirelroot)
113 )
113 )
114
114
115 relrootmatch = scmutil.match(ctx2, pats=[relroot], default=b'path')
115 relrootmatch = scmutil.match(ctx2, pats=[relroot], default=b'path')
116 match = matchmod.intersectmatchers(match, relrootmatch)
116 match = matchmod.intersectmatchers(match, relrootmatch)
117 copysourcematch = relrootmatch
117 copysourcematch = relrootmatch
118
118
119 checkroot = repo.ui.configbool(
119 checkroot = repo.ui.configbool(
120 b'devel', b'all-warnings'
120 b'devel', b'all-warnings'
121 ) or repo.ui.configbool(b'devel', b'check-relroot')
121 ) or repo.ui.configbool(b'devel', b'check-relroot')
122
122
123 def relrootpathfn(f):
123 def relrootpathfn(f):
124 if checkroot and not f.startswith(relroot):
124 if checkroot and not f.startswith(relroot):
125 raise AssertionError(
125 raise AssertionError(
126 b"file %s doesn't start with relroot %s" % (f, relroot)
126 b"file %s doesn't start with relroot %s" % (f, relroot)
127 )
127 )
128 return f[len(relroot) :]
128 return f[len(relroot) :]
129
129
130 pathfn = compose(relrootpathfn, pathfn)
130 pathfn = compose(relrootpathfn, pathfn)
131
131
132 if stat:
132 if stat:
133 diffopts = diffopts.copy(context=0, noprefix=False)
133 diffopts = diffopts.copy(context=0, noprefix=False)
134 width = 80
134 width = 80
135 if not ui.plain():
135 if not ui.plain():
136 width = ui.termwidth() - graphwidth
136 width = ui.termwidth() - graphwidth
137 # If an explicit --root was given, don't respect ui.relative-paths
137 # If an explicit --root was given, don't respect ui.relative-paths
138 if not relroot:
138 if not relroot:
139 pathfn = compose(scmutil.getuipathfn(repo), pathfn)
139 pathfn = compose(scmutil.getuipathfn(repo), pathfn)
140
140
141 chunks = ctx2.diff(
141 chunks = ctx2.diff(
142 ctx1,
142 ctx1,
143 match,
143 match,
144 changes,
144 changes,
145 opts=diffopts,
145 opts=diffopts,
146 pathfn=pathfn,
146 pathfn=pathfn,
147 copysourcematch=copysourcematch,
147 copysourcematch=copysourcematch,
148 hunksfilterfn=hunksfilterfn,
148 hunksfilterfn=hunksfilterfn,
149 )
149 )
150
150
151 if fp is not None or ui.canwritewithoutlabels():
151 if fp is not None or ui.canwritewithoutlabels():
152 out = fp or ui
152 out = fp or ui
153 if stat:
153 if stat:
154 chunks = [patch.diffstat(util.iterlines(chunks), width=width)]
154 chunks = [patch.diffstat(util.iterlines(chunks), width=width)]
155 for chunk in util.filechunkiter(util.chunkbuffer(chunks)):
155 for chunk in util.filechunkiter(util.chunkbuffer(chunks)):
156 out.write(chunk)
156 out.write(chunk)
157 else:
157 else:
158 if stat:
158 if stat:
159 chunks = patch.diffstatui(util.iterlines(chunks), width=width)
159 chunks = patch.diffstatui(util.iterlines(chunks), width=width)
160 else:
160 else:
161 chunks = patch.difflabel(
161 chunks = patch.difflabel(
162 lambda chunks, **kwargs: chunks, chunks, opts=diffopts
162 lambda chunks, **kwargs: chunks, chunks, opts=diffopts
163 )
163 )
164 if ui.canbatchlabeledwrites():
164 if ui.canbatchlabeledwrites():
165
165
166 def gen():
166 def gen():
167 for chunk, label in chunks:
167 for chunk, label in chunks:
168 yield ui.label(chunk, label=label)
168 yield ui.label(chunk, label=label)
169
169
170 for chunk in util.filechunkiter(util.chunkbuffer(gen())):
170 for chunk in util.filechunkiter(util.chunkbuffer(gen())):
171 ui.write(chunk)
171 ui.write(chunk)
172 else:
172 else:
173 for chunk, label in chunks:
173 for chunk, label in chunks:
174 ui.write(chunk, label=label)
174 ui.write(chunk, label=label)
175
175
176 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
176 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
177 tempnode2 = node2
177 tempnode2 = node2
178 try:
178 try:
179 if node2 is not None:
179 if node2 is not None:
180 tempnode2 = ctx2.substate[subpath][1]
180 tempnode2 = ctx2.substate[subpath][1]
181 except KeyError:
181 except KeyError:
182 # A subrepo that existed in node1 was deleted between node1 and
182 # A subrepo that existed in node1 was deleted between node1 and
183 # node2 (inclusive). Thus, ctx2's substate won't contain that
183 # node2 (inclusive). Thus, ctx2's substate won't contain that
184 # subpath. The best we can do is to ignore it.
184 # subpath. The best we can do is to ignore it.
185 tempnode2 = None
185 tempnode2 = None
186 submatch = matchmod.subdirmatcher(subpath, match)
186 submatch = matchmod.subdirmatcher(subpath, match)
187 subprefix = repo.wvfs.reljoin(prefix, subpath)
187 subprefix = repo.wvfs.reljoin(prefix, subpath)
188 if listsubrepos or match.exact(subpath) or any(submatch.files()):
188 if listsubrepos or match.exact(subpath) or any(submatch.files()):
189 sub.diff(
189 sub.diff(
190 ui,
190 ui,
191 diffopts,
191 diffopts,
192 tempnode2,
192 tempnode2,
193 submatch,
193 submatch,
194 changes=changes,
194 changes=changes,
195 stat=stat,
195 stat=stat,
196 fp=fp,
196 fp=fp,
197 prefix=subprefix,
197 prefix=subprefix,
198 )
198 )
199
199
200
200
201 class changesetdiffer(object):
201 class changesetdiffer(object):
202 """Generate diff of changeset with pre-configured filtering functions"""
202 """Generate diff of changeset with pre-configured filtering functions"""
203
203
204 def _makefilematcher(self, ctx):
204 def _makefilematcher(self, ctx):
205 return scmutil.matchall(ctx.repo())
205 return scmutil.matchall(ctx.repo())
206
206
207 def _makehunksfilter(self, ctx):
207 def _makehunksfilter(self, ctx):
208 return None
208 return None
209
209
210 def showdiff(self, ui, ctx, diffopts, graphwidth=0, stat=False):
210 def showdiff(self, ui, ctx, diffopts, graphwidth=0, stat=False):
211 repo = ctx.repo()
211 repo = ctx.repo()
212 node = ctx.node()
212 node = ctx.node()
213 prev = ctx.p1().node()
213 prev = ctx.p1().node()
214 diffordiffstat(
214 diffordiffstat(
215 ui,
215 ui,
216 repo,
216 repo,
217 diffopts,
217 diffopts,
218 prev,
218 prev,
219 node,
219 node,
220 match=self._makefilematcher(ctx),
220 match=self._makefilematcher(ctx),
221 stat=stat,
221 stat=stat,
222 graphwidth=graphwidth,
222 graphwidth=graphwidth,
223 hunksfilterfn=self._makehunksfilter(ctx),
223 hunksfilterfn=self._makehunksfilter(ctx),
224 )
224 )
225
225
226
226
227 def changesetlabels(ctx):
227 def changesetlabels(ctx):
228 labels = [b'log.changeset', b'changeset.%s' % ctx.phasestr()]
228 labels = [b'log.changeset', b'changeset.%s' % ctx.phasestr()]
229 if ctx.obsolete():
229 if ctx.obsolete():
230 labels.append(b'changeset.obsolete')
230 labels.append(b'changeset.obsolete')
231 if ctx.isunstable():
231 if ctx.isunstable():
232 labels.append(b'changeset.unstable')
232 labels.append(b'changeset.unstable')
233 for instability in ctx.instabilities():
233 for instability in ctx.instabilities():
234 labels.append(b'instability.%s' % instability)
234 labels.append(b'instability.%s' % instability)
235 return b' '.join(labels)
235 return b' '.join(labels)
236
236
237
237
238 class changesetprinter(object):
238 class changesetprinter(object):
239 '''show changeset information when templating not requested.'''
239 '''show changeset information when templating not requested.'''
240
240
241 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
241 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
242 self.ui = ui
242 self.ui = ui
243 self.repo = repo
243 self.repo = repo
244 self.buffered = buffered
244 self.buffered = buffered
245 self._differ = differ or changesetdiffer()
245 self._differ = differ or changesetdiffer()
246 self._diffopts = patch.diffallopts(ui, diffopts)
246 self._diffopts = patch.diffallopts(ui, diffopts)
247 self._includestat = diffopts and diffopts.get(b'stat')
247 self._includestat = diffopts and diffopts.get(b'stat')
248 self._includediff = diffopts and diffopts.get(b'patch')
248 self._includediff = diffopts and diffopts.get(b'patch')
249 self.header = {}
249 self.header = {}
250 self.hunk = {}
250 self.hunk = {}
251 self.lastheader = None
251 self.lastheader = None
252 self.footer = None
252 self.footer = None
253 self._columns = templatekw.getlogcolumns()
253 self._columns = templatekw.getlogcolumns()
254
254
255 def flush(self, ctx):
255 def flush(self, ctx):
256 rev = ctx.rev()
256 rev = ctx.rev()
257 if rev in self.header:
257 if rev in self.header:
258 h = self.header[rev]
258 h = self.header[rev]
259 if h != self.lastheader:
259 if h != self.lastheader:
260 self.lastheader = h
260 self.lastheader = h
261 self.ui.write(h)
261 self.ui.write(h)
262 del self.header[rev]
262 del self.header[rev]
263 if rev in self.hunk:
263 if rev in self.hunk:
264 self.ui.write(self.hunk[rev])
264 self.ui.write(self.hunk[rev])
265 del self.hunk[rev]
265 del self.hunk[rev]
266
266
267 def close(self):
267 def close(self):
268 if self.footer:
268 if self.footer:
269 self.ui.write(self.footer)
269 self.ui.write(self.footer)
270
270
271 def show(self, ctx, copies=None, **props):
271 def show(self, ctx, copies=None, **props):
272 props = pycompat.byteskwargs(props)
272 props = pycompat.byteskwargs(props)
273 if self.buffered:
273 if self.buffered:
274 self.ui.pushbuffer(labeled=True)
274 self.ui.pushbuffer(labeled=True)
275 self._show(ctx, copies, props)
275 self._show(ctx, copies, props)
276 self.hunk[ctx.rev()] = self.ui.popbuffer()
276 self.hunk[ctx.rev()] = self.ui.popbuffer()
277 else:
277 else:
278 self._show(ctx, copies, props)
278 self._show(ctx, copies, props)
279
279
280 def _show(self, ctx, copies, props):
280 def _show(self, ctx, copies, props):
281 '''show a single changeset or file revision'''
281 '''show a single changeset or file revision'''
282 changenode = ctx.node()
282 changenode = ctx.node()
283 graphwidth = props.get(b'graphwidth', 0)
283 graphwidth = props.get(b'graphwidth', 0)
284
284
285 if self.ui.quiet:
285 if self.ui.quiet:
286 self.ui.write(
286 self.ui.write(
287 b"%s\n" % scmutil.formatchangeid(ctx), label=b'log.node'
287 b"%s\n" % scmutil.formatchangeid(ctx), label=b'log.node'
288 )
288 )
289 return
289 return
290
290
291 columns = self._columns
291 columns = self._columns
292 self.ui.write(
292 self.ui.write(
293 columns[b'changeset'] % scmutil.formatchangeid(ctx),
293 columns[b'changeset'] % scmutil.formatchangeid(ctx),
294 label=changesetlabels(ctx),
294 label=changesetlabels(ctx),
295 )
295 )
296
296
297 # branches are shown first before any other names due to backwards
297 # branches are shown first before any other names due to backwards
298 # compatibility
298 # compatibility
299 branch = ctx.branch()
299 branch = ctx.branch()
300 # don't show the default branch name
300 # don't show the default branch name
301 if branch != b'default':
301 if branch != b'default':
302 self.ui.write(columns[b'branch'] % branch, label=b'log.branch')
302 self.ui.write(columns[b'branch'] % branch, label=b'log.branch')
303
303
304 for nsname, ns in pycompat.iteritems(self.repo.names):
304 for nsname, ns in pycompat.iteritems(self.repo.names):
305 # branches has special logic already handled above, so here we just
305 # branches has special logic already handled above, so here we just
306 # skip it
306 # skip it
307 if nsname == b'branches':
307 if nsname == b'branches':
308 continue
308 continue
309 # we will use the templatename as the color name since those two
309 # we will use the templatename as the color name since those two
310 # should be the same
310 # should be the same
311 for name in ns.names(self.repo, changenode):
311 for name in ns.names(self.repo, changenode):
312 self.ui.write(ns.logfmt % name, label=b'log.%s' % ns.colorname)
312 self.ui.write(ns.logfmt % name, label=b'log.%s' % ns.colorname)
313 if self.ui.debugflag:
313 if self.ui.debugflag:
314 self.ui.write(
314 self.ui.write(
315 columns[b'phase'] % ctx.phasestr(), label=b'log.phase'
315 columns[b'phase'] % ctx.phasestr(), label=b'log.phase'
316 )
316 )
317 for pctx in scmutil.meaningfulparents(self.repo, ctx):
317 for pctx in scmutil.meaningfulparents(self.repo, ctx):
318 label = b'log.parent changeset.%s' % pctx.phasestr()
318 label = b'log.parent changeset.%s' % pctx.phasestr()
319 self.ui.write(
319 self.ui.write(
320 columns[b'parent'] % scmutil.formatchangeid(pctx), label=label
320 columns[b'parent'] % scmutil.formatchangeid(pctx), label=label
321 )
321 )
322
322
323 if self.ui.debugflag:
323 if self.ui.debugflag:
324 mnode = ctx.manifestnode()
324 mnode = ctx.manifestnode()
325 if mnode is None:
325 if mnode is None:
326 mnode = wdirid
326 mnode = wdirid
327 mrev = wdirrev
327 mrev = wdirrev
328 else:
328 else:
329 mrev = self.repo.manifestlog.rev(mnode)
329 mrev = self.repo.manifestlog.rev(mnode)
330 self.ui.write(
330 self.ui.write(
331 columns[b'manifest']
331 columns[b'manifest']
332 % scmutil.formatrevnode(self.ui, mrev, mnode),
332 % scmutil.formatrevnode(self.ui, mrev, mnode),
333 label=b'ui.debug log.manifest',
333 label=b'ui.debug log.manifest',
334 )
334 )
335 self.ui.write(columns[b'user'] % ctx.user(), label=b'log.user')
335 self.ui.write(columns[b'user'] % ctx.user(), label=b'log.user')
336 self.ui.write(
336 self.ui.write(
337 columns[b'date'] % dateutil.datestr(ctx.date()), label=b'log.date'
337 columns[b'date'] % dateutil.datestr(ctx.date()), label=b'log.date'
338 )
338 )
339
339
340 if ctx.isunstable():
340 if ctx.isunstable():
341 instabilities = ctx.instabilities()
341 instabilities = ctx.instabilities()
342 self.ui.write(
342 self.ui.write(
343 columns[b'instability'] % b', '.join(instabilities),
343 columns[b'instability'] % b', '.join(instabilities),
344 label=b'log.instability',
344 label=b'log.instability',
345 )
345 )
346
346
347 elif ctx.obsolete():
347 elif ctx.obsolete():
348 self._showobsfate(ctx)
348 self._showobsfate(ctx)
349
349
350 self._exthook(ctx)
350 self._exthook(ctx)
351
351
352 if self.ui.debugflag:
352 if self.ui.debugflag:
353 files = ctx.p1().status(ctx)
353 files = ctx.p1().status(ctx)
354 for key, value in zip(
354 for key, value in zip(
355 [b'files', b'files+', b'files-'],
355 [b'files', b'files+', b'files-'],
356 [files.modified, files.added, files.removed],
356 [files.modified, files.added, files.removed],
357 ):
357 ):
358 if value:
358 if value:
359 self.ui.write(
359 self.ui.write(
360 columns[key] % b" ".join(value),
360 columns[key] % b" ".join(value),
361 label=b'ui.debug log.files',
361 label=b'ui.debug log.files',
362 )
362 )
363 elif ctx.files() and self.ui.verbose:
363 elif ctx.files() and self.ui.verbose:
364 self.ui.write(
364 self.ui.write(
365 columns[b'files'] % b" ".join(ctx.files()),
365 columns[b'files'] % b" ".join(ctx.files()),
366 label=b'ui.note log.files',
366 label=b'ui.note log.files',
367 )
367 )
368 if copies and self.ui.verbose:
368 if copies and self.ui.verbose:
369 copies = [b'%s (%s)' % c for c in copies]
369 copies = [b'%s (%s)' % c for c in copies]
370 self.ui.write(
370 self.ui.write(
371 columns[b'copies'] % b' '.join(copies),
371 columns[b'copies'] % b' '.join(copies),
372 label=b'ui.note log.copies',
372 label=b'ui.note log.copies',
373 )
373 )
374
374
375 extra = ctx.extra()
375 extra = ctx.extra()
376 if extra and self.ui.debugflag:
376 if extra and self.ui.debugflag:
377 for key, value in sorted(extra.items()):
377 for key, value in sorted(extra.items()):
378 self.ui.write(
378 self.ui.write(
379 columns[b'extra'] % (key, stringutil.escapestr(value)),
379 columns[b'extra'] % (key, stringutil.escapestr(value)),
380 label=b'ui.debug log.extra',
380 label=b'ui.debug log.extra',
381 )
381 )
382
382
383 description = ctx.description().strip()
383 description = ctx.description().strip()
384 if description:
384 if description:
385 if self.ui.verbose:
385 if self.ui.verbose:
386 self.ui.write(
386 self.ui.write(
387 _(b"description:\n"), label=b'ui.note log.description'
387 _(b"description:\n"), label=b'ui.note log.description'
388 )
388 )
389 self.ui.write(description, label=b'ui.note log.description')
389 self.ui.write(description, label=b'ui.note log.description')
390 self.ui.write(b"\n\n")
390 self.ui.write(b"\n\n")
391 else:
391 else:
392 self.ui.write(
392 self.ui.write(
393 columns[b'summary'] % description.splitlines()[0],
393 columns[b'summary'] % description.splitlines()[0],
394 label=b'log.summary',
394 label=b'log.summary',
395 )
395 )
396 self.ui.write(b"\n")
396 self.ui.write(b"\n")
397
397
398 self._showpatch(ctx, graphwidth)
398 self._showpatch(ctx, graphwidth)
399
399
400 def _showobsfate(self, ctx):
400 def _showobsfate(self, ctx):
401 # TODO: do not depend on templater
401 # TODO: do not depend on templater
402 tres = formatter.templateresources(self.repo.ui, self.repo)
402 tres = formatter.templateresources(self.repo.ui, self.repo)
403 t = formatter.maketemplater(
403 t = formatter.maketemplater(
404 self.repo.ui,
404 self.repo.ui,
405 b'{join(obsfate, "\n")}',
405 b'{join(obsfate, "\n")}',
406 defaults=templatekw.keywords,
406 defaults=templatekw.keywords,
407 resources=tres,
407 resources=tres,
408 )
408 )
409 obsfate = t.renderdefault({b'ctx': ctx}).splitlines()
409 obsfate = t.renderdefault({b'ctx': ctx}).splitlines()
410
410
411 if obsfate:
411 if obsfate:
412 for obsfateline in obsfate:
412 for obsfateline in obsfate:
413 self.ui.write(
413 self.ui.write(
414 self._columns[b'obsolete'] % obsfateline,
414 self._columns[b'obsolete'] % obsfateline,
415 label=b'log.obsfate',
415 label=b'log.obsfate',
416 )
416 )
417
417
418 def _exthook(self, ctx):
418 def _exthook(self, ctx):
419 '''empty method used by extension as a hook point
419 '''empty method used by extension as a hook point
420 '''
420 '''
421
421
422 def _showpatch(self, ctx, graphwidth=0):
422 def _showpatch(self, ctx, graphwidth=0):
423 if self._includestat:
423 if self._includestat:
424 self._differ.showdiff(
424 self._differ.showdiff(
425 self.ui, ctx, self._diffopts, graphwidth, stat=True
425 self.ui, ctx, self._diffopts, graphwidth, stat=True
426 )
426 )
427 if self._includestat and self._includediff:
427 if self._includestat and self._includediff:
428 self.ui.write(b"\n")
428 self.ui.write(b"\n")
429 if self._includediff:
429 if self._includediff:
430 self._differ.showdiff(
430 self._differ.showdiff(
431 self.ui, ctx, self._diffopts, graphwidth, stat=False
431 self.ui, ctx, self._diffopts, graphwidth, stat=False
432 )
432 )
433 if self._includestat or self._includediff:
433 if self._includestat or self._includediff:
434 self.ui.write(b"\n")
434 self.ui.write(b"\n")
435
435
436
436
437 class changesetformatter(changesetprinter):
437 class changesetformatter(changesetprinter):
438 """Format changeset information by generic formatter"""
438 """Format changeset information by generic formatter"""
439
439
440 def __init__(
440 def __init__(
441 self, ui, repo, fm, differ=None, diffopts=None, buffered=False
441 self, ui, repo, fm, differ=None, diffopts=None, buffered=False
442 ):
442 ):
443 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
443 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
444 self._diffopts = patch.difffeatureopts(ui, diffopts, git=True)
444 self._diffopts = patch.difffeatureopts(ui, diffopts, git=True)
445 self._fm = fm
445 self._fm = fm
446
446
447 def close(self):
447 def close(self):
448 self._fm.end()
448 self._fm.end()
449
449
450 def _show(self, ctx, copies, props):
450 def _show(self, ctx, copies, props):
451 '''show a single changeset or file revision'''
451 '''show a single changeset or file revision'''
452 fm = self._fm
452 fm = self._fm
453 fm.startitem()
453 fm.startitem()
454 fm.context(ctx=ctx)
454 fm.context(ctx=ctx)
455 fm.data(rev=scmutil.intrev(ctx), node=fm.hexfunc(scmutil.binnode(ctx)))
455 fm.data(rev=scmutil.intrev(ctx), node=fm.hexfunc(scmutil.binnode(ctx)))
456
456
457 datahint = fm.datahint()
457 datahint = fm.datahint()
458 if self.ui.quiet and not datahint:
458 if self.ui.quiet and not datahint:
459 return
459 return
460
460
461 fm.data(
461 fm.data(
462 branch=ctx.branch(),
462 branch=ctx.branch(),
463 phase=ctx.phasestr(),
463 phase=ctx.phasestr(),
464 user=ctx.user(),
464 user=ctx.user(),
465 date=fm.formatdate(ctx.date()),
465 date=fm.formatdate(ctx.date()),
466 desc=ctx.description(),
466 desc=ctx.description(),
467 bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'),
467 bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'),
468 tags=fm.formatlist(ctx.tags(), name=b'tag'),
468 tags=fm.formatlist(ctx.tags(), name=b'tag'),
469 parents=fm.formatlist(
469 parents=fm.formatlist(
470 [fm.hexfunc(c.node()) for c in ctx.parents()], name=b'node'
470 [fm.hexfunc(c.node()) for c in ctx.parents()], name=b'node'
471 ),
471 ),
472 )
472 )
473
473
474 if self.ui.debugflag or b'manifest' in datahint:
474 if self.ui.debugflag or b'manifest' in datahint:
475 fm.data(manifest=fm.hexfunc(ctx.manifestnode() or wdirid))
475 fm.data(manifest=fm.hexfunc(ctx.manifestnode() or wdirid))
476 if self.ui.debugflag or b'extra' in datahint:
476 if self.ui.debugflag or b'extra' in datahint:
477 fm.data(extra=fm.formatdict(ctx.extra()))
477 fm.data(extra=fm.formatdict(ctx.extra()))
478
478
479 if (
479 if (
480 self.ui.debugflag
480 self.ui.debugflag
481 or b'modified' in datahint
481 or b'modified' in datahint
482 or b'added' in datahint
482 or b'added' in datahint
483 or b'removed' in datahint
483 or b'removed' in datahint
484 ):
484 ):
485 files = ctx.p1().status(ctx)
485 files = ctx.p1().status(ctx)
486 fm.data(
486 fm.data(
487 modified=fm.formatlist(files.modified, name=b'file'),
487 modified=fm.formatlist(files.modified, name=b'file'),
488 added=fm.formatlist(files.added, name=b'file'),
488 added=fm.formatlist(files.added, name=b'file'),
489 removed=fm.formatlist(files.removed, name=b'file'),
489 removed=fm.formatlist(files.removed, name=b'file'),
490 )
490 )
491
491
492 verbose = not self.ui.debugflag and self.ui.verbose
492 verbose = not self.ui.debugflag and self.ui.verbose
493 if verbose or b'files' in datahint:
493 if verbose or b'files' in datahint:
494 fm.data(files=fm.formatlist(ctx.files(), name=b'file'))
494 fm.data(files=fm.formatlist(ctx.files(), name=b'file'))
495 if verbose and copies or b'copies' in datahint:
495 if verbose and copies or b'copies' in datahint:
496 fm.data(
496 fm.data(
497 copies=fm.formatdict(copies or {}, key=b'name', value=b'source')
497 copies=fm.formatdict(copies or {}, key=b'name', value=b'source')
498 )
498 )
499
499
500 if self._includestat or b'diffstat' in datahint:
500 if self._includestat or b'diffstat' in datahint:
501 self.ui.pushbuffer()
501 self.ui.pushbuffer()
502 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
502 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
503 fm.data(diffstat=self.ui.popbuffer())
503 fm.data(diffstat=self.ui.popbuffer())
504 if self._includediff or b'diff' in datahint:
504 if self._includediff or b'diff' in datahint:
505 self.ui.pushbuffer()
505 self.ui.pushbuffer()
506 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
506 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
507 fm.data(diff=self.ui.popbuffer())
507 fm.data(diff=self.ui.popbuffer())
508
508
509
509
510 class changesettemplater(changesetprinter):
510 class changesettemplater(changesetprinter):
511 '''format changeset information.
511 '''format changeset information.
512
512
513 Note: there are a variety of convenience functions to build a
513 Note: there are a variety of convenience functions to build a
514 changesettemplater for common cases. See functions such as:
514 changesettemplater for common cases. See functions such as:
515 maketemplater, changesetdisplayer, buildcommittemplate, or other
515 maketemplater, changesetdisplayer, buildcommittemplate, or other
516 functions that use changesest_templater.
516 functions that use changesest_templater.
517 '''
517 '''
518
518
519 # Arguments before "buffered" used to be positional. Consider not
519 # Arguments before "buffered" used to be positional. Consider not
520 # adding/removing arguments before "buffered" to not break callers.
520 # adding/removing arguments before "buffered" to not break callers.
521 def __init__(
521 def __init__(
522 self, ui, repo, tmplspec, differ=None, diffopts=None, buffered=False
522 self, ui, repo, tmplspec, differ=None, diffopts=None, buffered=False
523 ):
523 ):
524 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
524 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
525 # tres is shared with _graphnodeformatter()
525 # tres is shared with _graphnodeformatter()
526 self._tresources = tres = formatter.templateresources(ui, repo)
526 self._tresources = tres = formatter.templateresources(ui, repo)
527 self.t = formatter.loadtemplater(
527 self.t = formatter.loadtemplater(
528 ui,
528 ui,
529 tmplspec,
529 tmplspec,
530 defaults=templatekw.keywords,
530 defaults=templatekw.keywords,
531 resources=tres,
531 resources=tres,
532 cache=templatekw.defaulttempl,
532 cache=templatekw.defaulttempl,
533 )
533 )
534 self._counter = itertools.count()
534 self._counter = itertools.count()
535
535
536 self._tref = tmplspec.ref
536 self._tref = tmplspec.ref
537 self._parts = {
537 self._parts = {
538 b'header': b'',
538 b'header': b'',
539 b'footer': b'',
539 b'footer': b'',
540 tmplspec.ref: tmplspec.ref,
540 tmplspec.ref: tmplspec.ref,
541 b'docheader': b'',
541 b'docheader': b'',
542 b'docfooter': b'',
542 b'docfooter': b'',
543 b'separator': b'',
543 b'separator': b'',
544 }
544 }
545 if tmplspec.mapfile:
545 if tmplspec.mapfile:
546 # find correct templates for current mode, for backward
546 # find correct templates for current mode, for backward
547 # compatibility with 'log -v/-q/--debug' using a mapfile
547 # compatibility with 'log -v/-q/--debug' using a mapfile
548 tmplmodes = [
548 tmplmodes = [
549 (True, b''),
549 (True, b''),
550 (self.ui.verbose, b'_verbose'),
550 (self.ui.verbose, b'_verbose'),
551 (self.ui.quiet, b'_quiet'),
551 (self.ui.quiet, b'_quiet'),
552 (self.ui.debugflag, b'_debug'),
552 (self.ui.debugflag, b'_debug'),
553 ]
553 ]
554 for mode, postfix in tmplmodes:
554 for mode, postfix in tmplmodes:
555 for t in self._parts:
555 for t in self._parts:
556 cur = t + postfix
556 cur = t + postfix
557 if mode and cur in self.t:
557 if mode and cur in self.t:
558 self._parts[t] = cur
558 self._parts[t] = cur
559 else:
559 else:
560 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
560 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
561 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
561 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
562 self._parts.update(m)
562 self._parts.update(m)
563
563
564 if self._parts[b'docheader']:
564 if self._parts[b'docheader']:
565 self.ui.write(self.t.render(self._parts[b'docheader'], {}))
565 self.ui.write(self.t.render(self._parts[b'docheader'], {}))
566
566
567 def close(self):
567 def close(self):
568 if self._parts[b'docfooter']:
568 if self._parts[b'docfooter']:
569 if not self.footer:
569 if not self.footer:
570 self.footer = b""
570 self.footer = b""
571 self.footer += self.t.render(self._parts[b'docfooter'], {})
571 self.footer += self.t.render(self._parts[b'docfooter'], {})
572 return super(changesettemplater, self).close()
572 return super(changesettemplater, self).close()
573
573
574 def _show(self, ctx, copies, props):
574 def _show(self, ctx, copies, props):
575 '''show a single changeset or file revision'''
575 '''show a single changeset or file revision'''
576 props = props.copy()
576 props = props.copy()
577 props[b'ctx'] = ctx
577 props[b'ctx'] = ctx
578 props[b'index'] = index = next(self._counter)
578 props[b'index'] = index = next(self._counter)
579 props[b'revcache'] = {b'copies': copies}
579 props[b'revcache'] = {b'copies': copies}
580 graphwidth = props.get(b'graphwidth', 0)
580 graphwidth = props.get(b'graphwidth', 0)
581
581
582 # write separator, which wouldn't work well with the header part below
582 # write separator, which wouldn't work well with the header part below
583 # since there's inherently a conflict between header (across items) and
583 # since there's inherently a conflict between header (across items) and
584 # separator (per item)
584 # separator (per item)
585 if self._parts[b'separator'] and index > 0:
585 if self._parts[b'separator'] and index > 0:
586 self.ui.write(self.t.render(self._parts[b'separator'], {}))
586 self.ui.write(self.t.render(self._parts[b'separator'], {}))
587
587
588 # write header
588 # write header
589 if self._parts[b'header']:
589 if self._parts[b'header']:
590 h = self.t.render(self._parts[b'header'], props)
590 h = self.t.render(self._parts[b'header'], props)
591 if self.buffered:
591 if self.buffered:
592 self.header[ctx.rev()] = h
592 self.header[ctx.rev()] = h
593 else:
593 else:
594 if self.lastheader != h:
594 if self.lastheader != h:
595 self.lastheader = h
595 self.lastheader = h
596 self.ui.write(h)
596 self.ui.write(h)
597
597
598 # write changeset metadata, then patch if requested
598 # write changeset metadata, then patch if requested
599 key = self._parts[self._tref]
599 key = self._parts[self._tref]
600 self.ui.write(self.t.render(key, props))
600 self.ui.write(self.t.render(key, props))
601 self._showpatch(ctx, graphwidth)
601 self._showpatch(ctx, graphwidth)
602
602
603 if self._parts[b'footer']:
603 if self._parts[b'footer']:
604 if not self.footer:
604 if not self.footer:
605 self.footer = self.t.render(self._parts[b'footer'], props)
605 self.footer = self.t.render(self._parts[b'footer'], props)
606
606
607
607
608 def templatespec(tmpl, mapfile):
608 def templatespec(tmpl, mapfile):
609 if pycompat.ispy3:
609 if pycompat.ispy3:
610 assert not isinstance(tmpl, str), b'tmpl must not be a str'
610 assert not isinstance(tmpl, str), b'tmpl must not be a str'
611 if mapfile:
611 if mapfile:
612 return formatter.templatespec(b'changeset', tmpl, mapfile)
612 return formatter.templatespec(b'changeset', tmpl, mapfile)
613 else:
613 else:
614 return formatter.templatespec(b'', tmpl, None)
614 return formatter.templatespec(b'', tmpl, None)
615
615
616
616
617 def _lookuptemplate(ui, tmpl, style):
617 def _lookuptemplate(ui, tmpl, style):
618 """Find the template matching the given template spec or style
618 """Find the template matching the given template spec or style
619
619
620 See formatter.lookuptemplate() for details.
620 See formatter.lookuptemplate() for details.
621 """
621 """
622
622
623 # ui settings
623 # ui settings
624 if not tmpl and not style: # template are stronger than style
624 if not tmpl and not style: # template are stronger than style
625 tmpl = ui.config(b'ui', b'logtemplate')
625 tmpl = ui.config(b'ui', b'logtemplate')
626 if tmpl:
626 if tmpl:
627 return templatespec(templater.unquotestring(tmpl), None)
627 return templatespec(templater.unquotestring(tmpl), None)
628 else:
628 else:
629 style = util.expandpath(ui.config(b'ui', b'style'))
629 style = util.expandpath(ui.config(b'ui', b'style'))
630
630
631 if not tmpl and style:
631 if not tmpl and style:
632 mapfile = style
632 mapfile = style
633 if not os.path.split(mapfile)[0]:
633 if not os.path.split(mapfile)[0]:
634 mapname = templater.templatepath(
634 mapname = templater.templatepath(
635 b'map-cmdline.' + mapfile
635 b'map-cmdline.' + mapfile
636 ) or templater.templatepath(mapfile)
636 ) or templater.templatepath(mapfile)
637 if mapname:
637 if mapname:
638 mapfile = mapname
638 mapfile = mapname
639 return templatespec(None, mapfile)
639 return templatespec(None, mapfile)
640
640
641 return formatter.lookuptemplate(ui, b'changeset', tmpl)
641 return formatter.lookuptemplate(ui, b'changeset', tmpl)
642
642
643
643
644 def maketemplater(ui, repo, tmpl, buffered=False):
644 def maketemplater(ui, repo, tmpl, buffered=False):
645 """Create a changesettemplater from a literal template 'tmpl'
645 """Create a changesettemplater from a literal template 'tmpl'
646 byte-string."""
646 byte-string."""
647 spec = templatespec(tmpl, None)
647 spec = templatespec(tmpl, None)
648 return changesettemplater(ui, repo, spec, buffered=buffered)
648 return changesettemplater(ui, repo, spec, buffered=buffered)
649
649
650
650
651 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False):
651 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False):
652 """show one changeset using template or regular display.
652 """show one changeset using template or regular display.
653
653
654 Display format will be the first non-empty hit of:
654 Display format will be the first non-empty hit of:
655 1. option 'template'
655 1. option 'template'
656 2. option 'style'
656 2. option 'style'
657 3. [ui] setting 'logtemplate'
657 3. [ui] setting 'logtemplate'
658 4. [ui] setting 'style'
658 4. [ui] setting 'style'
659 If all of these values are either the unset or the empty string,
659 If all of these values are either the unset or the empty string,
660 regular display via changesetprinter() is done.
660 regular display via changesetprinter() is done.
661 """
661 """
662 postargs = (differ, opts, buffered)
662 postargs = (differ, opts, buffered)
663 spec = _lookuptemplate(ui, opts.get(b'template'), opts.get(b'style'))
663 spec = _lookuptemplate(ui, opts.get(b'template'), opts.get(b'style'))
664
664
665 # machine-readable formats have slightly different keyword set than
665 # machine-readable formats have slightly different keyword set than
666 # plain templates, which are handled by changesetformatter.
666 # plain templates, which are handled by changesetformatter.
667 # note that {b'pickle', b'debug'} can also be added to the list if needed.
667 # note that {b'pickle', b'debug'} can also be added to the list if needed.
668 if spec.ref in {b'cbor', b'json'}:
668 if spec.ref in {b'cbor', b'json'}:
669 fm = ui.formatter(b'log', opts)
669 fm = ui.formatter(b'log', opts)
670 return changesetformatter(ui, repo, fm, *postargs)
670 return changesetformatter(ui, repo, fm, *postargs)
671
671
672 if not spec.ref and not spec.tmpl and not spec.mapfile:
672 if not spec.ref and not spec.tmpl and not spec.mapfile:
673 return changesetprinter(ui, repo, *postargs)
673 return changesetprinter(ui, repo, *postargs)
674
674
675 return changesettemplater(ui, repo, spec, *postargs)
675 return changesettemplater(ui, repo, spec, *postargs)
676
676
677
677
678 def _makematcher(repo, revs, pats, opts):
678 def _makematcher(repo, revs, pats, opts):
679 """Build matcher and expanded patterns from log options
679 """Build matcher and expanded patterns from log options
680
680
681 If --follow, revs are the revisions to follow from.
681 If --follow, revs are the revisions to follow from.
682
682
683 Returns (match, pats, slowpath) where
683 Returns (match, pats, slowpath) where
684 - match: a matcher built from the given pats and -I/-X opts
684 - match: a matcher built from the given pats and -I/-X opts
685 - pats: patterns used (globs are expanded on Windows)
685 - pats: patterns used (globs are expanded on Windows)
686 - slowpath: True if patterns aren't as simple as scanning filelogs
686 - slowpath: True if patterns aren't as simple as scanning filelogs
687 """
687 """
688 # pats/include/exclude are passed to match.match() directly in
688 # pats/include/exclude are passed to match.match() directly in
689 # _matchfiles() revset but walkchangerevs() builds its matcher with
689 # _matchfiles() revset but walkchangerevs() builds its matcher with
690 # scmutil.match(). The difference is input pats are globbed on
690 # scmutil.match(). The difference is input pats are globbed on
691 # platforms without shell expansion (windows).
691 # platforms without shell expansion (windows).
692 wctx = repo[None]
692 wctx = repo[None]
693 match, pats = scmutil.matchandpats(wctx, pats, opts)
693 match, pats = scmutil.matchandpats(wctx, pats, opts)
694 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
694 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
695 if not slowpath:
695 if not slowpath:
696 follow = opts.get(b'follow') or opts.get(b'follow_first')
696 follow = opts.get(b'follow') or opts.get(b'follow_first')
697 startctxs = []
697 startctxs = []
698 if follow and opts.get(b'rev'):
698 if follow and opts.get(b'rev'):
699 startctxs = [repo[r] for r in revs]
699 startctxs = [repo[r] for r in revs]
700 for f in match.files():
700 for f in match.files():
701 if follow and startctxs:
701 if follow and startctxs:
702 # No idea if the path was a directory at that revision, so
702 # No idea if the path was a directory at that revision, so
703 # take the slow path.
703 # take the slow path.
704 if any(f not in c for c in startctxs):
704 if any(f not in c for c in startctxs):
705 slowpath = True
705 slowpath = True
706 continue
706 continue
707 elif follow and f not in wctx:
707 elif follow and f not in wctx:
708 # If the file exists, it may be a directory, so let it
708 # If the file exists, it may be a directory, so let it
709 # take the slow path.
709 # take the slow path.
710 if os.path.exists(repo.wjoin(f)):
710 if os.path.exists(repo.wjoin(f)):
711 slowpath = True
711 slowpath = True
712 continue
712 continue
713 else:
713 else:
714 raise error.Abort(
714 raise error.Abort(
715 _(
715 _(
716 b'cannot follow file not in parent '
716 b'cannot follow file not in parent '
717 b'revision: "%s"'
717 b'revision: "%s"'
718 )
718 )
719 % f
719 % f
720 )
720 )
721 filelog = repo.file(f)
721 filelog = repo.file(f)
722 if not filelog:
722 if not filelog:
723 # A zero count may be a directory or deleted file, so
723 # A zero count may be a directory or deleted file, so
724 # try to find matching entries on the slow path.
724 # try to find matching entries on the slow path.
725 if follow:
725 if follow:
726 raise error.Abort(
726 raise error.Abort(
727 _(b'cannot follow nonexistent file: "%s"') % f
727 _(b'cannot follow nonexistent file: "%s"') % f
728 )
728 )
729 slowpath = True
729 slowpath = True
730
730
731 # We decided to fall back to the slowpath because at least one
731 # We decided to fall back to the slowpath because at least one
732 # of the paths was not a file. Check to see if at least one of them
732 # of the paths was not a file. Check to see if at least one of them
733 # existed in history - in that case, we'll continue down the
733 # existed in history - in that case, we'll continue down the
734 # slowpath; otherwise, we can turn off the slowpath
734 # slowpath; otherwise, we can turn off the slowpath
735 if slowpath:
735 if slowpath:
736 for path in match.files():
736 for path in match.files():
737 if path == b'.' or path in repo.store:
737 if path == b'.' or path in repo.store:
738 break
738 break
739 else:
739 else:
740 slowpath = False
740 slowpath = False
741
741
742 return match, pats, slowpath
742 return match, pats, slowpath
743
743
744
744
745 def _fileancestors(repo, revs, match, followfirst):
745 def _fileancestors(repo, revs, match, followfirst):
746 fctxs = []
746 fctxs = []
747 for r in revs:
747 for r in revs:
748 ctx = repo[r]
748 ctx = repo[r]
749 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
749 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
750
750
751 # When displaying a revision with --patch --follow FILE, we have
751 # When displaying a revision with --patch --follow FILE, we have
752 # to know which file of the revision must be diffed. With
752 # to know which file of the revision must be diffed. With
753 # --follow, we want the names of the ancestors of FILE in the
753 # --follow, we want the names of the ancestors of FILE in the
754 # revision, stored in "fcache". "fcache" is populated as a side effect
754 # revision, stored in "fcache". "fcache" is populated as a side effect
755 # of the graph traversal.
755 # of the graph traversal.
756 fcache = {}
756 fcache = {}
757
757
758 def filematcher(ctx):
758 def filematcher(ctx):
759 return scmutil.matchfiles(repo, fcache.get(ctx.rev(), []))
759 return scmutil.matchfiles(repo, fcache.get(ctx.rev(), []))
760
760
761 def revgen():
761 def revgen():
762 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
762 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
763 fcache[rev] = [c.path() for c in cs]
763 fcache[rev] = [c.path() for c in cs]
764 yield rev
764 yield rev
765
765
766 return smartset.generatorset(revgen(), iterasc=False), filematcher
766 return smartset.generatorset(revgen(), iterasc=False), filematcher
767
767
768
768
769 def _makenofollowfilematcher(repo, pats, opts):
769 def _makenofollowfilematcher(repo, pats, opts):
770 '''hook for extensions to override the filematcher for non-follow cases'''
770 '''hook for extensions to override the filematcher for non-follow cases'''
771 return None
771 return None
772
772
773
773
774 _opt2logrevset = {
774 _opt2logrevset = {
775 b'no_merges': (b'not merge()', None),
775 b'no_merges': (b'not merge()', None),
776 b'only_merges': (b'merge()', None),
776 b'only_merges': (b'merge()', None),
777 b'_matchfiles': (None, b'_matchfiles(%ps)'),
777 b'_matchfiles': (None, b'_matchfiles(%ps)'),
778 b'date': (b'date(%s)', None),
778 b'date': (b'date(%s)', None),
779 b'branch': (b'branch(%s)', b'%lr'),
779 b'branch': (b'branch(%s)', b'%lr'),
780 b'_patslog': (b'filelog(%s)', b'%lr'),
780 b'_patslog': (b'filelog(%s)', b'%lr'),
781 b'keyword': (b'keyword(%s)', b'%lr'),
781 b'keyword': (b'keyword(%s)', b'%lr'),
782 b'prune': (b'ancestors(%s)', b'not %lr'),
782 b'prune': (b'ancestors(%s)', b'not %lr'),
783 b'user': (b'user(%s)', b'%lr'),
783 b'user': (b'user(%s)', b'%lr'),
784 }
784 }
785
785
786
786
787 def _makerevset(repo, match, pats, slowpath, opts):
787 def _makerevset(repo, match, pats, slowpath, opts):
788 """Return a revset string built from log options and file patterns"""
788 """Return a revset string built from log options and file patterns"""
789 opts = dict(opts)
789 opts = dict(opts)
790 # follow or not follow?
790 # follow or not follow?
791 follow = opts.get(b'follow') or opts.get(b'follow_first')
791 follow = opts.get(b'follow') or opts.get(b'follow_first')
792
792
793 # branch and only_branch are really aliases and must be handled at
793 # branch and only_branch are really aliases and must be handled at
794 # the same time
794 # the same time
795 opts[b'branch'] = opts.get(b'branch', []) + opts.get(b'only_branch', [])
795 opts[b'branch'] = opts.get(b'branch', []) + opts.get(b'only_branch', [])
796 opts[b'branch'] = [repo.lookupbranch(b) for b in opts[b'branch']]
796 opts[b'branch'] = [repo.lookupbranch(b) for b in opts[b'branch']]
797
797
798 if slowpath:
798 if slowpath:
799 # See walkchangerevs() slow path.
799 # See walkchangerevs() slow path.
800 #
800 #
801 # pats/include/exclude cannot be represented as separate
801 # pats/include/exclude cannot be represented as separate
802 # revset expressions as their filtering logic applies at file
802 # revset expressions as their filtering logic applies at file
803 # level. For instance "-I a -X b" matches a revision touching
803 # level. For instance "-I a -X b" matches a revision touching
804 # "a" and "b" while "file(a) and not file(b)" does
804 # "a" and "b" while "file(a) and not file(b)" does
805 # not. Besides, filesets are evaluated against the working
805 # not. Besides, filesets are evaluated against the working
806 # directory.
806 # directory.
807 matchargs = [b'r:', b'd:relpath']
807 matchargs = [b'r:', b'd:relpath']
808 for p in pats:
808 for p in pats:
809 matchargs.append(b'p:' + p)
809 matchargs.append(b'p:' + p)
810 for p in opts.get(b'include', []):
810 for p in opts.get(b'include', []):
811 matchargs.append(b'i:' + p)
811 matchargs.append(b'i:' + p)
812 for p in opts.get(b'exclude', []):
812 for p in opts.get(b'exclude', []):
813 matchargs.append(b'x:' + p)
813 matchargs.append(b'x:' + p)
814 opts[b'_matchfiles'] = matchargs
814 opts[b'_matchfiles'] = matchargs
815 elif not follow:
815 elif not follow:
816 opts[b'_patslog'] = list(pats)
816 opts[b'_patslog'] = list(pats)
817
817
818 expr = []
818 expr = []
819 for op, val in sorted(pycompat.iteritems(opts)):
819 for op, val in sorted(pycompat.iteritems(opts)):
820 if not val:
820 if not val:
821 continue
821 continue
822 if op not in _opt2logrevset:
822 if op not in _opt2logrevset:
823 continue
823 continue
824 revop, listop = _opt2logrevset[op]
824 revop, listop = _opt2logrevset[op]
825 if revop and b'%' not in revop:
825 if revop and b'%' not in revop:
826 expr.append(revop)
826 expr.append(revop)
827 elif not listop:
827 elif not listop:
828 expr.append(revsetlang.formatspec(revop, val))
828 expr.append(revsetlang.formatspec(revop, val))
829 else:
829 else:
830 if revop:
830 if revop:
831 val = [revsetlang.formatspec(revop, v) for v in val]
831 val = [revsetlang.formatspec(revop, v) for v in val]
832 expr.append(revsetlang.formatspec(listop, val))
832 expr.append(revsetlang.formatspec(listop, val))
833
833
834 if expr:
834 if expr:
835 expr = b'(' + b' and '.join(expr) + b')'
835 expr = b'(' + b' and '.join(expr) + b')'
836 else:
836 else:
837 expr = None
837 expr = None
838 return expr
838 return expr
839
839
840
840
841 def _initialrevs(repo, opts):
841 def _initialrevs(repo, opts):
842 """Return the initial set of revisions to be filtered or followed"""
842 """Return the initial set of revisions to be filtered or followed"""
843 follow = opts.get(b'follow') or opts.get(b'follow_first')
843 follow = opts.get(b'follow') or opts.get(b'follow_first')
844 if opts.get(b'rev'):
844 if opts.get(b'rev'):
845 revs = scmutil.revrange(repo, opts[b'rev'])
845 revs = scmutil.revrange(repo, opts[b'rev'])
846 elif follow and repo.dirstate.p1() == nullid:
846 elif follow and repo.dirstate.p1() == nullid:
847 revs = smartset.baseset()
847 revs = smartset.baseset()
848 elif follow:
848 elif follow:
849 revs = repo.revs(b'.')
849 revs = repo.revs(b'.')
850 else:
850 else:
851 revs = smartset.spanset(repo)
851 revs = smartset.spanset(repo)
852 revs.reverse()
852 revs.reverse()
853 return revs
853 return revs
854
854
855
855
856 def getrevs(repo, pats, opts):
856 def getrevs(repo, pats, opts):
857 # type: (Any, Any, Any) -> Tuple[smartset.abstractsmartset, Optional[changesetdiffer]]
857 # type: (Any, Any, Any) -> Tuple[smartset.abstractsmartset, Optional[changesetdiffer]]
858 """Return (revs, differ) where revs is a smartset
858 """Return (revs, differ) where revs is a smartset
859
859
860 differ is a changesetdiffer with pre-configured file matcher.
860 differ is a changesetdiffer with pre-configured file matcher.
861 """
861 """
862 follow = opts.get(b'follow') or opts.get(b'follow_first')
862 follow = opts.get(b'follow') or opts.get(b'follow_first')
863 followfirst = opts.get(b'follow_first')
863 followfirst = opts.get(b'follow_first')
864 limit = getlimit(opts)
864 limit = getlimit(opts)
865 revs = _initialrevs(repo, opts)
865 revs = _initialrevs(repo, opts)
866 if not revs:
866 if not revs:
867 return smartset.baseset(), None
867 return smartset.baseset(), None
868 match, pats, slowpath = _makematcher(repo, revs, pats, opts)
868 match, pats, slowpath = _makematcher(repo, revs, pats, opts)
869 filematcher = None
869 filematcher = None
870 if follow:
870 if follow:
871 if slowpath or match.always():
871 if slowpath or match.always():
872 revs = dagop.revancestors(repo, revs, followfirst=followfirst)
872 revs = dagop.revancestors(repo, revs, followfirst=followfirst)
873 else:
873 else:
874 revs, filematcher = _fileancestors(repo, revs, match, followfirst)
874 revs, filematcher = _fileancestors(repo, revs, match, followfirst)
875 revs.reverse()
875 revs.reverse()
876 if filematcher is None:
876 if filematcher is None:
877 filematcher = _makenofollowfilematcher(repo, pats, opts)
877 filematcher = _makenofollowfilematcher(repo, pats, opts)
878 if filematcher is None:
878 if filematcher is None:
879
879
880 def filematcher(ctx):
880 def filematcher(ctx):
881 return match
881 return match
882
882
883 expr = _makerevset(repo, match, pats, slowpath, opts)
883 expr = _makerevset(repo, match, pats, slowpath, opts)
884 if opts.get(b'graph'):
884 if opts.get(b'graph'):
885 # User-specified revs might be unsorted, but don't sort before
885 # User-specified revs might be unsorted, but don't sort before
886 # _makerevset because it might depend on the order of revs
886 # _makerevset because it might depend on the order of revs
887 if repo.ui.configbool(b'experimental', b'log.topo'):
887 if repo.ui.configbool(b'experimental', b'log.topo'):
888 if not revs.istopo():
888 if not revs.istopo():
889 revs = dagop.toposort(revs, repo.changelog.parentrevs)
889 revs = dagop.toposort(revs, repo.changelog.parentrevs)
890 # TODO: try to iterate the set lazily
890 # TODO: try to iterate the set lazily
891 revs = revset.baseset(list(revs), istopo=True)
891 revs = revset.baseset(list(revs), istopo=True)
892 elif not (revs.isdescending() or revs.istopo()):
892 elif not (revs.isdescending() or revs.istopo()):
893 revs.sort(reverse=True)
893 revs.sort(reverse=True)
894 if expr:
894 if expr:
895 matcher = revset.match(None, expr)
895 matcher = revset.match(None, expr)
896 revs = matcher(repo, revs)
896 revs = matcher(repo, revs)
897 if limit is not None:
897 if limit is not None:
898 revs = revs.slice(0, limit)
898 revs = revs.slice(0, limit)
899
899
900 differ = changesetdiffer()
900 differ = changesetdiffer()
901 differ._makefilematcher = filematcher
901 differ._makefilematcher = filematcher
902 return revs, differ
902 return revs, differ
903
903
904
904
905 def _parselinerangeopt(repo, opts):
905 def _parselinerangeopt(repo, opts):
906 """Parse --line-range log option and return a list of tuples (filename,
906 """Parse --line-range log option and return a list of tuples (filename,
907 (fromline, toline)).
907 (fromline, toline)).
908 """
908 """
909 linerangebyfname = []
909 linerangebyfname = []
910 for pat in opts.get(b'line_range', []):
910 for pat in opts.get(b'line_range', []):
911 try:
911 try:
912 pat, linerange = pat.rsplit(b',', 1)
912 pat, linerange = pat.rsplit(b',', 1)
913 except ValueError:
913 except ValueError:
914 raise error.Abort(_(b'malformatted line-range pattern %s') % pat)
914 raise error.Abort(_(b'malformatted line-range pattern %s') % pat)
915 try:
915 try:
916 fromline, toline = map(int, linerange.split(b':'))
916 fromline, toline = map(int, linerange.split(b':'))
917 except ValueError:
917 except ValueError:
918 raise error.Abort(_(b"invalid line range for %s") % pat)
918 raise error.Abort(_(b"invalid line range for %s") % pat)
919 msg = _(b"line range pattern '%s' must match exactly one file") % pat
919 msg = _(b"line range pattern '%s' must match exactly one file") % pat
920 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
920 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
921 linerangebyfname.append(
921 linerangebyfname.append(
922 (fname, util.processlinerange(fromline, toline))
922 (fname, util.processlinerange(fromline, toline))
923 )
923 )
924 return linerangebyfname
924 return linerangebyfname
925
925
926
926
927 def getlinerangerevs(repo, userrevs, opts):
927 def getlinerangerevs(repo, userrevs, opts):
928 """Return (revs, differ).
928 """Return (revs, differ).
929
929
930 "revs" are revisions obtained by processing "line-range" log options and
930 "revs" are revisions obtained by processing "line-range" log options and
931 walking block ancestors of each specified file/line-range.
931 walking block ancestors of each specified file/line-range.
932
932
933 "differ" is a changesetdiffer with pre-configured file matcher and hunks
933 "differ" is a changesetdiffer with pre-configured file matcher and hunks
934 filter.
934 filter.
935 """
935 """
936 wctx = repo[None]
936 wctx = repo[None]
937
937
938 # Two-levels map of "rev -> file ctx -> [line range]".
938 # Two-levels map of "rev -> file ctx -> [line range]".
939 linerangesbyrev = {}
939 linerangesbyrev = {}
940 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
940 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
941 if fname not in wctx:
941 if fname not in wctx:
942 raise error.Abort(
942 raise error.Abort(
943 _(b'cannot follow file not in parent revision: "%s"') % fname
943 _(b'cannot follow file not in parent revision: "%s"') % fname
944 )
944 )
945 fctx = wctx.filectx(fname)
945 fctx = wctx.filectx(fname)
946 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
946 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
947 rev = fctx.introrev()
947 rev = fctx.introrev()
948 if rev is None:
949 rev = wdirrev
948 if rev not in userrevs:
950 if rev not in userrevs:
949 continue
951 continue
950 linerangesbyrev.setdefault(rev, {}).setdefault(
952 linerangesbyrev.setdefault(rev, {}).setdefault(
951 fctx.path(), []
953 fctx.path(), []
952 ).append(linerange)
954 ).append(linerange)
953
955
954 def nofilterhunksfn(fctx, hunks):
956 def nofilterhunksfn(fctx, hunks):
955 return hunks
957 return hunks
956
958
957 def hunksfilter(ctx):
959 def hunksfilter(ctx):
958 fctxlineranges = linerangesbyrev.get(ctx.rev())
960 fctxlineranges = linerangesbyrev.get(scmutil.intrev(ctx))
959 if fctxlineranges is None:
961 if fctxlineranges is None:
960 return nofilterhunksfn
962 return nofilterhunksfn
961
963
962 def filterfn(fctx, hunks):
964 def filterfn(fctx, hunks):
963 lineranges = fctxlineranges.get(fctx.path())
965 lineranges = fctxlineranges.get(fctx.path())
964 if lineranges is not None:
966 if lineranges is not None:
965 for hr, lines in hunks:
967 for hr, lines in hunks:
966 if hr is None: # binary
968 if hr is None: # binary
967 yield hr, lines
969 yield hr, lines
968 continue
970 continue
969 if any(mdiff.hunkinrange(hr[2:], lr) for lr in lineranges):
971 if any(mdiff.hunkinrange(hr[2:], lr) for lr in lineranges):
970 yield hr, lines
972 yield hr, lines
971 else:
973 else:
972 for hunk in hunks:
974 for hunk in hunks:
973 yield hunk
975 yield hunk
974
976
975 return filterfn
977 return filterfn
976
978
977 def filematcher(ctx):
979 def filematcher(ctx):
978 files = list(linerangesbyrev.get(ctx.rev(), []))
980 files = list(linerangesbyrev.get(scmutil.intrev(ctx), []))
979 return scmutil.matchfiles(repo, files)
981 return scmutil.matchfiles(repo, files)
980
982
981 revs = sorted(linerangesbyrev, reverse=True)
983 revs = sorted(linerangesbyrev, reverse=True)
982
984
983 differ = changesetdiffer()
985 differ = changesetdiffer()
984 differ._makefilematcher = filematcher
986 differ._makefilematcher = filematcher
985 differ._makehunksfilter = hunksfilter
987 differ._makehunksfilter = hunksfilter
986 return smartset.baseset(revs), differ
988 return smartset.baseset(revs), differ
987
989
988
990
989 def _graphnodeformatter(ui, displayer):
991 def _graphnodeformatter(ui, displayer):
990 spec = ui.config(b'ui', b'graphnodetemplate')
992 spec = ui.config(b'ui', b'graphnodetemplate')
991 if not spec:
993 if not spec:
992 return templatekw.getgraphnode # fast path for "{graphnode}"
994 return templatekw.getgraphnode # fast path for "{graphnode}"
993
995
994 spec = templater.unquotestring(spec)
996 spec = templater.unquotestring(spec)
995 if isinstance(displayer, changesettemplater):
997 if isinstance(displayer, changesettemplater):
996 # reuse cache of slow templates
998 # reuse cache of slow templates
997 tres = displayer._tresources
999 tres = displayer._tresources
998 else:
1000 else:
999 tres = formatter.templateresources(ui)
1001 tres = formatter.templateresources(ui)
1000 templ = formatter.maketemplater(
1002 templ = formatter.maketemplater(
1001 ui, spec, defaults=templatekw.keywords, resources=tres
1003 ui, spec, defaults=templatekw.keywords, resources=tres
1002 )
1004 )
1003
1005
1004 def formatnode(repo, ctx):
1006 def formatnode(repo, ctx):
1005 props = {b'ctx': ctx, b'repo': repo}
1007 props = {b'ctx': ctx, b'repo': repo}
1006 return templ.renderdefault(props)
1008 return templ.renderdefault(props)
1007
1009
1008 return formatnode
1010 return formatnode
1009
1011
1010
1012
1011 def displaygraph(ui, repo, dag, displayer, edgefn, getcopies=None, props=None):
1013 def displaygraph(ui, repo, dag, displayer, edgefn, getcopies=None, props=None):
1012 props = props or {}
1014 props = props or {}
1013 formatnode = _graphnodeformatter(ui, displayer)
1015 formatnode = _graphnodeformatter(ui, displayer)
1014 state = graphmod.asciistate()
1016 state = graphmod.asciistate()
1015 styles = state.styles
1017 styles = state.styles
1016
1018
1017 # only set graph styling if HGPLAIN is not set.
1019 # only set graph styling if HGPLAIN is not set.
1018 if ui.plain(b'graph'):
1020 if ui.plain(b'graph'):
1019 # set all edge styles to |, the default pre-3.8 behaviour
1021 # set all edge styles to |, the default pre-3.8 behaviour
1020 styles.update(dict.fromkeys(styles, b'|'))
1022 styles.update(dict.fromkeys(styles, b'|'))
1021 else:
1023 else:
1022 edgetypes = {
1024 edgetypes = {
1023 b'parent': graphmod.PARENT,
1025 b'parent': graphmod.PARENT,
1024 b'grandparent': graphmod.GRANDPARENT,
1026 b'grandparent': graphmod.GRANDPARENT,
1025 b'missing': graphmod.MISSINGPARENT,
1027 b'missing': graphmod.MISSINGPARENT,
1026 }
1028 }
1027 for name, key in edgetypes.items():
1029 for name, key in edgetypes.items():
1028 # experimental config: experimental.graphstyle.*
1030 # experimental config: experimental.graphstyle.*
1029 styles[key] = ui.config(
1031 styles[key] = ui.config(
1030 b'experimental', b'graphstyle.%s' % name, styles[key]
1032 b'experimental', b'graphstyle.%s' % name, styles[key]
1031 )
1033 )
1032 if not styles[key]:
1034 if not styles[key]:
1033 styles[key] = None
1035 styles[key] = None
1034
1036
1035 # experimental config: experimental.graphshorten
1037 # experimental config: experimental.graphshorten
1036 state.graphshorten = ui.configbool(b'experimental', b'graphshorten')
1038 state.graphshorten = ui.configbool(b'experimental', b'graphshorten')
1037
1039
1038 for rev, type, ctx, parents in dag:
1040 for rev, type, ctx, parents in dag:
1039 char = formatnode(repo, ctx)
1041 char = formatnode(repo, ctx)
1040 copies = getcopies(ctx) if getcopies else None
1042 copies = getcopies(ctx) if getcopies else None
1041 edges = edgefn(type, char, state, rev, parents)
1043 edges = edgefn(type, char, state, rev, parents)
1042 firstedge = next(edges)
1044 firstedge = next(edges)
1043 width = firstedge[2]
1045 width = firstedge[2]
1044 displayer.show(
1046 displayer.show(
1045 ctx, copies=copies, graphwidth=width, **pycompat.strkwargs(props)
1047 ctx, copies=copies, graphwidth=width, **pycompat.strkwargs(props)
1046 )
1048 )
1047 lines = displayer.hunk.pop(rev).split(b'\n')
1049 lines = displayer.hunk.pop(rev).split(b'\n')
1048 if not lines[-1]:
1050 if not lines[-1]:
1049 del lines[-1]
1051 del lines[-1]
1050 displayer.flush(ctx)
1052 displayer.flush(ctx)
1051 for type, char, width, coldata in itertools.chain([firstedge], edges):
1053 for type, char, width, coldata in itertools.chain([firstedge], edges):
1052 graphmod.ascii(ui, state, type, char, lines, coldata)
1054 graphmod.ascii(ui, state, type, char, lines, coldata)
1053 lines = []
1055 lines = []
1054 displayer.close()
1056 displayer.close()
1055
1057
1056
1058
1057 def displaygraphrevs(ui, repo, revs, displayer, getrenamed):
1059 def displaygraphrevs(ui, repo, revs, displayer, getrenamed):
1058 revdag = graphmod.dagwalker(repo, revs)
1060 revdag = graphmod.dagwalker(repo, revs)
1059 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
1061 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
1060
1062
1061
1063
1062 def displayrevs(ui, repo, revs, displayer, getcopies):
1064 def displayrevs(ui, repo, revs, displayer, getcopies):
1063 for rev in revs:
1065 for rev in revs:
1064 ctx = repo[rev]
1066 ctx = repo[rev]
1065 copies = getcopies(ctx) if getcopies else None
1067 copies = getcopies(ctx) if getcopies else None
1066 displayer.show(ctx, copies=copies)
1068 displayer.show(ctx, copies=copies)
1067 displayer.flush(ctx)
1069 displayer.flush(ctx)
1068 displayer.close()
1070 displayer.close()
1069
1071
1070
1072
1071 def checkunsupportedgraphflags(pats, opts):
1073 def checkunsupportedgraphflags(pats, opts):
1072 for op in [b"newest_first"]:
1074 for op in [b"newest_first"]:
1073 if op in opts and opts[op]:
1075 if op in opts and opts[op]:
1074 raise error.Abort(
1076 raise error.Abort(
1075 _(b"-G/--graph option is incompatible with --%s")
1077 _(b"-G/--graph option is incompatible with --%s")
1076 % op.replace(b"_", b"-")
1078 % op.replace(b"_", b"-")
1077 )
1079 )
1078
1080
1079
1081
1080 def graphrevs(repo, nodes, opts):
1082 def graphrevs(repo, nodes, opts):
1081 limit = getlimit(opts)
1083 limit = getlimit(opts)
1082 nodes.reverse()
1084 nodes.reverse()
1083 if limit is not None:
1085 if limit is not None:
1084 nodes = nodes[:limit]
1086 nodes = nodes[:limit]
1085 return graphmod.nodes(repo, nodes)
1087 return graphmod.nodes(repo, nodes)
@@ -1,228 +1,228 b''
1 # pvec.py - probabilistic vector clocks for Mercurial
1 # pvec.py - probabilistic vector clocks for Mercurial
2 #
2 #
3 # Copyright 2012 Matt Mackall <mpm@selenic.com>
3 # Copyright 2012 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''
8 '''
9 A "pvec" is a changeset property based on the theory of vector clocks
9 A "pvec" is a changeset property based on the theory of vector clocks
10 that can be compared to discover relatedness without consulting a
10 that can be compared to discover relatedness without consulting a
11 graph. This can be useful for tasks like determining how a
11 graph. This can be useful for tasks like determining how a
12 disconnected patch relates to a repository.
12 disconnected patch relates to a repository.
13
13
14 Currently a pvec consist of 448 bits, of which 24 are 'depth' and the
14 Currently a pvec consist of 448 bits, of which 24 are 'depth' and the
15 remainder are a bit vector. It is represented as a 70-character base85
15 remainder are a bit vector. It is represented as a 70-character base85
16 string.
16 string.
17
17
18 Construction:
18 Construction:
19
19
20 - a root changeset has a depth of 0 and a bit vector based on its hash
20 - a root changeset has a depth of 0 and a bit vector based on its hash
21 - a normal commit has a changeset where depth is increased by one and
21 - a normal commit has a changeset where depth is increased by one and
22 one bit vector bit is flipped based on its hash
22 one bit vector bit is flipped based on its hash
23 - a merge changeset pvec is constructed by copying changes from one pvec into
23 - a merge changeset pvec is constructed by copying changes from one pvec into
24 the other to balance its depth
24 the other to balance its depth
25
25
26 Properties:
26 Properties:
27
27
28 - for linear changes, difference in depth is always <= hamming distance
28 - for linear changes, difference in depth is always <= hamming distance
29 - otherwise, changes are probably divergent
29 - otherwise, changes are probably divergent
30 - when hamming distance is < 200, we can reliably detect when pvecs are near
30 - when hamming distance is < 200, we can reliably detect when pvecs are near
31
31
32 Issues:
32 Issues:
33
33
34 - hamming distance ceases to work over distances of ~ 200
34 - hamming distance ceases to work over distances of ~ 200
35 - detecting divergence is less accurate when the common ancestor is very close
35 - detecting divergence is less accurate when the common ancestor is very close
36 to either revision or total distance is high
36 to either revision or total distance is high
37 - this could probably be improved by modeling the relation between
37 - this could probably be improved by modeling the relation between
38 delta and hdist
38 delta and hdist
39
39
40 Uses:
40 Uses:
41
41
42 - a patch pvec can be used to locate the nearest available common ancestor for
42 - a patch pvec can be used to locate the nearest available common ancestor for
43 resolving conflicts
43 resolving conflicts
44 - ordering of patches can be established without a DAG
44 - ordering of patches can be established without a DAG
45 - two head pvecs can be compared to determine whether push/pull/merge is needed
45 - two head pvecs can be compared to determine whether push/pull/merge is needed
46 and approximately how many changesets are involved
46 and approximately how many changesets are involved
47 - can be used to find a heuristic divergence measure between changesets on
47 - can be used to find a heuristic divergence measure between changesets on
48 different branches
48 different branches
49 '''
49 '''
50
50
51 from __future__ import absolute_import, division
51 from __future__ import absolute_import, division
52
52
53 from .node import nullrev
53 from .node import nullrev
54 from . import (
54 from . import (
55 pycompat,
55 pycompat,
56 util,
56 util,
57 )
57 )
58
58
59 _size = 448 # 70 chars b85-encoded
59 _size = 448 # 70 chars b85-encoded
60 _bytes = _size // 8
60 _bytes = _size // 8
61 _depthbits = 24
61 _depthbits = 24
62 _depthbytes = _depthbits // 8
62 _depthbytes = _depthbits // 8
63 _vecbytes = _bytes - _depthbytes
63 _vecbytes = _bytes - _depthbytes
64 _vecbits = _vecbytes * 8
64 _vecbits = _vecbytes * 8
65 _radius = (_vecbits - 30) // 2 # high probability vectors are related
65 _radius = (_vecbits - 30) // 2 # high probability vectors are related
66
66
67
67
68 def _bin(bs):
68 def _bin(bs):
69 '''convert a bytestring to a long'''
69 '''convert a bytestring to a long'''
70 v = 0
70 v = 0
71 for b in bs:
71 for b in bs:
72 v = v * 256 + ord(b)
72 v = v * 256 + ord(b)
73 return v
73 return v
74
74
75
75
76 def _str(v, l):
76 def _str(v, l):
77 # type: (int, int) -> bytes
77 # type: (int, int) -> bytes
78 bs = b""
78 bs = b""
79 for p in pycompat.xrange(l):
79 for p in pycompat.xrange(l):
80 bs = pycompat.bytechr(v & 255) + bs
80 bs = pycompat.bytechr(v & 255) + bs
81 v >>= 8
81 v >>= 8
82 return bs
82 return bs
83
83
84
84
85 def _split(b):
85 def _split(b):
86 '''depth and bitvec'''
86 '''depth and bitvec'''
87 return _bin(b[:_depthbytes]), _bin(b[_depthbytes:])
87 return _bin(b[:_depthbytes]), _bin(b[_depthbytes:])
88
88
89
89
90 def _join(depth, bitvec):
90 def _join(depth, bitvec):
91 return _str(depth, _depthbytes) + _str(bitvec, _vecbytes)
91 return _str(depth, _depthbytes) + _str(bitvec, _vecbytes)
92
92
93
93
94 def _hweight(x):
94 def _hweight(x):
95 c = 0
95 c = 0
96 while x:
96 while x:
97 if x & 1:
97 if x & 1:
98 c += 1
98 c += 1
99 x >>= 1
99 x >>= 1
100 return c
100 return c
101
101
102
102
103 _htab = [_hweight(x) for x in pycompat.xrange(256)]
103 _htab = [_hweight(x) for x in pycompat.xrange(256)]
104
104
105
105
106 def _hamming(a, b):
106 def _hamming(a, b):
107 '''find the hamming distance between two longs'''
107 '''find the hamming distance between two longs'''
108 d = a ^ b
108 d = a ^ b
109 c = 0
109 c = 0
110 while d:
110 while d:
111 c += _htab[d & 0xFF]
111 c += _htab[d & 0xFF]
112 d >>= 8
112 d >>= 8
113 return c
113 return c
114
114
115
115
116 def _mergevec(x, y, c):
116 def _mergevec(x, y, c):
117 # Ideally, this function would be x ^ y ^ ancestor, but finding
117 # Ideally, this function would be x ^ y ^ ancestor, but finding
118 # ancestors is a nuisance. So instead we find the minimal number
118 # ancestors is a nuisance. So instead we find the minimal number
119 # of changes to balance the depth and hamming distance
119 # of changes to balance the depth and hamming distance
120
120
121 d1, v1 = x
121 d1, v1 = x
122 d2, v2 = y
122 d2, v2 = y
123 if d1 < d2:
123 if d1 < d2:
124 d1, d2, v1, v2 = d2, d1, v2, v1
124 d1, d2, v1, v2 = d2, d1, v2, v1
125
125
126 hdist = _hamming(v1, v2)
126 hdist = _hamming(v1, v2)
127 ddist = d1 - d2
127 ddist = d1 - d2
128 v = v1
128 v = v1
129 m = v1 ^ v2 # mask of different bits
129 m = v1 ^ v2 # mask of different bits
130 i = 1
130 i = 1
131
131
132 if hdist > ddist:
132 if hdist > ddist:
133 # if delta = 10 and hdist = 100, then we need to go up 55 steps
133 # if delta = 10 and hdist = 100, then we need to go up 55 steps
134 # to the ancestor and down 45
134 # to the ancestor and down 45
135 changes = (hdist - ddist + 1) // 2
135 changes = (hdist - ddist + 1) // 2
136 else:
136 else:
137 # must make at least one change
137 # must make at least one change
138 changes = 1
138 changes = 1
139 depth = d1 + changes
139 depth = d1 + changes
140
140
141 # copy changes from v2
141 # copy changes from v2
142 if m:
142 if m:
143 while changes:
143 while changes:
144 if m & i:
144 if m & i:
145 v ^= i
145 v ^= i
146 changes -= 1
146 changes -= 1
147 i <<= 1
147 i <<= 1
148 else:
148 else:
149 v = _flipbit(v, c)
149 v = _flipbit(v, c)
150
150
151 return depth, v
151 return depth, v
152
152
153
153
154 def _flipbit(v, node):
154 def _flipbit(v, node):
155 # converting bit strings to longs is slow
155 # converting bit strings to longs is slow
156 bit = (hash(node) & 0xFFFFFFFF) % _vecbits
156 bit = (hash(node) & 0xFFFFFFFF) % _vecbits
157 return v ^ (1 << bit)
157 return v ^ (1 << bit)
158
158
159
159
160 def ctxpvec(ctx):
160 def ctxpvec(ctx):
161 '''construct a pvec for ctx while filling in the cache'''
161 '''construct a pvec for ctx while filling in the cache'''
162 r = ctx.repo()
162 r = ctx.repo()
163 if not util.safehasattr(r, "_pveccache"):
163 if not util.safehasattr(r, "_pveccache"):
164 r._pveccache = {}
164 r._pveccache = {}
165 pvc = r._pveccache
165 pvc = r._pveccache
166 if ctx.rev() not in pvc:
166 if ctx.rev() not in pvc:
167 cl = r.changelog
167 cl = r.changelog
168 for n in pycompat.xrange(ctx.rev() + 1):
168 for n in pycompat.xrange(ctx.rev() + 1):
169 if n not in pvc:
169 if n not in pvc:
170 node = cl.node(n)
170 node = cl.node(n)
171 p1, p2 = cl.parentrevs(n)
171 p1, p2 = cl.parentrevs(n)
172 if p1 == nullrev:
172 if p1 == nullrev:
173 # start with a 'random' vector at root
173 # start with a 'random' vector at root
174 pvc[n] = (0, _bin((node * 3)[:_vecbytes]))
174 pvc[n] = (0, _bin((node * 3)[:_vecbytes]))
175 elif p2 == nullrev:
175 elif p2 == nullrev:
176 d, v = pvc[p1]
176 d, v = pvc[p1]
177 pvc[n] = (d + 1, _flipbit(v, node))
177 pvc[n] = (d + 1, _flipbit(v, node))
178 else:
178 else:
179 pvc[n] = _mergevec(pvc[p1], pvc[p2], node)
179 pvc[n] = _mergevec(pvc[p1], pvc[p2], node)
180 bs = _join(*pvc[ctx.rev()])
180 bs = _join(*pvc[ctx.rev()])
181 return pvec(util.b85encode(bs))
181 return pvec(util.b85encode(bs))
182
182
183
183
184 class pvec(object):
184 class pvec(object):
185 def __init__(self, hashorctx):
185 def __init__(self, hashorctx):
186 if isinstance(hashorctx, str):
186 if isinstance(hashorctx, bytes):
187 self._bs = hashorctx
187 self._bs = hashorctx
188 self._depth, self._vec = _split(util.b85decode(hashorctx))
188 self._depth, self._vec = _split(util.b85decode(hashorctx))
189 else:
189 else:
190 self._vec = ctxpvec(hashorctx)
190 self._vec = ctxpvec(hashorctx)
191
191
192 def __str__(self):
192 def __str__(self):
193 return self._bs
193 return self._bs
194
194
195 def __eq__(self, b):
195 def __eq__(self, b):
196 return self._vec == b._vec and self._depth == b._depth
196 return self._vec == b._vec and self._depth == b._depth
197
197
198 def __lt__(self, b):
198 def __lt__(self, b):
199 delta = b._depth - self._depth
199 delta = b._depth - self._depth
200 if delta < 0:
200 if delta < 0:
201 return False # always correct
201 return False # always correct
202 if _hamming(self._vec, b._vec) > delta:
202 if _hamming(self._vec, b._vec) > delta:
203 return False
203 return False
204 return True
204 return True
205
205
206 def __gt__(self, b):
206 def __gt__(self, b):
207 return b < self
207 return b < self
208
208
209 def __or__(self, b):
209 def __or__(self, b):
210 delta = abs(b._depth - self._depth)
210 delta = abs(b._depth - self._depth)
211 if _hamming(self._vec, b._vec) <= delta:
211 if _hamming(self._vec, b._vec) <= delta:
212 return False
212 return False
213 return True
213 return True
214
214
215 def __sub__(self, b):
215 def __sub__(self, b):
216 if self | b:
216 if self | b:
217 raise ValueError(b"concurrent pvecs")
217 raise ValueError(b"concurrent pvecs")
218 return self._depth - b._depth
218 return self._depth - b._depth
219
219
220 def distance(self, b):
220 def distance(self, b):
221 d = abs(b._depth - self._depth)
221 d = abs(b._depth - self._depth)
222 h = _hamming(self._vec, b._vec)
222 h = _hamming(self._vec, b._vec)
223 return max(d, h)
223 return max(d, h)
224
224
225 def near(self, b):
225 def near(self, b):
226 dist = abs(b.depth - self._depth)
226 dist = abs(b.depth - self._depth)
227 if dist > _radius or _hamming(self._vec, b._vec) > _radius:
227 if dist > _radius or _hamming(self._vec, b._vec) > _radius:
228 return False
228 return False
@@ -1,537 +1,537 b''
1 # repair.py - functions for repository repair for mercurial
1 # repair.py - functions for repository repair for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 # Copyright 2007 Matt Mackall
4 # Copyright 2007 Matt Mackall
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import hashlib
12 import hashlib
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 hex,
16 hex,
17 short,
17 short,
18 )
18 )
19 from . import (
19 from . import (
20 bundle2,
20 bundle2,
21 changegroup,
21 changegroup,
22 discovery,
22 discovery,
23 error,
23 error,
24 exchange,
24 exchange,
25 obsolete,
25 obsolete,
26 obsutil,
26 obsutil,
27 pathutil,
27 pathutil,
28 phases,
28 phases,
29 pycompat,
29 pycompat,
30 util,
30 util,
31 )
31 )
32 from .utils import stringutil
32 from .utils import stringutil
33
33
34
34
35 def backupbundle(
35 def backupbundle(
36 repo, bases, heads, node, suffix, compress=True, obsolescence=True
36 repo, bases, heads, node, suffix, compress=True, obsolescence=True
37 ):
37 ):
38 """create a bundle with the specified revisions as a backup"""
38 """create a bundle with the specified revisions as a backup"""
39
39
40 backupdir = b"strip-backup"
40 backupdir = b"strip-backup"
41 vfs = repo.vfs
41 vfs = repo.vfs
42 if not vfs.isdir(backupdir):
42 if not vfs.isdir(backupdir):
43 vfs.mkdir(backupdir)
43 vfs.mkdir(backupdir)
44
44
45 # Include a hash of all the nodes in the filename for uniqueness
45 # Include a hash of all the nodes in the filename for uniqueness
46 allcommits = repo.set(b'%ln::%ln', bases, heads)
46 allcommits = repo.set(b'%ln::%ln', bases, heads)
47 allhashes = sorted(c.hex() for c in allcommits)
47 allhashes = sorted(c.hex() for c in allcommits)
48 totalhash = hashlib.sha1(b''.join(allhashes)).digest()
48 totalhash = hashlib.sha1(b''.join(allhashes)).digest()
49 name = b"%s/%s-%s-%s.hg" % (
49 name = b"%s/%s-%s-%s.hg" % (
50 backupdir,
50 backupdir,
51 short(node),
51 short(node),
52 hex(totalhash[:4]),
52 hex(totalhash[:4]),
53 suffix,
53 suffix,
54 )
54 )
55
55
56 cgversion = changegroup.localversion(repo)
56 cgversion = changegroup.localversion(repo)
57 comp = None
57 comp = None
58 if cgversion != b'01':
58 if cgversion != b'01':
59 bundletype = b"HG20"
59 bundletype = b"HG20"
60 if compress:
60 if compress:
61 comp = b'BZ'
61 comp = b'BZ'
62 elif compress:
62 elif compress:
63 bundletype = b"HG10BZ"
63 bundletype = b"HG10BZ"
64 else:
64 else:
65 bundletype = b"HG10UN"
65 bundletype = b"HG10UN"
66
66
67 outgoing = discovery.outgoing(repo, missingroots=bases, missingheads=heads)
67 outgoing = discovery.outgoing(repo, missingroots=bases, missingheads=heads)
68 contentopts = {
68 contentopts = {
69 b'cg.version': cgversion,
69 b'cg.version': cgversion,
70 b'obsolescence': obsolescence,
70 b'obsolescence': obsolescence,
71 b'phases': True,
71 b'phases': True,
72 }
72 }
73 return bundle2.writenewbundle(
73 return bundle2.writenewbundle(
74 repo.ui,
74 repo.ui,
75 repo,
75 repo,
76 b'strip',
76 b'strip',
77 name,
77 name,
78 bundletype,
78 bundletype,
79 outgoing,
79 outgoing,
80 contentopts,
80 contentopts,
81 vfs,
81 vfs,
82 compression=comp,
82 compression=comp,
83 )
83 )
84
84
85
85
86 def _collectfiles(repo, striprev):
86 def _collectfiles(repo, striprev):
87 """find out the filelogs affected by the strip"""
87 """find out the filelogs affected by the strip"""
88 files = set()
88 files = set()
89
89
90 for x in pycompat.xrange(striprev, len(repo)):
90 for x in pycompat.xrange(striprev, len(repo)):
91 files.update(repo[x].files())
91 files.update(repo[x].files())
92
92
93 return sorted(files)
93 return sorted(files)
94
94
95
95
96 def _collectrevlog(revlog, striprev):
96 def _collectrevlog(revlog, striprev):
97 _, brokenset = revlog.getstrippoint(striprev)
97 _, brokenset = revlog.getstrippoint(striprev)
98 return [revlog.linkrev(r) for r in brokenset]
98 return [revlog.linkrev(r) for r in brokenset]
99
99
100
100
101 def _collectbrokencsets(repo, files, striprev):
101 def _collectbrokencsets(repo, files, striprev):
102 """return the changesets which will be broken by the truncation"""
102 """return the changesets which will be broken by the truncation"""
103 s = set()
103 s = set()
104
104
105 for revlog in manifestrevlogs(repo):
105 for revlog in manifestrevlogs(repo):
106 s.update(_collectrevlog(revlog, striprev))
106 s.update(_collectrevlog(revlog, striprev))
107 for fname in files:
107 for fname in files:
108 s.update(_collectrevlog(repo.file(fname), striprev))
108 s.update(_collectrevlog(repo.file(fname), striprev))
109
109
110 return s
110 return s
111
111
112
112
113 def strip(ui, repo, nodelist, backup=True, topic=b'backup'):
113 def strip(ui, repo, nodelist, backup=True, topic=b'backup'):
114 # This function requires the caller to lock the repo, but it operates
114 # This function requires the caller to lock the repo, but it operates
115 # within a transaction of its own, and thus requires there to be no current
115 # within a transaction of its own, and thus requires there to be no current
116 # transaction when it is called.
116 # transaction when it is called.
117 if repo.currenttransaction() is not None:
117 if repo.currenttransaction() is not None:
118 raise error.ProgrammingError(b'cannot strip from inside a transaction')
118 raise error.ProgrammingError(b'cannot strip from inside a transaction')
119
119
120 # Simple way to maintain backwards compatibility for this
120 # Simple way to maintain backwards compatibility for this
121 # argument.
121 # argument.
122 if backup in [b'none', b'strip']:
122 if backup in [b'none', b'strip']:
123 backup = False
123 backup = False
124
124
125 repo = repo.unfiltered()
125 repo = repo.unfiltered()
126 repo.destroying()
126 repo.destroying()
127 vfs = repo.vfs
127 vfs = repo.vfs
128 # load bookmark before changelog to avoid side effect from outdated
128 # load bookmark before changelog to avoid side effect from outdated
129 # changelog (see repo._refreshchangelog)
129 # changelog (see repo._refreshchangelog)
130 repo._bookmarks
130 repo._bookmarks
131 cl = repo.changelog
131 cl = repo.changelog
132
132
133 # TODO handle undo of merge sets
133 # TODO handle undo of merge sets
134 if isinstance(nodelist, str):
134 if isinstance(nodelist, bytes):
135 nodelist = [nodelist]
135 nodelist = [nodelist]
136 striplist = [cl.rev(node) for node in nodelist]
136 striplist = [cl.rev(node) for node in nodelist]
137 striprev = min(striplist)
137 striprev = min(striplist)
138
138
139 files = _collectfiles(repo, striprev)
139 files = _collectfiles(repo, striprev)
140 saverevs = _collectbrokencsets(repo, files, striprev)
140 saverevs = _collectbrokencsets(repo, files, striprev)
141
141
142 # Some revisions with rev > striprev may not be descendants of striprev.
142 # Some revisions with rev > striprev may not be descendants of striprev.
143 # We have to find these revisions and put them in a bundle, so that
143 # We have to find these revisions and put them in a bundle, so that
144 # we can restore them after the truncations.
144 # we can restore them after the truncations.
145 # To create the bundle we use repo.changegroupsubset which requires
145 # To create the bundle we use repo.changegroupsubset which requires
146 # the list of heads and bases of the set of interesting revisions.
146 # the list of heads and bases of the set of interesting revisions.
147 # (head = revision in the set that has no descendant in the set;
147 # (head = revision in the set that has no descendant in the set;
148 # base = revision in the set that has no ancestor in the set)
148 # base = revision in the set that has no ancestor in the set)
149 tostrip = set(striplist)
149 tostrip = set(striplist)
150 saveheads = set(saverevs)
150 saveheads = set(saverevs)
151 for r in cl.revs(start=striprev + 1):
151 for r in cl.revs(start=striprev + 1):
152 if any(p in tostrip for p in cl.parentrevs(r)):
152 if any(p in tostrip for p in cl.parentrevs(r)):
153 tostrip.add(r)
153 tostrip.add(r)
154
154
155 if r not in tostrip:
155 if r not in tostrip:
156 saverevs.add(r)
156 saverevs.add(r)
157 saveheads.difference_update(cl.parentrevs(r))
157 saveheads.difference_update(cl.parentrevs(r))
158 saveheads.add(r)
158 saveheads.add(r)
159 saveheads = [cl.node(r) for r in saveheads]
159 saveheads = [cl.node(r) for r in saveheads]
160
160
161 # compute base nodes
161 # compute base nodes
162 if saverevs:
162 if saverevs:
163 descendants = set(cl.descendants(saverevs))
163 descendants = set(cl.descendants(saverevs))
164 saverevs.difference_update(descendants)
164 saverevs.difference_update(descendants)
165 savebases = [cl.node(r) for r in saverevs]
165 savebases = [cl.node(r) for r in saverevs]
166 stripbases = [cl.node(r) for r in tostrip]
166 stripbases = [cl.node(r) for r in tostrip]
167
167
168 stripobsidx = obsmarkers = ()
168 stripobsidx = obsmarkers = ()
169 if repo.ui.configbool(b'devel', b'strip-obsmarkers'):
169 if repo.ui.configbool(b'devel', b'strip-obsmarkers'):
170 obsmarkers = obsutil.exclusivemarkers(repo, stripbases)
170 obsmarkers = obsutil.exclusivemarkers(repo, stripbases)
171 if obsmarkers:
171 if obsmarkers:
172 stripobsidx = [
172 stripobsidx = [
173 i for i, m in enumerate(repo.obsstore) if m in obsmarkers
173 i for i, m in enumerate(repo.obsstore) if m in obsmarkers
174 ]
174 ]
175
175
176 newbmtarget, updatebm = _bookmarkmovements(repo, tostrip)
176 newbmtarget, updatebm = _bookmarkmovements(repo, tostrip)
177
177
178 backupfile = None
178 backupfile = None
179 node = nodelist[-1]
179 node = nodelist[-1]
180 if backup:
180 if backup:
181 backupfile = _createstripbackup(repo, stripbases, node, topic)
181 backupfile = _createstripbackup(repo, stripbases, node, topic)
182 # create a changegroup for all the branches we need to keep
182 # create a changegroup for all the branches we need to keep
183 tmpbundlefile = None
183 tmpbundlefile = None
184 if saveheads:
184 if saveheads:
185 # do not compress temporary bundle if we remove it from disk later
185 # do not compress temporary bundle if we remove it from disk later
186 #
186 #
187 # We do not include obsolescence, it might re-introduce prune markers
187 # We do not include obsolescence, it might re-introduce prune markers
188 # we are trying to strip. This is harmless since the stripped markers
188 # we are trying to strip. This is harmless since the stripped markers
189 # are already backed up and we did not touched the markers for the
189 # are already backed up and we did not touched the markers for the
190 # saved changesets.
190 # saved changesets.
191 tmpbundlefile = backupbundle(
191 tmpbundlefile = backupbundle(
192 repo,
192 repo,
193 savebases,
193 savebases,
194 saveheads,
194 saveheads,
195 node,
195 node,
196 b'temp',
196 b'temp',
197 compress=False,
197 compress=False,
198 obsolescence=False,
198 obsolescence=False,
199 )
199 )
200
200
201 with ui.uninterruptible():
201 with ui.uninterruptible():
202 try:
202 try:
203 with repo.transaction(b"strip") as tr:
203 with repo.transaction(b"strip") as tr:
204 # TODO this code violates the interface abstraction of the
204 # TODO this code violates the interface abstraction of the
205 # transaction and makes assumptions that file storage is
205 # transaction and makes assumptions that file storage is
206 # using append-only files. We'll need some kind of storage
206 # using append-only files. We'll need some kind of storage
207 # API to handle stripping for us.
207 # API to handle stripping for us.
208 offset = len(tr._entries)
208 offset = len(tr._entries)
209
209
210 tr.startgroup()
210 tr.startgroup()
211 cl.strip(striprev, tr)
211 cl.strip(striprev, tr)
212 stripmanifest(repo, striprev, tr, files)
212 stripmanifest(repo, striprev, tr, files)
213
213
214 for fn in files:
214 for fn in files:
215 repo.file(fn).strip(striprev, tr)
215 repo.file(fn).strip(striprev, tr)
216 tr.endgroup()
216 tr.endgroup()
217
217
218 for i in pycompat.xrange(offset, len(tr._entries)):
218 for i in pycompat.xrange(offset, len(tr._entries)):
219 file, troffset, ignore = tr._entries[i]
219 file, troffset, ignore = tr._entries[i]
220 with repo.svfs(file, b'a', checkambig=True) as fp:
220 with repo.svfs(file, b'a', checkambig=True) as fp:
221 fp.truncate(troffset)
221 fp.truncate(troffset)
222 if troffset == 0:
222 if troffset == 0:
223 repo.store.markremoved(file)
223 repo.store.markremoved(file)
224
224
225 deleteobsmarkers(repo.obsstore, stripobsidx)
225 deleteobsmarkers(repo.obsstore, stripobsidx)
226 del repo.obsstore
226 del repo.obsstore
227 repo.invalidatevolatilesets()
227 repo.invalidatevolatilesets()
228 repo._phasecache.filterunknown(repo)
228 repo._phasecache.filterunknown(repo)
229
229
230 if tmpbundlefile:
230 if tmpbundlefile:
231 ui.note(_(b"adding branch\n"))
231 ui.note(_(b"adding branch\n"))
232 f = vfs.open(tmpbundlefile, b"rb")
232 f = vfs.open(tmpbundlefile, b"rb")
233 gen = exchange.readbundle(ui, f, tmpbundlefile, vfs)
233 gen = exchange.readbundle(ui, f, tmpbundlefile, vfs)
234 if not repo.ui.verbose:
234 if not repo.ui.verbose:
235 # silence internal shuffling chatter
235 # silence internal shuffling chatter
236 repo.ui.pushbuffer()
236 repo.ui.pushbuffer()
237 tmpbundleurl = b'bundle:' + vfs.join(tmpbundlefile)
237 tmpbundleurl = b'bundle:' + vfs.join(tmpbundlefile)
238 txnname = b'strip'
238 txnname = b'strip'
239 if not isinstance(gen, bundle2.unbundle20):
239 if not isinstance(gen, bundle2.unbundle20):
240 txnname = b"strip\n%s" % util.hidepassword(tmpbundleurl)
240 txnname = b"strip\n%s" % util.hidepassword(tmpbundleurl)
241 with repo.transaction(txnname) as tr:
241 with repo.transaction(txnname) as tr:
242 bundle2.applybundle(
242 bundle2.applybundle(
243 repo, gen, tr, source=b'strip', url=tmpbundleurl
243 repo, gen, tr, source=b'strip', url=tmpbundleurl
244 )
244 )
245 if not repo.ui.verbose:
245 if not repo.ui.verbose:
246 repo.ui.popbuffer()
246 repo.ui.popbuffer()
247 f.close()
247 f.close()
248
248
249 with repo.transaction(b'repair') as tr:
249 with repo.transaction(b'repair') as tr:
250 bmchanges = [(m, repo[newbmtarget].node()) for m in updatebm]
250 bmchanges = [(m, repo[newbmtarget].node()) for m in updatebm]
251 repo._bookmarks.applychanges(repo, tr, bmchanges)
251 repo._bookmarks.applychanges(repo, tr, bmchanges)
252
252
253 # remove undo files
253 # remove undo files
254 for undovfs, undofile in repo.undofiles():
254 for undovfs, undofile in repo.undofiles():
255 try:
255 try:
256 undovfs.unlink(undofile)
256 undovfs.unlink(undofile)
257 except OSError as e:
257 except OSError as e:
258 if e.errno != errno.ENOENT:
258 if e.errno != errno.ENOENT:
259 ui.warn(
259 ui.warn(
260 _(b'error removing %s: %s\n')
260 _(b'error removing %s: %s\n')
261 % (
261 % (
262 undovfs.join(undofile),
262 undovfs.join(undofile),
263 stringutil.forcebytestr(e),
263 stringutil.forcebytestr(e),
264 )
264 )
265 )
265 )
266
266
267 except: # re-raises
267 except: # re-raises
268 if backupfile:
268 if backupfile:
269 ui.warn(
269 ui.warn(
270 _(b"strip failed, backup bundle stored in '%s'\n")
270 _(b"strip failed, backup bundle stored in '%s'\n")
271 % vfs.join(backupfile)
271 % vfs.join(backupfile)
272 )
272 )
273 if tmpbundlefile:
273 if tmpbundlefile:
274 ui.warn(
274 ui.warn(
275 _(b"strip failed, unrecovered changes stored in '%s'\n")
275 _(b"strip failed, unrecovered changes stored in '%s'\n")
276 % vfs.join(tmpbundlefile)
276 % vfs.join(tmpbundlefile)
277 )
277 )
278 ui.warn(
278 ui.warn(
279 _(
279 _(
280 b"(fix the problem, then recover the changesets with "
280 b"(fix the problem, then recover the changesets with "
281 b"\"hg unbundle '%s'\")\n"
281 b"\"hg unbundle '%s'\")\n"
282 )
282 )
283 % vfs.join(tmpbundlefile)
283 % vfs.join(tmpbundlefile)
284 )
284 )
285 raise
285 raise
286 else:
286 else:
287 if tmpbundlefile:
287 if tmpbundlefile:
288 # Remove temporary bundle only if there were no exceptions
288 # Remove temporary bundle only if there were no exceptions
289 vfs.unlink(tmpbundlefile)
289 vfs.unlink(tmpbundlefile)
290
290
291 repo.destroyed()
291 repo.destroyed()
292 # return the backup file path (or None if 'backup' was False) so
292 # return the backup file path (or None if 'backup' was False) so
293 # extensions can use it
293 # extensions can use it
294 return backupfile
294 return backupfile
295
295
296
296
297 def softstrip(ui, repo, nodelist, backup=True, topic=b'backup'):
297 def softstrip(ui, repo, nodelist, backup=True, topic=b'backup'):
298 """perform a "soft" strip using the archived phase"""
298 """perform a "soft" strip using the archived phase"""
299 tostrip = [c.node() for c in repo.set(b'sort(%ln::)', nodelist)]
299 tostrip = [c.node() for c in repo.set(b'sort(%ln::)', nodelist)]
300 if not tostrip:
300 if not tostrip:
301 return None
301 return None
302
302
303 newbmtarget, updatebm = _bookmarkmovements(repo, tostrip)
303 newbmtarget, updatebm = _bookmarkmovements(repo, tostrip)
304 if backup:
304 if backup:
305 node = tostrip[0]
305 node = tostrip[0]
306 backupfile = _createstripbackup(repo, tostrip, node, topic)
306 backupfile = _createstripbackup(repo, tostrip, node, topic)
307
307
308 with repo.transaction(b'strip') as tr:
308 with repo.transaction(b'strip') as tr:
309 phases.retractboundary(repo, tr, phases.archived, tostrip)
309 phases.retractboundary(repo, tr, phases.archived, tostrip)
310 bmchanges = [(m, repo[newbmtarget].node()) for m in updatebm]
310 bmchanges = [(m, repo[newbmtarget].node()) for m in updatebm]
311 repo._bookmarks.applychanges(repo, tr, bmchanges)
311 repo._bookmarks.applychanges(repo, tr, bmchanges)
312 return backupfile
312 return backupfile
313
313
314
314
315 def _bookmarkmovements(repo, tostrip):
315 def _bookmarkmovements(repo, tostrip):
316 # compute necessary bookmark movement
316 # compute necessary bookmark movement
317 bm = repo._bookmarks
317 bm = repo._bookmarks
318 updatebm = []
318 updatebm = []
319 for m in bm:
319 for m in bm:
320 rev = repo[bm[m]].rev()
320 rev = repo[bm[m]].rev()
321 if rev in tostrip:
321 if rev in tostrip:
322 updatebm.append(m)
322 updatebm.append(m)
323 newbmtarget = None
323 newbmtarget = None
324 # If we need to move bookmarks, compute bookmark
324 # If we need to move bookmarks, compute bookmark
325 # targets. Otherwise we can skip doing this logic.
325 # targets. Otherwise we can skip doing this logic.
326 if updatebm:
326 if updatebm:
327 # For a set s, max(parents(s) - s) is the same as max(heads(::s - s)),
327 # For a set s, max(parents(s) - s) is the same as max(heads(::s - s)),
328 # but is much faster
328 # but is much faster
329 newbmtarget = repo.revs(b'max(parents(%ld) - (%ld))', tostrip, tostrip)
329 newbmtarget = repo.revs(b'max(parents(%ld) - (%ld))', tostrip, tostrip)
330 if newbmtarget:
330 if newbmtarget:
331 newbmtarget = repo[newbmtarget.first()].node()
331 newbmtarget = repo[newbmtarget.first()].node()
332 else:
332 else:
333 newbmtarget = b'.'
333 newbmtarget = b'.'
334 return newbmtarget, updatebm
334 return newbmtarget, updatebm
335
335
336
336
337 def _createstripbackup(repo, stripbases, node, topic):
337 def _createstripbackup(repo, stripbases, node, topic):
338 # backup the changeset we are about to strip
338 # backup the changeset we are about to strip
339 vfs = repo.vfs
339 vfs = repo.vfs
340 cl = repo.changelog
340 cl = repo.changelog
341 backupfile = backupbundle(repo, stripbases, cl.heads(), node, topic)
341 backupfile = backupbundle(repo, stripbases, cl.heads(), node, topic)
342 repo.ui.status(_(b"saved backup bundle to %s\n") % vfs.join(backupfile))
342 repo.ui.status(_(b"saved backup bundle to %s\n") % vfs.join(backupfile))
343 repo.ui.log(
343 repo.ui.log(
344 b"backupbundle", b"saved backup bundle to %s\n", vfs.join(backupfile)
344 b"backupbundle", b"saved backup bundle to %s\n", vfs.join(backupfile)
345 )
345 )
346 return backupfile
346 return backupfile
347
347
348
348
349 def safestriproots(ui, repo, nodes):
349 def safestriproots(ui, repo, nodes):
350 """return list of roots of nodes where descendants are covered by nodes"""
350 """return list of roots of nodes where descendants are covered by nodes"""
351 torev = repo.unfiltered().changelog.rev
351 torev = repo.unfiltered().changelog.rev
352 revs = set(torev(n) for n in nodes)
352 revs = set(torev(n) for n in nodes)
353 # tostrip = wanted - unsafe = wanted - ancestors(orphaned)
353 # tostrip = wanted - unsafe = wanted - ancestors(orphaned)
354 # orphaned = affected - wanted
354 # orphaned = affected - wanted
355 # affected = descendants(roots(wanted))
355 # affected = descendants(roots(wanted))
356 # wanted = revs
356 # wanted = revs
357 revset = b'%ld - ( ::( (roots(%ld):: and not _phase(%s)) -%ld) )'
357 revset = b'%ld - ( ::( (roots(%ld):: and not _phase(%s)) -%ld) )'
358 tostrip = set(repo.revs(revset, revs, revs, phases.internal, revs))
358 tostrip = set(repo.revs(revset, revs, revs, phases.internal, revs))
359 notstrip = revs - tostrip
359 notstrip = revs - tostrip
360 if notstrip:
360 if notstrip:
361 nodestr = b', '.join(sorted(short(repo[n].node()) for n in notstrip))
361 nodestr = b', '.join(sorted(short(repo[n].node()) for n in notstrip))
362 ui.warn(
362 ui.warn(
363 _(b'warning: orphaned descendants detected, not stripping %s\n')
363 _(b'warning: orphaned descendants detected, not stripping %s\n')
364 % nodestr
364 % nodestr
365 )
365 )
366 return [c.node() for c in repo.set(b'roots(%ld)', tostrip)]
366 return [c.node() for c in repo.set(b'roots(%ld)', tostrip)]
367
367
368
368
369 class stripcallback(object):
369 class stripcallback(object):
370 """used as a transaction postclose callback"""
370 """used as a transaction postclose callback"""
371
371
372 def __init__(self, ui, repo, backup, topic):
372 def __init__(self, ui, repo, backup, topic):
373 self.ui = ui
373 self.ui = ui
374 self.repo = repo
374 self.repo = repo
375 self.backup = backup
375 self.backup = backup
376 self.topic = topic or b'backup'
376 self.topic = topic or b'backup'
377 self.nodelist = []
377 self.nodelist = []
378
378
379 def addnodes(self, nodes):
379 def addnodes(self, nodes):
380 self.nodelist.extend(nodes)
380 self.nodelist.extend(nodes)
381
381
382 def __call__(self, tr):
382 def __call__(self, tr):
383 roots = safestriproots(self.ui, self.repo, self.nodelist)
383 roots = safestriproots(self.ui, self.repo, self.nodelist)
384 if roots:
384 if roots:
385 strip(self.ui, self.repo, roots, self.backup, self.topic)
385 strip(self.ui, self.repo, roots, self.backup, self.topic)
386
386
387
387
388 def delayedstrip(ui, repo, nodelist, topic=None, backup=True):
388 def delayedstrip(ui, repo, nodelist, topic=None, backup=True):
389 """like strip, but works inside transaction and won't strip irreverent revs
389 """like strip, but works inside transaction and won't strip irreverent revs
390
390
391 nodelist must explicitly contain all descendants. Otherwise a warning will
391 nodelist must explicitly contain all descendants. Otherwise a warning will
392 be printed that some nodes are not stripped.
392 be printed that some nodes are not stripped.
393
393
394 Will do a backup if `backup` is True. The last non-None "topic" will be
394 Will do a backup if `backup` is True. The last non-None "topic" will be
395 used as the backup topic name. The default backup topic name is "backup".
395 used as the backup topic name. The default backup topic name is "backup".
396 """
396 """
397 tr = repo.currenttransaction()
397 tr = repo.currenttransaction()
398 if not tr:
398 if not tr:
399 nodes = safestriproots(ui, repo, nodelist)
399 nodes = safestriproots(ui, repo, nodelist)
400 return strip(ui, repo, nodes, backup=backup, topic=topic)
400 return strip(ui, repo, nodes, backup=backup, topic=topic)
401 # transaction postclose callbacks are called in alphabet order.
401 # transaction postclose callbacks are called in alphabet order.
402 # use '\xff' as prefix so we are likely to be called last.
402 # use '\xff' as prefix so we are likely to be called last.
403 callback = tr.getpostclose(b'\xffstrip')
403 callback = tr.getpostclose(b'\xffstrip')
404 if callback is None:
404 if callback is None:
405 callback = stripcallback(ui, repo, backup=backup, topic=topic)
405 callback = stripcallback(ui, repo, backup=backup, topic=topic)
406 tr.addpostclose(b'\xffstrip', callback)
406 tr.addpostclose(b'\xffstrip', callback)
407 if topic:
407 if topic:
408 callback.topic = topic
408 callback.topic = topic
409 callback.addnodes(nodelist)
409 callback.addnodes(nodelist)
410
410
411
411
412 def stripmanifest(repo, striprev, tr, files):
412 def stripmanifest(repo, striprev, tr, files):
413 for revlog in manifestrevlogs(repo):
413 for revlog in manifestrevlogs(repo):
414 revlog.strip(striprev, tr)
414 revlog.strip(striprev, tr)
415
415
416
416
417 def manifestrevlogs(repo):
417 def manifestrevlogs(repo):
418 yield repo.manifestlog.getstorage(b'')
418 yield repo.manifestlog.getstorage(b'')
419 if b'treemanifest' in repo.requirements:
419 if b'treemanifest' in repo.requirements:
420 # This logic is safe if treemanifest isn't enabled, but also
420 # This logic is safe if treemanifest isn't enabled, but also
421 # pointless, so we skip it if treemanifest isn't enabled.
421 # pointless, so we skip it if treemanifest isn't enabled.
422 for unencoded, encoded, size in repo.store.datafiles():
422 for unencoded, encoded, size in repo.store.datafiles():
423 if unencoded.startswith(b'meta/') and unencoded.endswith(
423 if unencoded.startswith(b'meta/') and unencoded.endswith(
424 b'00manifest.i'
424 b'00manifest.i'
425 ):
425 ):
426 dir = unencoded[5:-12]
426 dir = unencoded[5:-12]
427 yield repo.manifestlog.getstorage(dir)
427 yield repo.manifestlog.getstorage(dir)
428
428
429
429
430 def rebuildfncache(ui, repo):
430 def rebuildfncache(ui, repo):
431 """Rebuilds the fncache file from repo history.
431 """Rebuilds the fncache file from repo history.
432
432
433 Missing entries will be added. Extra entries will be removed.
433 Missing entries will be added. Extra entries will be removed.
434 """
434 """
435 repo = repo.unfiltered()
435 repo = repo.unfiltered()
436
436
437 if b'fncache' not in repo.requirements:
437 if b'fncache' not in repo.requirements:
438 ui.warn(
438 ui.warn(
439 _(
439 _(
440 b'(not rebuilding fncache because repository does not '
440 b'(not rebuilding fncache because repository does not '
441 b'support fncache)\n'
441 b'support fncache)\n'
442 )
442 )
443 )
443 )
444 return
444 return
445
445
446 with repo.lock():
446 with repo.lock():
447 fnc = repo.store.fncache
447 fnc = repo.store.fncache
448 fnc.ensureloaded(warn=ui.warn)
448 fnc.ensureloaded(warn=ui.warn)
449
449
450 oldentries = set(fnc.entries)
450 oldentries = set(fnc.entries)
451 newentries = set()
451 newentries = set()
452 seenfiles = set()
452 seenfiles = set()
453
453
454 progress = ui.makeprogress(
454 progress = ui.makeprogress(
455 _(b'rebuilding'), unit=_(b'changesets'), total=len(repo)
455 _(b'rebuilding'), unit=_(b'changesets'), total=len(repo)
456 )
456 )
457 for rev in repo:
457 for rev in repo:
458 progress.update(rev)
458 progress.update(rev)
459
459
460 ctx = repo[rev]
460 ctx = repo[rev]
461 for f in ctx.files():
461 for f in ctx.files():
462 # This is to minimize I/O.
462 # This is to minimize I/O.
463 if f in seenfiles:
463 if f in seenfiles:
464 continue
464 continue
465 seenfiles.add(f)
465 seenfiles.add(f)
466
466
467 i = b'data/%s.i' % f
467 i = b'data/%s.i' % f
468 d = b'data/%s.d' % f
468 d = b'data/%s.d' % f
469
469
470 if repo.store._exists(i):
470 if repo.store._exists(i):
471 newentries.add(i)
471 newentries.add(i)
472 if repo.store._exists(d):
472 if repo.store._exists(d):
473 newentries.add(d)
473 newentries.add(d)
474
474
475 progress.complete()
475 progress.complete()
476
476
477 if b'treemanifest' in repo.requirements:
477 if b'treemanifest' in repo.requirements:
478 # This logic is safe if treemanifest isn't enabled, but also
478 # This logic is safe if treemanifest isn't enabled, but also
479 # pointless, so we skip it if treemanifest isn't enabled.
479 # pointless, so we skip it if treemanifest isn't enabled.
480 for dir in pathutil.dirs(seenfiles):
480 for dir in pathutil.dirs(seenfiles):
481 i = b'meta/%s/00manifest.i' % dir
481 i = b'meta/%s/00manifest.i' % dir
482 d = b'meta/%s/00manifest.d' % dir
482 d = b'meta/%s/00manifest.d' % dir
483
483
484 if repo.store._exists(i):
484 if repo.store._exists(i):
485 newentries.add(i)
485 newentries.add(i)
486 if repo.store._exists(d):
486 if repo.store._exists(d):
487 newentries.add(d)
487 newentries.add(d)
488
488
489 addcount = len(newentries - oldentries)
489 addcount = len(newentries - oldentries)
490 removecount = len(oldentries - newentries)
490 removecount = len(oldentries - newentries)
491 for p in sorted(oldentries - newentries):
491 for p in sorted(oldentries - newentries):
492 ui.write(_(b'removing %s\n') % p)
492 ui.write(_(b'removing %s\n') % p)
493 for p in sorted(newentries - oldentries):
493 for p in sorted(newentries - oldentries):
494 ui.write(_(b'adding %s\n') % p)
494 ui.write(_(b'adding %s\n') % p)
495
495
496 if addcount or removecount:
496 if addcount or removecount:
497 ui.write(
497 ui.write(
498 _(b'%d items added, %d removed from fncache\n')
498 _(b'%d items added, %d removed from fncache\n')
499 % (addcount, removecount)
499 % (addcount, removecount)
500 )
500 )
501 fnc.entries = newentries
501 fnc.entries = newentries
502 fnc._dirty = True
502 fnc._dirty = True
503
503
504 with repo.transaction(b'fncache') as tr:
504 with repo.transaction(b'fncache') as tr:
505 fnc.write(tr)
505 fnc.write(tr)
506 else:
506 else:
507 ui.write(_(b'fncache already up to date\n'))
507 ui.write(_(b'fncache already up to date\n'))
508
508
509
509
510 def deleteobsmarkers(obsstore, indices):
510 def deleteobsmarkers(obsstore, indices):
511 """Delete some obsmarkers from obsstore and return how many were deleted
511 """Delete some obsmarkers from obsstore and return how many were deleted
512
512
513 'indices' is a list of ints which are the indices
513 'indices' is a list of ints which are the indices
514 of the markers to be deleted.
514 of the markers to be deleted.
515
515
516 Every invocation of this function completely rewrites the obsstore file,
516 Every invocation of this function completely rewrites the obsstore file,
517 skipping the markers we want to be removed. The new temporary file is
517 skipping the markers we want to be removed. The new temporary file is
518 created, remaining markers are written there and on .close() this file
518 created, remaining markers are written there and on .close() this file
519 gets atomically renamed to obsstore, thus guaranteeing consistency."""
519 gets atomically renamed to obsstore, thus guaranteeing consistency."""
520 if not indices:
520 if not indices:
521 # we don't want to rewrite the obsstore with the same content
521 # we don't want to rewrite the obsstore with the same content
522 return
522 return
523
523
524 left = []
524 left = []
525 current = obsstore._all
525 current = obsstore._all
526 n = 0
526 n = 0
527 for i, m in enumerate(current):
527 for i, m in enumerate(current):
528 if i in indices:
528 if i in indices:
529 n += 1
529 n += 1
530 continue
530 continue
531 left.append(m)
531 left.append(m)
532
532
533 newobsstorefile = obsstore.svfs(b'obsstore', b'w', atomictemp=True)
533 newobsstorefile = obsstore.svfs(b'obsstore', b'w', atomictemp=True)
534 for bytes in obsolete.encodemarkers(left, True, obsstore._version):
534 for bytes in obsolete.encodemarkers(left, True, obsstore._version):
535 newobsstorefile.write(bytes)
535 newobsstorefile.write(bytes)
536 newobsstorefile.close()
536 newobsstorefile.close()
537 return n
537 return n
@@ -1,66 +1,66 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 import os
3 import os
4
4
5 from . import (
5 from . import (
6 encoding,
6 encoding,
7 pycompat,
7 pycompat,
8 util,
8 util,
9 win32,
9 win32,
10 )
10 )
11
11
12 try:
12 try:
13 import _winreg as winreg # pytype: disable=import-error
13 import _winreg as winreg # pytype: disable=import-error
14
14
15 winreg.CloseKey
15 winreg.CloseKey
16 except ImportError:
16 except ImportError:
17 # py2 only
17 # py2 only
18 import winreg # pytype: disable=import-error
18 import winreg # pytype: disable=import-error
19
19
20 # MS-DOS 'more' is the only pager available by default on Windows.
20 # MS-DOS 'more' is the only pager available by default on Windows.
21 fallbackpager = b'more'
21 fallbackpager = b'more'
22
22
23
23
24 def systemrcpath():
24 def systemrcpath():
25 '''return default os-specific hgrc search path'''
25 '''return default os-specific hgrc search path'''
26 rcpath = []
26 rcpath = []
27 filename = win32.executablepath()
27 filename = win32.executablepath()
28 # Use mercurial.ini found in directory with hg.exe
28 # Use mercurial.ini found in directory with hg.exe
29 progrc = os.path.join(os.path.dirname(filename), b'mercurial.ini')
29 progrc = os.path.join(os.path.dirname(filename), b'mercurial.ini')
30 rcpath.append(progrc)
30 rcpath.append(progrc)
31 # Use hgrc.d found in directory with hg.exe
31 # Use hgrc.d found in directory with hg.exe
32 progrcd = os.path.join(os.path.dirname(filename), b'hgrc.d')
32 progrcd = os.path.join(os.path.dirname(filename), b'hgrc.d')
33 if os.path.isdir(progrcd):
33 if os.path.isdir(progrcd):
34 for f, kind in util.listdir(progrcd):
34 for f, kind in util.listdir(progrcd):
35 if f.endswith(b'.rc'):
35 if f.endswith(b'.rc'):
36 rcpath.append(os.path.join(progrcd, f))
36 rcpath.append(os.path.join(progrcd, f))
37 # else look for a system rcpath in the registry
37 # else look for a system rcpath in the registry
38 value = util.lookupreg(
38 value = util.lookupreg(
39 b'SOFTWARE\\Mercurial', None, winreg.HKEY_LOCAL_MACHINE
39 b'SOFTWARE\\Mercurial', None, winreg.HKEY_LOCAL_MACHINE
40 )
40 )
41 if not isinstance(value, str) or not value:
41 if not isinstance(value, bytes) or not value:
42 return rcpath
42 return rcpath
43 value = util.localpath(value)
43 value = util.localpath(value)
44 for p in value.split(pycompat.ospathsep):
44 for p in value.split(pycompat.ospathsep):
45 if p.lower().endswith(b'mercurial.ini'):
45 if p.lower().endswith(b'mercurial.ini'):
46 rcpath.append(p)
46 rcpath.append(p)
47 elif os.path.isdir(p):
47 elif os.path.isdir(p):
48 for f, kind in util.listdir(p):
48 for f, kind in util.listdir(p):
49 if f.endswith(b'.rc'):
49 if f.endswith(b'.rc'):
50 rcpath.append(os.path.join(p, f))
50 rcpath.append(os.path.join(p, f))
51 return rcpath
51 return rcpath
52
52
53
53
54 def userrcpath():
54 def userrcpath():
55 '''return os-specific hgrc search path to the user dir'''
55 '''return os-specific hgrc search path to the user dir'''
56 home = os.path.expanduser(b'~')
56 home = os.path.expanduser(b'~')
57 path = [os.path.join(home, b'mercurial.ini'), os.path.join(home, b'.hgrc')]
57 path = [os.path.join(home, b'mercurial.ini'), os.path.join(home, b'.hgrc')]
58 userprofile = encoding.environ.get(b'USERPROFILE')
58 userprofile = encoding.environ.get(b'USERPROFILE')
59 if userprofile and userprofile != home:
59 if userprofile and userprofile != home:
60 path.append(os.path.join(userprofile, b'mercurial.ini'))
60 path.append(os.path.join(userprofile, b'mercurial.ini'))
61 path.append(os.path.join(userprofile, b'.hgrc'))
61 path.append(os.path.join(userprofile, b'.hgrc'))
62 return path
62 return path
63
63
64
64
65 def termsize(ui):
65 def termsize(ui):
66 return win32.termsize()
66 return win32.termsize()
@@ -1,316 +1,320 b''
1 $ cat >> $HGRCPATH << EOF
1 $ cat >> $HGRCPATH << EOF
2 > [extensions]
2 > [extensions]
3 > githelp =
3 > githelp =
4 > EOF
4 > EOF
5
5
6 $ hg init repo
6 $ hg init repo
7 $ cd repo
7 $ cd repo
8 $ echo foo > test_file
8 $ echo foo > test_file
9 $ mkdir dir
9 $ mkdir dir
10 $ echo foo > dir/file
10 $ echo foo > dir/file
11 $ echo foo > removed_file
11 $ echo foo > removed_file
12 $ echo foo > deleted_file
12 $ echo foo > deleted_file
13 $ hg add -q .
13 $ hg add -q .
14 $ hg commit -m 'bar'
14 $ hg commit -m 'bar'
15 $ hg bookmark both
15 $ hg bookmark both
16 $ touch both
16 $ touch both
17 $ touch untracked_file
17 $ touch untracked_file
18 $ hg remove removed_file
18 $ hg remove removed_file
19 $ rm deleted_file
19 $ rm deleted_file
20
20
21 githelp on a single command should succeed
21 githelp on a single command should succeed
22 $ hg githelp -- commit
22 $ hg githelp -- commit
23 hg commit
23 hg commit
24 $ hg githelp -- git commit
24 $ hg githelp -- git commit
25 hg commit
25 hg commit
26
26
27 githelp should fail nicely if we don't give it arguments
27 githelp should fail nicely if we don't give it arguments
28 $ hg githelp
28 $ hg githelp
29 abort: missing git command - usage: hg githelp -- <git command>
29 abort: missing git command - usage: hg githelp -- <git command>
30 [255]
30 [255]
31 $ hg githelp -- git
31 $ hg githelp -- git
32 abort: missing git command - usage: hg githelp -- <git command>
32 abort: missing git command - usage: hg githelp -- <git command>
33 [255]
33 [255]
34
34
35 githelp on a command with options should succeed
35 githelp on a command with options should succeed
36 $ hg githelp -- commit -pm "abc"
36 $ hg githelp -- commit -pm "abc"
37 hg commit --interactive -m 'abc'
37 hg commit --interactive -m 'abc'
38
38
39 githelp on a command with standalone unrecognized option should succeed with warning
39 githelp on a command with standalone unrecognized option should succeed with warning
40 $ hg githelp -- commit -p -v
40 $ hg githelp -- commit -p -v
41 ignoring unknown option -v
41 ignoring unknown option -v
42 hg commit --interactive
42 hg commit --interactive
43
43
44 githelp on a command with unrecognized option packed with other options should fail with error
44 githelp on a command with unrecognized option packed with other options should fail with error
45 $ hg githelp -- commit -pv
45 $ hg githelp -- commit -pv
46 abort: unknown option 'v' packed with other options
46 abort: unknown option 'v' packed with other options
47 (please try passing the option as its own flag: -v)
47 (please try passing the option as its own flag: -v)
48 [255]
48 [255]
49
49
50 githelp for git rebase --skip
50 githelp for git rebase --skip
51 $ hg githelp -- git rebase --skip
51 $ hg githelp -- git rebase --skip
52 hg revert --all -r .
52 hg revert --all -r .
53 hg rebase --continue
53 hg rebase --continue
54
54
55 githelp for git commit --amend (hg commit --amend pulls up an editor)
55 githelp for git commit --amend (hg commit --amend pulls up an editor)
56 $ hg githelp -- commit --amend
56 $ hg githelp -- commit --amend
57 hg commit --amend
57 hg commit --amend
58
58
59 githelp for git commit --amend --no-edit (hg amend does not pull up an editor)
59 githelp for git commit --amend --no-edit (hg amend does not pull up an editor)
60 $ hg githelp -- commit --amend --no-edit
60 $ hg githelp -- commit --amend --no-edit
61 hg amend
61 hg amend
62
62
63 githelp for git checkout -- . (checking out a directory)
63 githelp for git checkout -- . (checking out a directory)
64 $ hg githelp -- checkout -- .
64 $ hg githelp -- checkout -- .
65 note: use --no-backup to avoid creating .orig files
65 note: use --no-backup to avoid creating .orig files
66
66
67 hg revert .
67 hg revert .
68
68
69 githelp for git checkout "HEAD^" (should still work to pass a rev)
69 githelp for git checkout "HEAD^" (should still work to pass a rev)
70 $ hg githelp -- checkout "HEAD^"
70 $ hg githelp -- checkout "HEAD^"
71 hg update .^
71 hg update .^
72
72
73 githelp checkout: args after -- should be treated as paths no matter what
73 githelp checkout: args after -- should be treated as paths no matter what
74 $ hg githelp -- checkout -- HEAD
74 $ hg githelp -- checkout -- HEAD
75 note: use --no-backup to avoid creating .orig files
75 note: use --no-backup to avoid creating .orig files
76
76
77 hg revert HEAD
77 hg revert HEAD
78
78
79 githelp for git checkout with rev and path
79 githelp for git checkout with rev and path
80 $ hg githelp -- checkout "HEAD^" -- file.txt
80 $ hg githelp -- checkout "HEAD^" -- file.txt
81 note: use --no-backup to avoid creating .orig files
81 note: use --no-backup to avoid creating .orig files
82
82
83 hg revert -r .^ file.txt
83 hg revert -r .^ file.txt
84
84
85 githelp for git with rev and path, without separator
85 githelp for git with rev and path, without separator
86 $ hg githelp -- checkout "HEAD^" file.txt
86 $ hg githelp -- checkout "HEAD^" file.txt
87 note: use --no-backup to avoid creating .orig files
87 note: use --no-backup to avoid creating .orig files
88
88
89 hg revert -r .^ file.txt
89 hg revert -r .^ file.txt
90
90
91 githelp for checkout with a file as first argument
91 githelp for checkout with a file as first argument
92 $ hg githelp -- checkout test_file
92 $ hg githelp -- checkout test_file
93 note: use --no-backup to avoid creating .orig files
93 note: use --no-backup to avoid creating .orig files
94
94
95 hg revert test_file
95 hg revert test_file
96
96
97 githelp for checkout with a removed file as first argument
97 githelp for checkout with a removed file as first argument
98 $ hg githelp -- checkout removed_file
98 $ hg githelp -- checkout removed_file
99 note: use --no-backup to avoid creating .orig files
99 note: use --no-backup to avoid creating .orig files
100
100
101 hg revert removed_file
101 hg revert removed_file
102
102
103 githelp for checkout with a deleted file as first argument
103 githelp for checkout with a deleted file as first argument
104 $ hg githelp -- checkout deleted_file
104 $ hg githelp -- checkout deleted_file
105 note: use --no-backup to avoid creating .orig files
105 note: use --no-backup to avoid creating .orig files
106
106
107 hg revert deleted_file
107 hg revert deleted_file
108
108
109 githelp for checkout with a untracked file as first argument
109 githelp for checkout with a untracked file as first argument
110 $ hg githelp -- checkout untracked_file
110 $ hg githelp -- checkout untracked_file
111 note: use --no-backup to avoid creating .orig files
111 note: use --no-backup to avoid creating .orig files
112
112
113 hg revert untracked_file
113 hg revert untracked_file
114
114
115 githelp for checkout with a directory as first argument
115 githelp for checkout with a directory as first argument
116 $ hg githelp -- checkout dir
116 $ hg githelp -- checkout dir
117 note: use --no-backup to avoid creating .orig files
117 note: use --no-backup to avoid creating .orig files
118
118
119 hg revert dir
119 hg revert dir
120
120
121 githelp for checkout when not in repo root
121 githelp for checkout when not in repo root
122 $ cd dir
122 $ cd dir
123 $ hg githelp -- checkout file
123 $ hg githelp -- checkout file
124 note: use --no-backup to avoid creating .orig files
124 note: use --no-backup to avoid creating .orig files
125
125
126 hg revert file
126 hg revert file
127
127
128 $ cd ..
128 $ cd ..
129
129
130 githelp for checkout with an argument that is both a file and a revision
130 githelp for checkout with an argument that is both a file and a revision
131 $ hg githelp -- checkout both
131 $ hg githelp -- checkout both
132 hg update both
132 hg update both
133
133
134 githelp for checkout with the -p option
134 githelp for checkout with the -p option
135 $ hg githelp -- git checkout -p xyz
135 $ hg githelp -- git checkout -p xyz
136 hg revert -i -r xyz
136 hg revert -i -r xyz
137
137
138 $ hg githelp -- git checkout -p xyz -- abc
138 $ hg githelp -- git checkout -p xyz -- abc
139 note: use --no-backup to avoid creating .orig files
139 note: use --no-backup to avoid creating .orig files
140
140
141 hg revert -i -r xyz abc
141 hg revert -i -r xyz abc
142
142
143 githelp for checkout with the -f option and a rev
143 githelp for checkout with the -f option and a rev
144 $ hg githelp -- git checkout -f xyz
144 $ hg githelp -- git checkout -f xyz
145 hg update -C xyz
145 hg update -C xyz
146 $ hg githelp -- git checkout --force xyz
146 $ hg githelp -- git checkout --force xyz
147 hg update -C xyz
147 hg update -C xyz
148
148
149 githelp for checkout with the -f option without an arg
149 githelp for checkout with the -f option without an arg
150 $ hg githelp -- git checkout -f
150 $ hg githelp -- git checkout -f
151 hg revert --all
151 hg revert --all
152 $ hg githelp -- git checkout --force
152 $ hg githelp -- git checkout --force
153 hg revert --all
153 hg revert --all
154
154
155 githelp for grep with pattern and path
155 githelp for grep with pattern and path
156 $ hg githelp -- grep shrubbery flib/intern/
156 $ hg githelp -- grep shrubbery flib/intern/
157 hg grep shrubbery flib/intern/
157 hg grep shrubbery flib/intern/
158
158
159 githelp for reset, checking ~ in git becomes ~1 in mercurial
159 githelp for reset, checking ~ in git becomes ~1 in mercurial
160 $ hg githelp -- reset HEAD~
160 $ hg githelp -- reset HEAD~
161 hg update .~1
161 hg update .~1
162 $ hg githelp -- reset "HEAD^"
162 $ hg githelp -- reset "HEAD^"
163 hg update .^
163 hg update .^
164 $ hg githelp -- reset HEAD~3
164 $ hg githelp -- reset HEAD~3
165 hg update .~3
165 hg update .~3
166
166
167 $ hg githelp -- reset --mixed HEAD
167 $ hg githelp -- reset --mixed HEAD
168 note: --mixed has no meaning since Mercurial has no staging area
168 note: --mixed has no meaning since Mercurial has no staging area
169
169
170 hg update .
170 hg update .
171 $ hg githelp -- reset --soft HEAD
171 $ hg githelp -- reset --soft HEAD
172 note: --soft has no meaning since Mercurial has no staging area
172 note: --soft has no meaning since Mercurial has no staging area
173
173
174 hg update .
174 hg update .
175 $ hg githelp -- reset --hard HEAD
175 $ hg githelp -- reset --hard HEAD
176 hg update --clean .
176 hg update --clean .
177
177
178 githelp for git show --name-status
178 githelp for git show --name-status
179 $ hg githelp -- git show --name-status
179 $ hg githelp -- git show --name-status
180 hg log --style status -r .
180 hg log --style status -r .
181
181
182 githelp for git show --pretty=format: --name-status
182 githelp for git show --pretty=format: --name-status
183 $ hg githelp -- git show --pretty=format: --name-status
183 $ hg githelp -- git show --pretty=format: --name-status
184 hg status --change .
184 hg status --change .
185
185
186 githelp for show with no arguments
186 githelp for show with no arguments
187 $ hg githelp -- show
187 $ hg githelp -- show
188 hg export
188 hg export
189
189
190 githelp for show with a path
190 githelp for show with a path
191 $ hg githelp -- show test_file
191 $ hg githelp -- show test_file
192 hg cat test_file
192 hg cat test_file
193
193
194 githelp for show with not a path:
194 githelp for show with not a path:
195 $ hg githelp -- show rev
195 $ hg githelp -- show rev
196 hg export rev
196 hg export rev
197
197
198 githelp for show with many arguments
198 githelp for show with many arguments
199 $ hg githelp -- show argone argtwo
199 $ hg githelp -- show argone argtwo
200 hg export argone argtwo
200 hg export argone argtwo
201 $ hg githelp -- show test_file argone argtwo
201 $ hg githelp -- show test_file argone argtwo
202 hg cat test_file argone argtwo
202 hg cat test_file argone argtwo
203
203
204 githelp for show with --unified options
204 githelp for show with --unified options
205 $ hg githelp -- show --unified=10
205 $ hg githelp -- show --unified=10
206 hg export --config diff.unified=10
206 hg export --config diff.unified=10
207 $ hg githelp -- show -U100
207 $ hg githelp -- show -U100
208 hg export --config diff.unified=100
208 hg export --config diff.unified=100
209
209
210 githelp for show with a path and --unified
210 githelp for show with a path and --unified
211 $ hg githelp -- show -U20 test_file
211 $ hg githelp -- show -U20 test_file
212 hg cat test_file --config diff.unified=20
212 hg cat test_file --config diff.unified=20
213
213
214 githelp for stash drop without name
214 githelp for stash drop without name
215 $ hg githelp -- git stash drop
215 $ hg githelp -- git stash drop
216 hg shelve -d <shelve name>
216 hg shelve -d <shelve name>
217
217
218 githelp for stash drop with name
218 githelp for stash drop with name
219 $ hg githelp -- git stash drop xyz
219 $ hg githelp -- git stash drop xyz
220 hg shelve -d xyz
220 hg shelve -d xyz
221
221
222 githelp for stash list with patch
222 githelp for stash list with patch
223 $ hg githelp -- git stash list -p
223 $ hg githelp -- git stash list -p
224 hg shelve -l -p
224 hg shelve -l -p
225
225
226 githelp for stash show
226 githelp for stash show
227 $ hg githelp -- git stash show
227 $ hg githelp -- git stash show
228 hg shelve --stat
228 hg shelve --stat
229
229
230 githelp for stash show with patch and name
230 githelp for stash show with patch and name
231 $ hg githelp -- git stash show -p mystash
231 $ hg githelp -- git stash show -p mystash
232 hg shelve -p mystash
232 hg shelve -p mystash
233
233
234 githelp for stash clear
234 githelp for stash clear
235 $ hg githelp -- git stash clear
235 $ hg githelp -- git stash clear
236 hg shelve --cleanup
236 hg shelve --cleanup
237
237
238 githelp for whatchanged should show deprecated message
238 githelp for whatchanged should show deprecated message
239 $ hg githelp -- whatchanged -p
239 $ hg githelp -- whatchanged -p
240 this command has been deprecated in the git project, thus isn't supported by this tool
240 this command has been deprecated in the git project, thus isn't supported by this tool
241
241
242
242
243 githelp for git branch -m renaming
243 githelp for git branch -m renaming
244 $ hg githelp -- git branch -m old new
244 $ hg githelp -- git branch -m old new
245 hg bookmark -m old new
245 hg bookmark -m old new
246
246
247 When the old name is omitted, git branch -m new renames the current branch.
247 When the old name is omitted, git branch -m new renames the current branch.
248 $ hg githelp -- git branch -m new
248 $ hg githelp -- git branch -m new
249 hg bookmark -m `hg log -T"{activebookmark}" -r .` new
249 hg bookmark -m `hg log -T"{activebookmark}" -r .` new
250
250
251 Branch deletion in git strips commits
251 Branch deletion in git strips commits
252 $ hg githelp -- git branch -d
252 $ hg githelp -- git branch -d
253 hg strip -B
253 hg strip -B
254 $ hg githelp -- git branch -d feature
254 $ hg githelp -- git branch -d feature
255 hg strip -B feature -B
255 hg strip -B feature -B
256 $ hg githelp -- git branch --delete experiment1 experiment2
256 $ hg githelp -- git branch --delete experiment1 experiment2
257 hg strip -B experiment1 -B experiment2 -B
257 hg strip -B experiment1 -B experiment2 -B
258
258
259 githelp for reuse message using the shorthand
259 githelp for reuse message using the shorthand
260 $ hg githelp -- git commit -C deadbeef
260 $ hg githelp -- git commit -C deadbeef
261 hg commit -M deadbeef
261 hg commit -M deadbeef
262
262
263 githelp for reuse message using the the long version
263 githelp for reuse message using the the long version
264 $ hg githelp -- git commit --reuse-message deadbeef
264 $ hg githelp -- git commit --reuse-message deadbeef
265 hg commit -M deadbeef
265 hg commit -M deadbeef
266
266
267 githelp for reuse message using HEAD
268 $ hg githelp -- git commit --reuse-message HEAD~
269 hg commit -M .~1
270
267 githelp for apply with no options
271 githelp for apply with no options
268 $ hg githelp -- apply
272 $ hg githelp -- apply
269 hg import --no-commit
273 hg import --no-commit
270
274
271 githelp for apply with directory strip custom
275 githelp for apply with directory strip custom
272 $ hg githelp -- apply -p 5
276 $ hg githelp -- apply -p 5
273 hg import --no-commit -p 5
277 hg import --no-commit -p 5
274
278
275 githelp for apply with prefix directory
279 githelp for apply with prefix directory
276 $ hg githelp -- apply --directory=modules
280 $ hg githelp -- apply --directory=modules
277 hg import --no-commit --prefix modules
281 hg import --no-commit --prefix modules
278
282
279 git merge-base
283 git merge-base
280 $ hg githelp -- git merge-base --is-ancestor
284 $ hg githelp -- git merge-base --is-ancestor
281 ignoring unknown option --is-ancestor
285 ignoring unknown option --is-ancestor
282 note: ancestors() is part of the revset language
286 note: ancestors() is part of the revset language
283 (learn more about revsets with 'hg help revsets')
287 (learn more about revsets with 'hg help revsets')
284
288
285 hg log -T '{node}\n' -r 'ancestor(A,B)'
289 hg log -T '{node}\n' -r 'ancestor(A,B)'
286
290
287 githelp for git blame
291 githelp for git blame
288 $ hg githelp -- git blame
292 $ hg githelp -- git blame
289 hg annotate -udl
293 hg annotate -udl
290
294
291 githelp for add
295 githelp for add
292
296
293 $ hg githelp -- git add
297 $ hg githelp -- git add
294 hg add
298 hg add
295
299
296 $ hg githelp -- git add -p
300 $ hg githelp -- git add -p
297 note: Mercurial will commit when complete, as there is no staging area in Mercurial
301 note: Mercurial will commit when complete, as there is no staging area in Mercurial
298
302
299 hg commit --interactive
303 hg commit --interactive
300
304
301 $ hg githelp -- git add --all
305 $ hg githelp -- git add --all
302 note: use hg addremove to remove files that have been deleted
306 note: use hg addremove to remove files that have been deleted
303
307
304 hg add
308 hg add
305
309
306 githelp for reflog
310 githelp for reflog
307
311
308 $ hg githelp -- git reflog
312 $ hg githelp -- git reflog
309 hg journal
313 hg journal
310
314
311 note: in hg commits can be deleted from repo but we always have backups
315 note: in hg commits can be deleted from repo but we always have backups
312
316
313 $ hg githelp -- git reflog --all
317 $ hg githelp -- git reflog --all
314 hg journal --all
318 hg journal --all
315
319
316 note: in hg commits can be deleted from repo but we always have backups
320 note: in hg commits can be deleted from repo but we always have backups
@@ -1,1028 +1,1153 b''
1 $ cat >> $HGRCPATH << EOF
1 $ cat >> $HGRCPATH << EOF
2 > [diff]
2 > [diff]
3 > git = true
3 > git = true
4 > EOF
4 > EOF
5
5
6 $ hg init
6 $ hg init
7 $ cat > foo << EOF
7 $ cat > foo << EOF
8 > 0
8 > 0
9 > 1
9 > 1
10 > 2
10 > 2
11 > 3
11 > 3
12 > 4
12 > 4
13 > EOF
13 > EOF
14 $ hg ci -Am init
14 $ hg ci -Am init
15 adding foo
15 adding foo
16 $ cat > foo << EOF
16 $ cat > foo << EOF
17 > 0
17 > 0
18 > 0
18 > 0
19 > 0
19 > 0
20 > 0
20 > 0
21 > 1
21 > 1
22 > 2
22 > 2
23 > 3
23 > 3
24 > 4
24 > 4
25 > EOF
25 > EOF
26 $ hg ci -m 'more 0'
26 $ hg ci -m 'more 0'
27 $ sed 's/2/2+/' foo > foo.new
27 $ sed 's/2/2+/' foo > foo.new
28 $ mv foo.new foo
28 $ mv foo.new foo
29 $ cat > bar << EOF
29 $ cat > bar << EOF
30 > a
30 > a
31 > b
31 > b
32 > c
32 > c
33 > d
33 > d
34 > e
34 > e
35 > EOF
35 > EOF
36 $ hg add bar
36 $ hg add bar
37 $ hg ci -Am "2 -> 2+; added bar"
37 $ hg ci -Am "2 -> 2+; added bar"
38 $ cat >> foo << EOF
38 $ cat >> foo << EOF
39 > 5
39 > 5
40 > 6
40 > 6
41 > 7
41 > 7
42 > 8
42 > 8
43 > 9
43 > 9
44 > 10
44 > 10
45 > 11
45 > 11
46 > EOF
46 > EOF
47 $ hg ci -m "to 11"
47 $ hg ci -m "to 11"
48
48
49 Add some changes with two diff hunks
49 Add some changes with two diff hunks
50
50
51 $ sed 's/^1$/ 1/' foo > foo.new
51 $ sed 's/^1$/ 1/' foo > foo.new
52 $ mv foo.new foo
52 $ mv foo.new foo
53 $ sed 's/^11$/11+/' foo > foo.new
53 $ sed 's/^11$/11+/' foo > foo.new
54 $ mv foo.new foo
54 $ mv foo.new foo
55 $ hg ci -m '11 -> 11+; leading space before "1"'
55 $ hg ci -m '11 -> 11+; leading space before "1"'
56 (make sure there are two hunks in "foo")
56 (make sure there are two hunks in "foo")
57 $ hg diff -c .
57 $ hg diff -c .
58 diff --git a/foo b/foo
58 diff --git a/foo b/foo
59 --- a/foo
59 --- a/foo
60 +++ b/foo
60 +++ b/foo
61 @@ -2,7 +2,7 @@
61 @@ -2,7 +2,7 @@
62 0
62 0
63 0
63 0
64 0
64 0
65 -1
65 -1
66 + 1
66 + 1
67 2+
67 2+
68 3
68 3
69 4
69 4
70 @@ -12,4 +12,4 @@
70 @@ -12,4 +12,4 @@
71 8
71 8
72 9
72 9
73 10
73 10
74 -11
74 -11
75 +11+
75 +11+
76 $ sed 's/3/3+/' foo > foo.new
76 $ sed 's/3/3+/' foo > foo.new
77 $ mv foo.new foo
77 $ mv foo.new foo
78 $ sed 's/^11+$/11-/' foo > foo.new
78 $ sed 's/^11+$/11-/' foo > foo.new
79 $ mv foo.new foo
79 $ mv foo.new foo
80 $ sed 's/a/a+/' bar > bar.new
80 $ sed 's/a/a+/' bar > bar.new
81 $ mv bar.new bar
81 $ mv bar.new bar
82 $ hg ci -m 'foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+'
82 $ hg ci -m 'foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+'
83 (make sure there are two hunks in "foo")
83 (make sure there are two hunks in "foo")
84 $ hg diff -c . foo
84 $ hg diff -c . foo
85 diff --git a/foo b/foo
85 diff --git a/foo b/foo
86 --- a/foo
86 --- a/foo
87 +++ b/foo
87 +++ b/foo
88 @@ -4,7 +4,7 @@
88 @@ -4,7 +4,7 @@
89 0
89 0
90 1
90 1
91 2+
91 2+
92 -3
92 -3
93 +3+
93 +3+
94 4
94 4
95 5
95 5
96 6
96 6
97 @@ -12,4 +12,4 @@
97 @@ -12,4 +12,4 @@
98 8
98 8
99 9
99 9
100 10
100 10
101 -11+
101 -11+
102 +11-
102 +11-
103
103
104 $ hg log -f -L foo,5:7 -p
104 $ hg log -f -L foo,5:7 -p
105 changeset: 5:cfdf972b3971
105 changeset: 5:cfdf972b3971
106 tag: tip
106 tag: tip
107 user: test
107 user: test
108 date: Thu Jan 01 00:00:00 1970 +0000
108 date: Thu Jan 01 00:00:00 1970 +0000
109 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
109 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
110
110
111 diff --git a/foo b/foo
111 diff --git a/foo b/foo
112 --- a/foo
112 --- a/foo
113 +++ b/foo
113 +++ b/foo
114 @@ -4,7 +4,7 @@
114 @@ -4,7 +4,7 @@
115 0
115 0
116 1
116 1
117 2+
117 2+
118 -3
118 -3
119 +3+
119 +3+
120 4
120 4
121 5
121 5
122 6
122 6
123
123
124 changeset: 4:eaec41c1a0c9
124 changeset: 4:eaec41c1a0c9
125 user: test
125 user: test
126 date: Thu Jan 01 00:00:00 1970 +0000
126 date: Thu Jan 01 00:00:00 1970 +0000
127 summary: 11 -> 11+; leading space before "1"
127 summary: 11 -> 11+; leading space before "1"
128
128
129 diff --git a/foo b/foo
129 diff --git a/foo b/foo
130 --- a/foo
130 --- a/foo
131 +++ b/foo
131 +++ b/foo
132 @@ -2,7 +2,7 @@
132 @@ -2,7 +2,7 @@
133 0
133 0
134 0
134 0
135 0
135 0
136 -1
136 -1
137 + 1
137 + 1
138 2+
138 2+
139 3
139 3
140 4
140 4
141
141
142 changeset: 2:63a884426fd0
142 changeset: 2:63a884426fd0
143 user: test
143 user: test
144 date: Thu Jan 01 00:00:00 1970 +0000
144 date: Thu Jan 01 00:00:00 1970 +0000
145 summary: 2 -> 2+; added bar
145 summary: 2 -> 2+; added bar
146
146
147 diff --git a/foo b/foo
147 diff --git a/foo b/foo
148 --- a/foo
148 --- a/foo
149 +++ b/foo
149 +++ b/foo
150 @@ -3,6 +3,6 @@
150 @@ -3,6 +3,6 @@
151 0
151 0
152 0
152 0
153 1
153 1
154 -2
154 -2
155 +2+
155 +2+
156 3
156 3
157 4
157 4
158
158
159 changeset: 0:5ae1f82b9a00
159 changeset: 0:5ae1f82b9a00
160 user: test
160 user: test
161 date: Thu Jan 01 00:00:00 1970 +0000
161 date: Thu Jan 01 00:00:00 1970 +0000
162 summary: init
162 summary: init
163
163
164 diff --git a/foo b/foo
164 diff --git a/foo b/foo
165 new file mode 100644
165 new file mode 100644
166 --- /dev/null
166 --- /dev/null
167 +++ b/foo
167 +++ b/foo
168 @@ -0,0 +1,5 @@
168 @@ -0,0 +1,5 @@
169 +0
169 +0
170 +1
170 +1
171 +2
171 +2
172 +3
172 +3
173 +4
173 +4
174
174
175 $ hg log -f --graph -L foo,5:7 -p
175 $ hg log -f --graph -L foo,5:7 -p
176 @ changeset: 5:cfdf972b3971
176 @ changeset: 5:cfdf972b3971
177 | tag: tip
177 | tag: tip
178 | user: test
178 | user: test
179 | date: Thu Jan 01 00:00:00 1970 +0000
179 | date: Thu Jan 01 00:00:00 1970 +0000
180 | summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
180 | summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
181 |
181 |
182 | diff --git a/foo b/foo
182 | diff --git a/foo b/foo
183 | --- a/foo
183 | --- a/foo
184 | +++ b/foo
184 | +++ b/foo
185 | @@ -4,7 +4,7 @@
185 | @@ -4,7 +4,7 @@
186 | 0
186 | 0
187 | 1
187 | 1
188 | 2+
188 | 2+
189 | -3
189 | -3
190 | +3+
190 | +3+
191 | 4
191 | 4
192 | 5
192 | 5
193 | 6
193 | 6
194 |
194 |
195 o changeset: 4:eaec41c1a0c9
195 o changeset: 4:eaec41c1a0c9
196 : user: test
196 : user: test
197 : date: Thu Jan 01 00:00:00 1970 +0000
197 : date: Thu Jan 01 00:00:00 1970 +0000
198 : summary: 11 -> 11+; leading space before "1"
198 : summary: 11 -> 11+; leading space before "1"
199 :
199 :
200 : diff --git a/foo b/foo
200 : diff --git a/foo b/foo
201 : --- a/foo
201 : --- a/foo
202 : +++ b/foo
202 : +++ b/foo
203 : @@ -2,7 +2,7 @@
203 : @@ -2,7 +2,7 @@
204 : 0
204 : 0
205 : 0
205 : 0
206 : 0
206 : 0
207 : -1
207 : -1
208 : + 1
208 : + 1
209 : 2+
209 : 2+
210 : 3
210 : 3
211 : 4
211 : 4
212 :
212 :
213 o changeset: 2:63a884426fd0
213 o changeset: 2:63a884426fd0
214 : user: test
214 : user: test
215 : date: Thu Jan 01 00:00:00 1970 +0000
215 : date: Thu Jan 01 00:00:00 1970 +0000
216 : summary: 2 -> 2+; added bar
216 : summary: 2 -> 2+; added bar
217 :
217 :
218 : diff --git a/foo b/foo
218 : diff --git a/foo b/foo
219 : --- a/foo
219 : --- a/foo
220 : +++ b/foo
220 : +++ b/foo
221 : @@ -3,6 +3,6 @@
221 : @@ -3,6 +3,6 @@
222 : 0
222 : 0
223 : 0
223 : 0
224 : 1
224 : 1
225 : -2
225 : -2
226 : +2+
226 : +2+
227 : 3
227 : 3
228 : 4
228 : 4
229 :
229 :
230 o changeset: 0:5ae1f82b9a00
230 o changeset: 0:5ae1f82b9a00
231 user: test
231 user: test
232 date: Thu Jan 01 00:00:00 1970 +0000
232 date: Thu Jan 01 00:00:00 1970 +0000
233 summary: init
233 summary: init
234
234
235 diff --git a/foo b/foo
235 diff --git a/foo b/foo
236 new file mode 100644
236 new file mode 100644
237 --- /dev/null
237 --- /dev/null
238 +++ b/foo
238 +++ b/foo
239 @@ -0,0 +1,5 @@
239 @@ -0,0 +1,5 @@
240 +0
240 +0
241 +1
241 +1
242 +2
242 +2
243 +3
243 +3
244 +4
244 +4
245
245
246
246
247 With --template.
247 With --template.
248
248
249 $ hg log -f -L foo,5:7 -T '{rev}:{node|short} {desc|firstline}\n'
249 $ hg log -f -L foo,5:7 -T '{rev}:{node|short} {desc|firstline}\n'
250 5:cfdf972b3971 foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
250 5:cfdf972b3971 foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
251 4:eaec41c1a0c9 11 -> 11+; leading space before "1"
251 4:eaec41c1a0c9 11 -> 11+; leading space before "1"
252 2:63a884426fd0 2 -> 2+; added bar
252 2:63a884426fd0 2 -> 2+; added bar
253 0:5ae1f82b9a00 init
253 0:5ae1f82b9a00 init
254 $ hg log -f -L foo,5:7 -T json
254 $ hg log -f -L foo,5:7 -T json
255 [
255 [
256 {
256 {
257 "bookmarks": [],
257 "bookmarks": [],
258 "branch": "default",
258 "branch": "default",
259 "date": [0, 0],
259 "date": [0, 0],
260 "desc": "foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+",
260 "desc": "foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+",
261 "node": "cfdf972b3971a2a59638bf9583c0debbffee5404",
261 "node": "cfdf972b3971a2a59638bf9583c0debbffee5404",
262 "parents": ["eaec41c1a0c9ad0a5e999611d0149d171beffb8c"],
262 "parents": ["eaec41c1a0c9ad0a5e999611d0149d171beffb8c"],
263 "phase": "draft",
263 "phase": "draft",
264 "rev": 5,
264 "rev": 5,
265 "tags": ["tip"],
265 "tags": ["tip"],
266 "user": "test"
266 "user": "test"
267 },
267 },
268 {
268 {
269 "bookmarks": [],
269 "bookmarks": [],
270 "branch": "default",
270 "branch": "default",
271 "date": [0, 0],
271 "date": [0, 0],
272 "desc": "11 -> 11+; leading space before \"1\"",
272 "desc": "11 -> 11+; leading space before \"1\"",
273 "node": "eaec41c1a0c9ad0a5e999611d0149d171beffb8c",
273 "node": "eaec41c1a0c9ad0a5e999611d0149d171beffb8c",
274 "parents": ["730a61fbaecf426c17c2c66bc42d195b5d5b0ba8"],
274 "parents": ["730a61fbaecf426c17c2c66bc42d195b5d5b0ba8"],
275 "phase": "draft",
275 "phase": "draft",
276 "rev": 4,
276 "rev": 4,
277 "tags": [],
277 "tags": [],
278 "user": "test"
278 "user": "test"
279 },
279 },
280 {
280 {
281 "bookmarks": [],
281 "bookmarks": [],
282 "branch": "default",
282 "branch": "default",
283 "date": [0, 0],
283 "date": [0, 0],
284 "desc": "2 -> 2+; added bar",
284 "desc": "2 -> 2+; added bar",
285 "node": "63a884426fd0b277fcd55895bbb2f230434576eb",
285 "node": "63a884426fd0b277fcd55895bbb2f230434576eb",
286 "parents": ["29a1e7c6b80024f63f310a2d71de979e9d2996d7"],
286 "parents": ["29a1e7c6b80024f63f310a2d71de979e9d2996d7"],
287 "phase": "draft",
287 "phase": "draft",
288 "rev": 2,
288 "rev": 2,
289 "tags": [],
289 "tags": [],
290 "user": "test"
290 "user": "test"
291 },
291 },
292 {
292 {
293 "bookmarks": [],
293 "bookmarks": [],
294 "branch": "default",
294 "branch": "default",
295 "date": [0, 0],
295 "date": [0, 0],
296 "desc": "init",
296 "desc": "init",
297 "node": "5ae1f82b9a000ff1e0967d0dac1c58b9d796e1b4",
297 "node": "5ae1f82b9a000ff1e0967d0dac1c58b9d796e1b4",
298 "parents": ["0000000000000000000000000000000000000000"],
298 "parents": ["0000000000000000000000000000000000000000"],
299 "phase": "draft",
299 "phase": "draft",
300 "rev": 0,
300 "rev": 0,
301 "tags": [],
301 "tags": [],
302 "user": "test"
302 "user": "test"
303 }
303 }
304 ]
304 ]
305
305
306 With some white-space diff option, respective revisions are skipped.
306 With some white-space diff option, respective revisions are skipped.
307
307
308 $ hg log -f -L foo,5:7 -p --config diff.ignorews=true
308 $ hg log -f -L foo,5:7 -p --config diff.ignorews=true
309 changeset: 5:cfdf972b3971
309 changeset: 5:cfdf972b3971
310 tag: tip
310 tag: tip
311 user: test
311 user: test
312 date: Thu Jan 01 00:00:00 1970 +0000
312 date: Thu Jan 01 00:00:00 1970 +0000
313 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
313 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
314
314
315 diff --git a/foo b/foo
315 diff --git a/foo b/foo
316 --- a/foo
316 --- a/foo
317 +++ b/foo
317 +++ b/foo
318 @@ -4,7 +4,7 @@
318 @@ -4,7 +4,7 @@
319 0
319 0
320 1
320 1
321 2+
321 2+
322 -3
322 -3
323 +3+
323 +3+
324 4
324 4
325 5
325 5
326 6
326 6
327
327
328 changeset: 2:63a884426fd0
328 changeset: 2:63a884426fd0
329 user: test
329 user: test
330 date: Thu Jan 01 00:00:00 1970 +0000
330 date: Thu Jan 01 00:00:00 1970 +0000
331 summary: 2 -> 2+; added bar
331 summary: 2 -> 2+; added bar
332
332
333 diff --git a/foo b/foo
333 diff --git a/foo b/foo
334 --- a/foo
334 --- a/foo
335 +++ b/foo
335 +++ b/foo
336 @@ -3,6 +3,6 @@
336 @@ -3,6 +3,6 @@
337 0
337 0
338 0
338 0
339 1
339 1
340 -2
340 -2
341 +2+
341 +2+
342 3
342 3
343 4
343 4
344
344
345 changeset: 0:5ae1f82b9a00
345 changeset: 0:5ae1f82b9a00
346 user: test
346 user: test
347 date: Thu Jan 01 00:00:00 1970 +0000
347 date: Thu Jan 01 00:00:00 1970 +0000
348 summary: init
348 summary: init
349
349
350 diff --git a/foo b/foo
350 diff --git a/foo b/foo
351 new file mode 100644
351 new file mode 100644
352 --- /dev/null
352 --- /dev/null
353 +++ b/foo
353 +++ b/foo
354 @@ -0,0 +1,5 @@
354 @@ -0,0 +1,5 @@
355 +0
355 +0
356 +1
356 +1
357 +2
357 +2
358 +3
358 +3
359 +4
359 +4
360
360
361
361
362 Regular file patterns are not allowed.
362 Regular file patterns are not allowed.
363
363
364 $ hg log -f -L foo,5:7 -p bar
364 $ hg log -f -L foo,5:7 -p bar
365 abort: FILE arguments are not compatible with --line-range option
365 abort: FILE arguments are not compatible with --line-range option
366 [255]
366 [255]
367
367
368 Option --rev acts as a restriction.
368 Option --rev acts as a restriction.
369
369
370 $ hg log -f -L foo,5:7 -p -r 'desc(2)'
370 $ hg log -f -L foo,5:7 -p -r 'desc(2)'
371 changeset: 2:63a884426fd0
371 changeset: 2:63a884426fd0
372 user: test
372 user: test
373 date: Thu Jan 01 00:00:00 1970 +0000
373 date: Thu Jan 01 00:00:00 1970 +0000
374 summary: 2 -> 2+; added bar
374 summary: 2 -> 2+; added bar
375
375
376 diff --git a/foo b/foo
376 diff --git a/foo b/foo
377 --- a/foo
377 --- a/foo
378 +++ b/foo
378 +++ b/foo
379 @@ -3,6 +3,6 @@
379 @@ -3,6 +3,6 @@
380 0
380 0
381 0
381 0
382 1
382 1
383 -2
383 -2
384 +2+
384 +2+
385 3
385 3
386 4
386 4
387
387
388 changeset: 0:5ae1f82b9a00
388 changeset: 0:5ae1f82b9a00
389 user: test
389 user: test
390 date: Thu Jan 01 00:00:00 1970 +0000
390 date: Thu Jan 01 00:00:00 1970 +0000
391 summary: init
391 summary: init
392
392
393 diff --git a/foo b/foo
393 diff --git a/foo b/foo
394 new file mode 100644
394 new file mode 100644
395 --- /dev/null
395 --- /dev/null
396 +++ b/foo
396 +++ b/foo
397 @@ -0,0 +1,5 @@
397 @@ -0,0 +1,5 @@
398 +0
398 +0
399 +1
399 +1
400 +2
400 +2
401 +3
401 +3
402 +4
402 +4
403
403
404
404
405 With several -L patterns, changes touching any files in their respective line
405 With several -L patterns, changes touching any files in their respective line
406 range are show.
406 range are show.
407
407
408 $ hg log -f -L foo,5:7 -L bar,1:2 -p
408 $ hg log -f -L foo,5:7 -L bar,1:2 -p
409 changeset: 5:cfdf972b3971
409 changeset: 5:cfdf972b3971
410 tag: tip
410 tag: tip
411 user: test
411 user: test
412 date: Thu Jan 01 00:00:00 1970 +0000
412 date: Thu Jan 01 00:00:00 1970 +0000
413 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
413 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
414
414
415 diff --git a/bar b/bar
415 diff --git a/bar b/bar
416 --- a/bar
416 --- a/bar
417 +++ b/bar
417 +++ b/bar
418 @@ -1,4 +1,4 @@
418 @@ -1,4 +1,4 @@
419 -a
419 -a
420 +a+
420 +a+
421 b
421 b
422 c
422 c
423 d
423 d
424 diff --git a/foo b/foo
424 diff --git a/foo b/foo
425 --- a/foo
425 --- a/foo
426 +++ b/foo
426 +++ b/foo
427 @@ -4,7 +4,7 @@
427 @@ -4,7 +4,7 @@
428 0
428 0
429 1
429 1
430 2+
430 2+
431 -3
431 -3
432 +3+
432 +3+
433 4
433 4
434 5
434 5
435 6
435 6
436
436
437 changeset: 4:eaec41c1a0c9
437 changeset: 4:eaec41c1a0c9
438 user: test
438 user: test
439 date: Thu Jan 01 00:00:00 1970 +0000
439 date: Thu Jan 01 00:00:00 1970 +0000
440 summary: 11 -> 11+; leading space before "1"
440 summary: 11 -> 11+; leading space before "1"
441
441
442 diff --git a/foo b/foo
442 diff --git a/foo b/foo
443 --- a/foo
443 --- a/foo
444 +++ b/foo
444 +++ b/foo
445 @@ -2,7 +2,7 @@
445 @@ -2,7 +2,7 @@
446 0
446 0
447 0
447 0
448 0
448 0
449 -1
449 -1
450 + 1
450 + 1
451 2+
451 2+
452 3
452 3
453 4
453 4
454
454
455 changeset: 2:63a884426fd0
455 changeset: 2:63a884426fd0
456 user: test
456 user: test
457 date: Thu Jan 01 00:00:00 1970 +0000
457 date: Thu Jan 01 00:00:00 1970 +0000
458 summary: 2 -> 2+; added bar
458 summary: 2 -> 2+; added bar
459
459
460 diff --git a/bar b/bar
460 diff --git a/bar b/bar
461 new file mode 100644
461 new file mode 100644
462 --- /dev/null
462 --- /dev/null
463 +++ b/bar
463 +++ b/bar
464 @@ -0,0 +1,5 @@
464 @@ -0,0 +1,5 @@
465 +a
465 +a
466 +b
466 +b
467 +c
467 +c
468 +d
468 +d
469 +e
469 +e
470 diff --git a/foo b/foo
470 diff --git a/foo b/foo
471 --- a/foo
471 --- a/foo
472 +++ b/foo
472 +++ b/foo
473 @@ -3,6 +3,6 @@
473 @@ -3,6 +3,6 @@
474 0
474 0
475 0
475 0
476 1
476 1
477 -2
477 -2
478 +2+
478 +2+
479 3
479 3
480 4
480 4
481
481
482 changeset: 0:5ae1f82b9a00
482 changeset: 0:5ae1f82b9a00
483 user: test
483 user: test
484 date: Thu Jan 01 00:00:00 1970 +0000
484 date: Thu Jan 01 00:00:00 1970 +0000
485 summary: init
485 summary: init
486
486
487 diff --git a/foo b/foo
487 diff --git a/foo b/foo
488 new file mode 100644
488 new file mode 100644
489 --- /dev/null
489 --- /dev/null
490 +++ b/foo
490 +++ b/foo
491 @@ -0,0 +1,5 @@
491 @@ -0,0 +1,5 @@
492 +0
492 +0
493 +1
493 +1
494 +2
494 +2
495 +3
495 +3
496 +4
496 +4
497
497
498
498
499 Multiple -L options with the same file yields changes touching any of
499 Multiple -L options with the same file yields changes touching any of
500 specified line ranges.
500 specified line ranges.
501
501
502 $ hg log -f -L foo,5:7 -L foo,14:15 -p
502 $ hg log -f -L foo,5:7 -L foo,14:15 -p
503 changeset: 5:cfdf972b3971
503 changeset: 5:cfdf972b3971
504 tag: tip
504 tag: tip
505 user: test
505 user: test
506 date: Thu Jan 01 00:00:00 1970 +0000
506 date: Thu Jan 01 00:00:00 1970 +0000
507 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
507 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
508
508
509 diff --git a/foo b/foo
509 diff --git a/foo b/foo
510 --- a/foo
510 --- a/foo
511 +++ b/foo
511 +++ b/foo
512 @@ -4,7 +4,7 @@
512 @@ -4,7 +4,7 @@
513 0
513 0
514 1
514 1
515 2+
515 2+
516 -3
516 -3
517 +3+
517 +3+
518 4
518 4
519 5
519 5
520 6
520 6
521 @@ -12,4 +12,4 @@
521 @@ -12,4 +12,4 @@
522 8
522 8
523 9
523 9
524 10
524 10
525 -11+
525 -11+
526 +11-
526 +11-
527
527
528 changeset: 4:eaec41c1a0c9
528 changeset: 4:eaec41c1a0c9
529 user: test
529 user: test
530 date: Thu Jan 01 00:00:00 1970 +0000
530 date: Thu Jan 01 00:00:00 1970 +0000
531 summary: 11 -> 11+; leading space before "1"
531 summary: 11 -> 11+; leading space before "1"
532
532
533 diff --git a/foo b/foo
533 diff --git a/foo b/foo
534 --- a/foo
534 --- a/foo
535 +++ b/foo
535 +++ b/foo
536 @@ -2,7 +2,7 @@
536 @@ -2,7 +2,7 @@
537 0
537 0
538 0
538 0
539 0
539 0
540 -1
540 -1
541 + 1
541 + 1
542 2+
542 2+
543 3
543 3
544 4
544 4
545 @@ -12,4 +12,4 @@
545 @@ -12,4 +12,4 @@
546 8
546 8
547 9
547 9
548 10
548 10
549 -11
549 -11
550 +11+
550 +11+
551
551
552 changeset: 3:730a61fbaecf
552 changeset: 3:730a61fbaecf
553 user: test
553 user: test
554 date: Thu Jan 01 00:00:00 1970 +0000
554 date: Thu Jan 01 00:00:00 1970 +0000
555 summary: to 11
555 summary: to 11
556
556
557 diff --git a/foo b/foo
557 diff --git a/foo b/foo
558 --- a/foo
558 --- a/foo
559 +++ b/foo
559 +++ b/foo
560 @@ -6,3 +6,10 @@
560 @@ -6,3 +6,10 @@
561 2+
561 2+
562 3
562 3
563 4
563 4
564 +5
564 +5
565 +6
565 +6
566 +7
566 +7
567 +8
567 +8
568 +9
568 +9
569 +10
569 +10
570 +11
570 +11
571
571
572 changeset: 2:63a884426fd0
572 changeset: 2:63a884426fd0
573 user: test
573 user: test
574 date: Thu Jan 01 00:00:00 1970 +0000
574 date: Thu Jan 01 00:00:00 1970 +0000
575 summary: 2 -> 2+; added bar
575 summary: 2 -> 2+; added bar
576
576
577 diff --git a/foo b/foo
577 diff --git a/foo b/foo
578 --- a/foo
578 --- a/foo
579 +++ b/foo
579 +++ b/foo
580 @@ -3,6 +3,6 @@
580 @@ -3,6 +3,6 @@
581 0
581 0
582 0
582 0
583 1
583 1
584 -2
584 -2
585 +2+
585 +2+
586 3
586 3
587 4
587 4
588
588
589 changeset: 0:5ae1f82b9a00
589 changeset: 0:5ae1f82b9a00
590 user: test
590 user: test
591 date: Thu Jan 01 00:00:00 1970 +0000
591 date: Thu Jan 01 00:00:00 1970 +0000
592 summary: init
592 summary: init
593
593
594 diff --git a/foo b/foo
594 diff --git a/foo b/foo
595 new file mode 100644
595 new file mode 100644
596 --- /dev/null
596 --- /dev/null
597 +++ b/foo
597 +++ b/foo
598 @@ -0,0 +1,5 @@
598 @@ -0,0 +1,5 @@
599 +0
599 +0
600 +1
600 +1
601 +2
601 +2
602 +3
602 +3
603 +4
603 +4
604
604
605
605
606 A file with a comma in its name.
606 A file with a comma in its name.
607
607
608 $ cat > ba,z << EOF
608 $ cat > ba,z << EOF
609 > q
609 > q
610 > w
610 > w
611 > e
611 > e
612 > r
612 > r
613 > t
613 > t
614 > y
614 > y
615 > EOF
615 > EOF
616 $ hg ci -Am 'querty'
616 $ hg ci -Am 'querty'
617 adding ba,z
617 adding ba,z
618 $ cat >> ba,z << EOF
618 $ cat >> ba,z << EOF
619 > u
619 > u
620 > i
620 > i
621 > o
621 > o
622 > p
622 > p
623 > EOF
623 > EOF
624 $ hg ci -m 'more keys'
624 $ hg ci -m 'more keys'
625 $ cat > ba,z << EOF
625 $ cat > ba,z << EOF
626 > a
626 > a
627 > z
627 > z
628 > e
628 > e
629 > r
629 > r
630 > t
630 > t
631 > y
631 > y
632 > u
632 > u
633 > i
633 > i
634 > o
634 > o
635 > p
635 > p
636 > EOF
636 > EOF
637 $ hg ci -m 'azerty'
637 $ hg ci -m 'azerty'
638 $ hg log -f -L ba,z,1:2 -p
638 $ hg log -f -L ba,z,1:2 -p
639 changeset: 8:52373265138b
639 changeset: 8:52373265138b
640 tag: tip
640 tag: tip
641 user: test
641 user: test
642 date: Thu Jan 01 00:00:00 1970 +0000
642 date: Thu Jan 01 00:00:00 1970 +0000
643 summary: azerty
643 summary: azerty
644
644
645 diff --git a/ba,z b/ba,z
645 diff --git a/ba,z b/ba,z
646 --- a/ba,z
646 --- a/ba,z
647 +++ b/ba,z
647 +++ b/ba,z
648 @@ -1,5 +1,5 @@
648 @@ -1,5 +1,5 @@
649 -q
649 -q
650 -w
650 -w
651 +a
651 +a
652 +z
652 +z
653 e
653 e
654 r
654 r
655 t
655 t
656
656
657 changeset: 6:96ba8850f316
657 changeset: 6:96ba8850f316
658 user: test
658 user: test
659 date: Thu Jan 01 00:00:00 1970 +0000
659 date: Thu Jan 01 00:00:00 1970 +0000
660 summary: querty
660 summary: querty
661
661
662 diff --git a/ba,z b/ba,z
662 diff --git a/ba,z b/ba,z
663 new file mode 100644
663 new file mode 100644
664 --- /dev/null
664 --- /dev/null
665 +++ b/ba,z
665 +++ b/ba,z
666 @@ -0,0 +1,6 @@
666 @@ -0,0 +1,6 @@
667 +q
667 +q
668 +w
668 +w
669 +e
669 +e
670 +r
670 +r
671 +t
671 +t
672 +y
672 +y
673
673
674
674
675 Exact prefix kinds work in -L options.
675 Exact prefix kinds work in -L options.
676
676
677 $ mkdir dir
677 $ mkdir dir
678 $ cd dir
678 $ cd dir
679 $ hg log -f -L path:foo,5:7 -p
679 $ hg log -f -L path:foo,5:7 -p
680 changeset: 5:cfdf972b3971
680 changeset: 5:cfdf972b3971
681 user: test
681 user: test
682 date: Thu Jan 01 00:00:00 1970 +0000
682 date: Thu Jan 01 00:00:00 1970 +0000
683 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
683 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
684
684
685 diff --git a/foo b/foo
685 diff --git a/foo b/foo
686 --- a/foo
686 --- a/foo
687 +++ b/foo
687 +++ b/foo
688 @@ -4,7 +4,7 @@
688 @@ -4,7 +4,7 @@
689 0
689 0
690 1
690 1
691 2+
691 2+
692 -3
692 -3
693 +3+
693 +3+
694 4
694 4
695 5
695 5
696 6
696 6
697
697
698 changeset: 4:eaec41c1a0c9
698 changeset: 4:eaec41c1a0c9
699 user: test
699 user: test
700 date: Thu Jan 01 00:00:00 1970 +0000
700 date: Thu Jan 01 00:00:00 1970 +0000
701 summary: 11 -> 11+; leading space before "1"
701 summary: 11 -> 11+; leading space before "1"
702
702
703 diff --git a/foo b/foo
703 diff --git a/foo b/foo
704 --- a/foo
704 --- a/foo
705 +++ b/foo
705 +++ b/foo
706 @@ -2,7 +2,7 @@
706 @@ -2,7 +2,7 @@
707 0
707 0
708 0
708 0
709 0
709 0
710 -1
710 -1
711 + 1
711 + 1
712 2+
712 2+
713 3
713 3
714 4
714 4
715
715
716 changeset: 2:63a884426fd0
716 changeset: 2:63a884426fd0
717 user: test
717 user: test
718 date: Thu Jan 01 00:00:00 1970 +0000
718 date: Thu Jan 01 00:00:00 1970 +0000
719 summary: 2 -> 2+; added bar
719 summary: 2 -> 2+; added bar
720
720
721 diff --git a/foo b/foo
721 diff --git a/foo b/foo
722 --- a/foo
722 --- a/foo
723 +++ b/foo
723 +++ b/foo
724 @@ -3,6 +3,6 @@
724 @@ -3,6 +3,6 @@
725 0
725 0
726 0
726 0
727 1
727 1
728 -2
728 -2
729 +2+
729 +2+
730 3
730 3
731 4
731 4
732
732
733 changeset: 0:5ae1f82b9a00
733 changeset: 0:5ae1f82b9a00
734 user: test
734 user: test
735 date: Thu Jan 01 00:00:00 1970 +0000
735 date: Thu Jan 01 00:00:00 1970 +0000
736 summary: init
736 summary: init
737
737
738 diff --git a/foo b/foo
738 diff --git a/foo b/foo
739 new file mode 100644
739 new file mode 100644
740 --- /dev/null
740 --- /dev/null
741 +++ b/foo
741 +++ b/foo
742 @@ -0,0 +1,5 @@
742 @@ -0,0 +1,5 @@
743 +0
743 +0
744 +1
744 +1
745 +2
745 +2
746 +3
746 +3
747 +4
747 +4
748
748
749
749
750 Renames are followed.
750 Renames are followed.
751
751
752 $ hg mv ../foo baz
752 $ hg mv ../foo baz
753 $ sed 's/1/1+/' baz > baz.new
753 $ sed 's/1/1+/' baz > baz.new
754 $ mv baz.new baz
754 $ mv baz.new baz
755 $ hg ci -m 'foo -> dir/baz; 1-1+'
755 $ hg ci -m 'foo -> dir/baz; 1-1+'
756 $ hg diff -c .
756 $ hg diff -c .
757 diff --git a/foo b/dir/baz
757 diff --git a/foo b/dir/baz
758 rename from foo
758 rename from foo
759 rename to dir/baz
759 rename to dir/baz
760 --- a/foo
760 --- a/foo
761 +++ b/dir/baz
761 +++ b/dir/baz
762 @@ -2,7 +2,7 @@
762 @@ -2,7 +2,7 @@
763 0
763 0
764 0
764 0
765 0
765 0
766 - 1
766 - 1
767 + 1+
767 + 1+
768 2+
768 2+
769 3+
769 3+
770 4
770 4
771 @@ -11,5 +11,5 @@
771 @@ -11,5 +11,5 @@
772 7
772 7
773 8
773 8
774 9
774 9
775 -10
775 -10
776 -11-
776 -11-
777 +1+0
777 +1+0
778 +1+1-
778 +1+1-
779 $ hg log -f -L relpath:baz,5:7 -p
779 $ hg log -f -L relpath:baz,5:7 -p
780 changeset: 9:6af29c3a778f
780 changeset: 9:6af29c3a778f
781 tag: tip
781 tag: tip
782 user: test
782 user: test
783 date: Thu Jan 01 00:00:00 1970 +0000
783 date: Thu Jan 01 00:00:00 1970 +0000
784 summary: foo -> dir/baz; 1-1+
784 summary: foo -> dir/baz; 1-1+
785
785
786 diff --git a/foo b/dir/baz
786 diff --git a/foo b/dir/baz
787 copy from foo
787 copy from foo
788 copy to dir/baz
788 copy to dir/baz
789 --- a/foo
789 --- a/foo
790 +++ b/dir/baz
790 +++ b/dir/baz
791 @@ -2,7 +2,7 @@
791 @@ -2,7 +2,7 @@
792 0
792 0
793 0
793 0
794 0
794 0
795 - 1
795 - 1
796 + 1+
796 + 1+
797 2+
797 2+
798 3+
798 3+
799 4
799 4
800
800
801 changeset: 5:cfdf972b3971
801 changeset: 5:cfdf972b3971
802 user: test
802 user: test
803 date: Thu Jan 01 00:00:00 1970 +0000
803 date: Thu Jan 01 00:00:00 1970 +0000
804 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
804 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
805
805
806 diff --git a/foo b/foo
806 diff --git a/foo b/foo
807 --- a/foo
807 --- a/foo
808 +++ b/foo
808 +++ b/foo
809 @@ -4,7 +4,7 @@
809 @@ -4,7 +4,7 @@
810 0
810 0
811 1
811 1
812 2+
812 2+
813 -3
813 -3
814 +3+
814 +3+
815 4
815 4
816 5
816 5
817 6
817 6
818
818
819 changeset: 4:eaec41c1a0c9
819 changeset: 4:eaec41c1a0c9
820 user: test
820 user: test
821 date: Thu Jan 01 00:00:00 1970 +0000
821 date: Thu Jan 01 00:00:00 1970 +0000
822 summary: 11 -> 11+; leading space before "1"
822 summary: 11 -> 11+; leading space before "1"
823
823
824 diff --git a/foo b/foo
824 diff --git a/foo b/foo
825 --- a/foo
825 --- a/foo
826 +++ b/foo
826 +++ b/foo
827 @@ -2,7 +2,7 @@
827 @@ -2,7 +2,7 @@
828 0
828 0
829 0
829 0
830 0
830 0
831 -1
831 -1
832 + 1
832 + 1
833 2+
833 2+
834 3
834 3
835 4
835 4
836
836
837 changeset: 2:63a884426fd0
837 changeset: 2:63a884426fd0
838 user: test
838 user: test
839 date: Thu Jan 01 00:00:00 1970 +0000
839 date: Thu Jan 01 00:00:00 1970 +0000
840 summary: 2 -> 2+; added bar
840 summary: 2 -> 2+; added bar
841
841
842 diff --git a/foo b/foo
842 diff --git a/foo b/foo
843 --- a/foo
843 --- a/foo
844 +++ b/foo
844 +++ b/foo
845 @@ -3,6 +3,6 @@
845 @@ -3,6 +3,6 @@
846 0
846 0
847 0
847 0
848 1
848 1
849 -2
849 -2
850 +2+
850 +2+
851 3
851 3
852 4
852 4
853
853
854 changeset: 0:5ae1f82b9a00
854 changeset: 0:5ae1f82b9a00
855 user: test
855 user: test
856 date: Thu Jan 01 00:00:00 1970 +0000
856 date: Thu Jan 01 00:00:00 1970 +0000
857 summary: init
857 summary: init
858
858
859 diff --git a/foo b/foo
859 diff --git a/foo b/foo
860 new file mode 100644
860 new file mode 100644
861 --- /dev/null
861 --- /dev/null
862 +++ b/foo
862 +++ b/foo
863 @@ -0,0 +1,5 @@
863 @@ -0,0 +1,5 @@
864 +0
864 +0
865 +1
865 +1
866 +2
866 +2
867 +3
867 +3
868 +4
868 +4
869
869
870
870
871 Uncommitted changes with a rename
872
873 $ hg mv baz bazn
874 $ hg log -f -L bazn,5:7
875 changeset: 9:6af29c3a778f
876 tag: tip
877 user: test
878 date: Thu Jan 01 00:00:00 1970 +0000
879 summary: foo -> dir/baz; 1-1+
880
881 changeset: 5:cfdf972b3971
882 user: test
883 date: Thu Jan 01 00:00:00 1970 +0000
884 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
885
886 changeset: 4:eaec41c1a0c9
887 user: test
888 date: Thu Jan 01 00:00:00 1970 +0000
889 summary: 11 -> 11+; leading space before "1"
890
891 changeset: 2:63a884426fd0
892 user: test
893 date: Thu Jan 01 00:00:00 1970 +0000
894 summary: 2 -> 2+; added bar
895
896 changeset: 0:5ae1f82b9a00
897 user: test
898 date: Thu Jan 01 00:00:00 1970 +0000
899 summary: init
900
901
902 Uncommitted changes in requested line range
903
904 $ sed 's/2/ /' bazn > bazn.new
905 $ mv bazn.new bazn
906 $ hg diff
907 diff --git a/dir/baz b/dir/bazn
908 rename from dir/baz
909 rename to dir/bazn
910 --- a/dir/baz
911 +++ b/dir/bazn
912 @@ -3,7 +3,7 @@
913 0
914 0
915 1+
916 -2+
917 + +
918 3+
919 4
920 5
921 $ hg log -f -L bazn,5:7
922 changeset: 9:6af29c3a778f
923 tag: tip
924 user: test
925 date: Thu Jan 01 00:00:00 1970 +0000
926 summary: foo -> dir/baz; 1-1+
927
928 changeset: 5:cfdf972b3971
929 user: test
930 date: Thu Jan 01 00:00:00 1970 +0000
931 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
932
933 changeset: 4:eaec41c1a0c9
934 user: test
935 date: Thu Jan 01 00:00:00 1970 +0000
936 summary: 11 -> 11+; leading space before "1"
937
938 changeset: 2:63a884426fd0
939 user: test
940 date: Thu Jan 01 00:00:00 1970 +0000
941 summary: 2 -> 2+; added bar
942
943 changeset: 0:5ae1f82b9a00
944 user: test
945 date: Thu Jan 01 00:00:00 1970 +0000
946 summary: init
947
948
949 Uncommitted changes in line-range + wdir()
950
951 $ hg log -r 'wdir()' -f -L bazn,5:7 --limit 2 -p
952 changeset: 2147483647:ffffffffffff
953 parent: 9:6af29c3a778f
954 user: test
955 date: Thu Jan 01 00:00:00 1970 +0000
956
957 diff --git a/dir/baz b/dir/bazn
958 copy from dir/baz
959 copy to dir/bazn
960 --- a/dir/baz
961 +++ b/dir/bazn
962 @@ -3,7 +3,7 @@
963 0
964 0
965 1+
966 -2+
967 + +
968 3+
969 4
970 5
971
972 changeset: 9:6af29c3a778f
973 tag: tip
974 user: test
975 date: Thu Jan 01 00:00:00 1970 +0000
976 summary: foo -> dir/baz; 1-1+
977
978 diff --git a/foo b/dir/baz
979 copy from foo
980 copy to dir/baz
981 --- a/foo
982 +++ b/dir/baz
983 @@ -2,7 +2,7 @@
984 0
985 0
986 0
987 - 1
988 + 1+
989 2+
990 3+
991 4
992
993
994 $ hg revert -a -C -q
995
871 Copies.
996 Copies.
872
997
873 $ hg copy baz bbaz
998 $ hg copy baz bbaz
874 $ sed 's/6/6+/' bbaz > bbaz.new
999 $ sed 's/6/6+/' bbaz > bbaz.new
875 $ mv bbaz.new bbaz
1000 $ mv bbaz.new bbaz
876 $ hg commit -m 'cp baz bbaz; 6-6+'
1001 $ hg commit -m 'cp baz bbaz; 6-6+'
877 $ hg diff -c .
1002 $ hg diff -c .
878 diff --git a/dir/baz b/dir/bbaz
1003 diff --git a/dir/baz b/dir/bbaz
879 copy from dir/baz
1004 copy from dir/baz
880 copy to dir/bbaz
1005 copy to dir/bbaz
881 --- a/dir/baz
1006 --- a/dir/baz
882 +++ b/dir/bbaz
1007 +++ b/dir/bbaz
883 @@ -7,7 +7,7 @@
1008 @@ -7,7 +7,7 @@
884 3+
1009 3+
885 4
1010 4
886 5
1011 5
887 -6
1012 -6
888 +6+
1013 +6+
889 7
1014 7
890 8
1015 8
891 9
1016 9
892 $ hg log --copies -f -L bbaz,10:11 -p
1017 $ hg log --copies -f -L bbaz,10:11 -p
893 changeset: 10:91a3d3b6c546
1018 changeset: 10:91a3d3b6c546
894 tag: tip
1019 tag: tip
895 user: test
1020 user: test
896 date: Thu Jan 01 00:00:00 1970 +0000
1021 date: Thu Jan 01 00:00:00 1970 +0000
897 summary: cp baz bbaz; 6-6+
1022 summary: cp baz bbaz; 6-6+
898
1023
899 diff --git a/dir/baz b/dir/bbaz
1024 diff --git a/dir/baz b/dir/bbaz
900 copy from dir/baz
1025 copy from dir/baz
901 copy to dir/bbaz
1026 copy to dir/bbaz
902 --- a/dir/baz
1027 --- a/dir/baz
903 +++ b/dir/bbaz
1028 +++ b/dir/bbaz
904 @@ -7,7 +7,7 @@
1029 @@ -7,7 +7,7 @@
905 3+
1030 3+
906 4
1031 4
907 5
1032 5
908 -6
1033 -6
909 +6+
1034 +6+
910 7
1035 7
911 8
1036 8
912 9
1037 9
913
1038
914 changeset: 3:730a61fbaecf
1039 changeset: 3:730a61fbaecf
915 user: test
1040 user: test
916 date: Thu Jan 01 00:00:00 1970 +0000
1041 date: Thu Jan 01 00:00:00 1970 +0000
917 summary: to 11
1042 summary: to 11
918
1043
919 diff --git a/foo b/foo
1044 diff --git a/foo b/foo
920 --- a/foo
1045 --- a/foo
921 +++ b/foo
1046 +++ b/foo
922 @@ -6,3 +6,10 @@
1047 @@ -6,3 +6,10 @@
923 2+
1048 2+
924 3
1049 3
925 4
1050 4
926 +5
1051 +5
927 +6
1052 +6
928 +7
1053 +7
929 +8
1054 +8
930 +9
1055 +9
931 +10
1056 +10
932 +11
1057 +11
933
1058
934 $ hg log -f -L bbaz,10:11 -p
1059 $ hg log -f -L bbaz,10:11 -p
935 changeset: 10:91a3d3b6c546
1060 changeset: 10:91a3d3b6c546
936 tag: tip
1061 tag: tip
937 user: test
1062 user: test
938 date: Thu Jan 01 00:00:00 1970 +0000
1063 date: Thu Jan 01 00:00:00 1970 +0000
939 summary: cp baz bbaz; 6-6+
1064 summary: cp baz bbaz; 6-6+
940
1065
941 diff --git a/dir/baz b/dir/bbaz
1066 diff --git a/dir/baz b/dir/bbaz
942 copy from dir/baz
1067 copy from dir/baz
943 copy to dir/bbaz
1068 copy to dir/bbaz
944 --- a/dir/baz
1069 --- a/dir/baz
945 +++ b/dir/bbaz
1070 +++ b/dir/bbaz
946 @@ -7,7 +7,7 @@
1071 @@ -7,7 +7,7 @@
947 3+
1072 3+
948 4
1073 4
949 5
1074 5
950 -6
1075 -6
951 +6+
1076 +6+
952 7
1077 7
953 8
1078 8
954 9
1079 9
955
1080
956 changeset: 3:730a61fbaecf
1081 changeset: 3:730a61fbaecf
957 user: test
1082 user: test
958 date: Thu Jan 01 00:00:00 1970 +0000
1083 date: Thu Jan 01 00:00:00 1970 +0000
959 summary: to 11
1084 summary: to 11
960
1085
961 diff --git a/foo b/foo
1086 diff --git a/foo b/foo
962 --- a/foo
1087 --- a/foo
963 +++ b/foo
1088 +++ b/foo
964 @@ -6,3 +6,10 @@
1089 @@ -6,3 +6,10 @@
965 2+
1090 2+
966 3
1091 3
967 4
1092 4
968 +5
1093 +5
969 +6
1094 +6
970 +7
1095 +7
971 +8
1096 +8
972 +9
1097 +9
973 +10
1098 +10
974 +11
1099 +11
975
1100
976
1101
977 Binary files work but without diff hunks filtering.
1102 Binary files work but without diff hunks filtering.
978 (Checking w/ and w/o diff.git option.)
1103 (Checking w/ and w/o diff.git option.)
979
1104
980 >>> open('binary', 'wb').write(b'this\nis\na\nbinary\0') and None
1105 >>> open('binary', 'wb').write(b'this\nis\na\nbinary\0') and None
981 $ hg add binary
1106 $ hg add binary
982 $ hg ci -m 'add a binary file' --quiet
1107 $ hg ci -m 'add a binary file' --quiet
983 $ hg log -f -L binary,1:2 -p
1108 $ hg log -f -L binary,1:2 -p
984 changeset: 11:dc865b608edf
1109 changeset: 11:dc865b608edf
985 tag: tip
1110 tag: tip
986 user: test
1111 user: test
987 date: Thu Jan 01 00:00:00 1970 +0000
1112 date: Thu Jan 01 00:00:00 1970 +0000
988 summary: add a binary file
1113 summary: add a binary file
989
1114
990 diff --git a/dir/binary b/dir/binary
1115 diff --git a/dir/binary b/dir/binary
991 new file mode 100644
1116 new file mode 100644
992 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c2e1fbed209fe919b3f189a6a31950e9adf61e45
1117 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c2e1fbed209fe919b3f189a6a31950e9adf61e45
993 GIT binary patch
1118 GIT binary patch
994 literal 17
1119 literal 17
995 Wc$_QA$SmdpqC~Ew%)G>+N(KNlNClYy
1120 Wc$_QA$SmdpqC~Ew%)G>+N(KNlNClYy
996
1121
997
1122
998 $ hg log -f -L binary,1:2 -p --config diff.git=false
1123 $ hg log -f -L binary,1:2 -p --config diff.git=false
999 changeset: 11:dc865b608edf
1124 changeset: 11:dc865b608edf
1000 tag: tip
1125 tag: tip
1001 user: test
1126 user: test
1002 date: Thu Jan 01 00:00:00 1970 +0000
1127 date: Thu Jan 01 00:00:00 1970 +0000
1003 summary: add a binary file
1128 summary: add a binary file
1004
1129
1005 diff -r 91a3d3b6c546 -r dc865b608edf dir/binary
1130 diff -r 91a3d3b6c546 -r dc865b608edf dir/binary
1006 Binary file dir/binary has changed
1131 Binary file dir/binary has changed
1007
1132
1008
1133
1009 Option --follow is required.
1134 Option --follow is required.
1010
1135
1011 $ hg log -L foo,5:7
1136 $ hg log -L foo,5:7
1012 abort: --line-range requires --follow
1137 abort: --line-range requires --follow
1013 [255]
1138 [255]
1014
1139
1015 Non-exact pattern kinds are not allowed.
1140 Non-exact pattern kinds are not allowed.
1016
1141
1017 $ cd ..
1142 $ cd ..
1018 $ hg log -f -L glob:*a*,1:2
1143 $ hg log -f -L glob:*a*,1:2
1019 hg: parse error: line range pattern 'glob:*a*' must match exactly one file
1144 hg: parse error: line range pattern 'glob:*a*' must match exactly one file
1020 [255]
1145 [255]
1021
1146
1022 We get an error for removed files.
1147 We get an error for removed files.
1023
1148
1024 $ hg rm dir/baz
1149 $ hg rm dir/baz
1025 $ hg ci -m 'remove baz' --quiet
1150 $ hg ci -m 'remove baz' --quiet
1026 $ hg log -f -L dir/baz,5:7 -p
1151 $ hg log -f -L dir/baz,5:7 -p
1027 abort: cannot follow file not in parent revision: "dir/baz"
1152 abort: cannot follow file not in parent revision: "dir/baz"
1028 [255]
1153 [255]
@@ -1,2088 +1,2088 b''
1 test merge-tools configuration - mostly exercising filemerge.py
1 test merge-tools configuration - mostly exercising filemerge.py
2
2
3 $ unset HGMERGE # make sure HGMERGE doesn't interfere with the test
3 $ unset HGMERGE # make sure HGMERGE doesn't interfere with the test
4 $ cat >> $HGRCPATH << EOF
4 $ cat >> $HGRCPATH << EOF
5 > [ui]
5 > [ui]
6 > merge=
6 > merge=
7 > EOF
7 > EOF
8 $ hg init repo
8 $ hg init repo
9 $ cd repo
9 $ cd repo
10
10
11 revision 0
11 revision 0
12
12
13 $ echo "revision 0" > f
13 $ echo "revision 0" > f
14 $ echo "space" >> f
14 $ echo "space" >> f
15 $ hg commit -Am "revision 0"
15 $ hg commit -Am "revision 0"
16 adding f
16 adding f
17
17
18 revision 1
18 revision 1
19
19
20 $ echo "revision 1" > f
20 $ echo "revision 1" > f
21 $ echo "space" >> f
21 $ echo "space" >> f
22 $ hg commit -Am "revision 1"
22 $ hg commit -Am "revision 1"
23 $ hg update 0 > /dev/null
23 $ hg update 0 > /dev/null
24
24
25 revision 2
25 revision 2
26
26
27 $ echo "revision 2" > f
27 $ echo "revision 2" > f
28 $ echo "space" >> f
28 $ echo "space" >> f
29 $ hg commit -Am "revision 2"
29 $ hg commit -Am "revision 2"
30 created new head
30 created new head
31 $ hg update 0 > /dev/null
31 $ hg update 0 > /dev/null
32
32
33 revision 3 - simple to merge
33 revision 3 - simple to merge
34
34
35 $ echo "revision 3" >> f
35 $ echo "revision 3" >> f
36 $ hg commit -Am "revision 3"
36 $ hg commit -Am "revision 3"
37 created new head
37 created new head
38
38
39 revision 4 - hard to merge
39 revision 4 - hard to merge
40
40
41 $ hg update 0 > /dev/null
41 $ hg update 0 > /dev/null
42 $ echo "revision 4" > f
42 $ echo "revision 4" > f
43 $ hg commit -Am "revision 4"
43 $ hg commit -Am "revision 4"
44 created new head
44 created new head
45
45
46 $ echo "[merge-tools]" > .hg/hgrc
46 $ echo "[merge-tools]" > .hg/hgrc
47
47
48 $ beforemerge() {
48 $ beforemerge() {
49 > cat .hg/hgrc
49 > cat .hg/hgrc
50 > echo "# hg update -C 1"
50 > echo "# hg update -C 1"
51 > hg update -C 1 > /dev/null
51 > hg update -C 1 > /dev/null
52 > }
52 > }
53 $ aftermerge() {
53 $ aftermerge() {
54 > echo "# cat f"
54 > echo "# cat f"
55 > cat f
55 > cat f
56 > echo "# hg stat"
56 > echo "# hg stat"
57 > hg stat
57 > hg stat
58 > echo "# hg resolve --list"
58 > echo "# hg resolve --list"
59 > hg resolve --list
59 > hg resolve --list
60 > rm -f f.orig
60 > rm -f f.orig
61 > }
61 > }
62
62
63 Tool selection
63 Tool selection
64
64
65 default is internal merge:
65 default is internal merge:
66
66
67 $ beforemerge
67 $ beforemerge
68 [merge-tools]
68 [merge-tools]
69 # hg update -C 1
69 # hg update -C 1
70
70
71 hg merge -r 2
71 hg merge -r 2
72 override $PATH to ensure hgmerge not visible; use $PYTHON in case we're
72 override $PATH to ensure hgmerge not visible; use $PYTHON in case we're
73 running from a devel copy, not a temp installation
73 running from a devel copy, not a temp installation
74
74
75 $ PATH="$BINDIR:/usr/sbin" "$PYTHON" "$BINDIR"/hg merge -r 2
75 $ PATH="/usr/sbin" "$PYTHON" "$BINDIR"/hg merge -r 2
76 merging f
76 merging f
77 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
77 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
78 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
78 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
79 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
79 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
80 [1]
80 [1]
81 $ aftermerge
81 $ aftermerge
82 # cat f
82 # cat f
83 <<<<<<< working copy: ef83787e2614 - test: revision 1
83 <<<<<<< working copy: ef83787e2614 - test: revision 1
84 revision 1
84 revision 1
85 =======
85 =======
86 revision 2
86 revision 2
87 >>>>>>> merge rev: 0185f4e0cf02 - test: revision 2
87 >>>>>>> merge rev: 0185f4e0cf02 - test: revision 2
88 space
88 space
89 # hg stat
89 # hg stat
90 M f
90 M f
91 ? f.orig
91 ? f.orig
92 # hg resolve --list
92 # hg resolve --list
93 U f
93 U f
94
94
95 simplest hgrc using false for merge:
95 simplest hgrc using false for merge:
96
96
97 $ echo "false.whatever=" >> .hg/hgrc
97 $ echo "false.whatever=" >> .hg/hgrc
98 $ beforemerge
98 $ beforemerge
99 [merge-tools]
99 [merge-tools]
100 false.whatever=
100 false.whatever=
101 # hg update -C 1
101 # hg update -C 1
102 $ hg merge -r 2
102 $ hg merge -r 2
103 merging f
103 merging f
104 merging f failed!
104 merging f failed!
105 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
105 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
106 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
106 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
107 [1]
107 [1]
108 $ aftermerge
108 $ aftermerge
109 # cat f
109 # cat f
110 revision 1
110 revision 1
111 space
111 space
112 # hg stat
112 # hg stat
113 M f
113 M f
114 ? f.orig
114 ? f.orig
115 # hg resolve --list
115 # hg resolve --list
116 U f
116 U f
117
117
118 #if unix-permissions
118 #if unix-permissions
119
119
120 unexecutable file in $PATH shouldn't be found:
120 unexecutable file in $PATH shouldn't be found:
121
121
122 $ echo "echo fail" > false
122 $ echo "echo fail" > false
123 $ hg up -qC 1
123 $ hg up -qC 1
124 $ PATH="`pwd`:$BINDIR:/usr/sbin" "$PYTHON" "$BINDIR"/hg merge -r 2
124 $ PATH="`pwd`:/usr/sbin" "$PYTHON" "$BINDIR"/hg merge -r 2
125 merging f
125 merging f
126 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
126 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
127 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
127 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
128 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
128 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
129 [1]
129 [1]
130 $ rm false
130 $ rm false
131
131
132 #endif
132 #endif
133
133
134 executable directory in $PATH shouldn't be found:
134 executable directory in $PATH shouldn't be found:
135
135
136 $ mkdir false
136 $ mkdir false
137 $ hg up -qC 1
137 $ hg up -qC 1
138 $ PATH="`pwd`:$BINDIR:/usr/sbin" "$PYTHON" "$BINDIR"/hg merge -r 2
138 $ PATH="`pwd`:/usr/sbin" "$PYTHON" "$BINDIR"/hg merge -r 2
139 merging f
139 merging f
140 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
140 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
141 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
141 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
142 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
142 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
143 [1]
143 [1]
144 $ rmdir false
144 $ rmdir false
145
145
146 true with higher .priority gets precedence:
146 true with higher .priority gets precedence:
147
147
148 $ echo "true.priority=1" >> .hg/hgrc
148 $ echo "true.priority=1" >> .hg/hgrc
149 $ beforemerge
149 $ beforemerge
150 [merge-tools]
150 [merge-tools]
151 false.whatever=
151 false.whatever=
152 true.priority=1
152 true.priority=1
153 # hg update -C 1
153 # hg update -C 1
154 $ hg merge -r 2
154 $ hg merge -r 2
155 merging f
155 merging f
156 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
156 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
157 (branch merge, don't forget to commit)
157 (branch merge, don't forget to commit)
158 $ aftermerge
158 $ aftermerge
159 # cat f
159 # cat f
160 revision 1
160 revision 1
161 space
161 space
162 # hg stat
162 # hg stat
163 M f
163 M f
164 # hg resolve --list
164 # hg resolve --list
165 R f
165 R f
166
166
167 unless lowered on command line:
167 unless lowered on command line:
168
168
169 $ beforemerge
169 $ beforemerge
170 [merge-tools]
170 [merge-tools]
171 false.whatever=
171 false.whatever=
172 true.priority=1
172 true.priority=1
173 # hg update -C 1
173 # hg update -C 1
174 $ hg merge -r 2 --config merge-tools.true.priority=-7
174 $ hg merge -r 2 --config merge-tools.true.priority=-7
175 merging f
175 merging f
176 merging f failed!
176 merging f failed!
177 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
177 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
178 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
178 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
179 [1]
179 [1]
180 $ aftermerge
180 $ aftermerge
181 # cat f
181 # cat f
182 revision 1
182 revision 1
183 space
183 space
184 # hg stat
184 # hg stat
185 M f
185 M f
186 ? f.orig
186 ? f.orig
187 # hg resolve --list
187 # hg resolve --list
188 U f
188 U f
189
189
190 or false set higher on command line:
190 or false set higher on command line:
191
191
192 $ beforemerge
192 $ beforemerge
193 [merge-tools]
193 [merge-tools]
194 false.whatever=
194 false.whatever=
195 true.priority=1
195 true.priority=1
196 # hg update -C 1
196 # hg update -C 1
197 $ hg merge -r 2 --config merge-tools.false.priority=117
197 $ hg merge -r 2 --config merge-tools.false.priority=117
198 merging f
198 merging f
199 merging f failed!
199 merging f failed!
200 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
200 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
201 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
201 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
202 [1]
202 [1]
203 $ aftermerge
203 $ aftermerge
204 # cat f
204 # cat f
205 revision 1
205 revision 1
206 space
206 space
207 # hg stat
207 # hg stat
208 M f
208 M f
209 ? f.orig
209 ? f.orig
210 # hg resolve --list
210 # hg resolve --list
211 U f
211 U f
212
212
213 or true set to disabled:
213 or true set to disabled:
214 $ beforemerge
214 $ beforemerge
215 [merge-tools]
215 [merge-tools]
216 false.whatever=
216 false.whatever=
217 true.priority=1
217 true.priority=1
218 # hg update -C 1
218 # hg update -C 1
219 $ hg merge -r 2 --config merge-tools.true.disabled=yes
219 $ hg merge -r 2 --config merge-tools.true.disabled=yes
220 merging f
220 merging f
221 merging f failed!
221 merging f failed!
222 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
222 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
223 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
223 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
224 [1]
224 [1]
225 $ aftermerge
225 $ aftermerge
226 # cat f
226 # cat f
227 revision 1
227 revision 1
228 space
228 space
229 # hg stat
229 # hg stat
230 M f
230 M f
231 ? f.orig
231 ? f.orig
232 # hg resolve --list
232 # hg resolve --list
233 U f
233 U f
234
234
235 or true.executable not found in PATH:
235 or true.executable not found in PATH:
236
236
237 $ beforemerge
237 $ beforemerge
238 [merge-tools]
238 [merge-tools]
239 false.whatever=
239 false.whatever=
240 true.priority=1
240 true.priority=1
241 # hg update -C 1
241 # hg update -C 1
242 $ hg merge -r 2 --config merge-tools.true.executable=nonexistentmergetool
242 $ hg merge -r 2 --config merge-tools.true.executable=nonexistentmergetool
243 merging f
243 merging f
244 merging f failed!
244 merging f failed!
245 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
245 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
246 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
246 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
247 [1]
247 [1]
248 $ aftermerge
248 $ aftermerge
249 # cat f
249 # cat f
250 revision 1
250 revision 1
251 space
251 space
252 # hg stat
252 # hg stat
253 M f
253 M f
254 ? f.orig
254 ? f.orig
255 # hg resolve --list
255 # hg resolve --list
256 U f
256 U f
257
257
258 or true.executable with bogus path:
258 or true.executable with bogus path:
259
259
260 $ beforemerge
260 $ beforemerge
261 [merge-tools]
261 [merge-tools]
262 false.whatever=
262 false.whatever=
263 true.priority=1
263 true.priority=1
264 # hg update -C 1
264 # hg update -C 1
265 $ hg merge -r 2 --config merge-tools.true.executable=/nonexistent/mergetool
265 $ hg merge -r 2 --config merge-tools.true.executable=/nonexistent/mergetool
266 merging f
266 merging f
267 merging f failed!
267 merging f failed!
268 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
268 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
269 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
269 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
270 [1]
270 [1]
271 $ aftermerge
271 $ aftermerge
272 # cat f
272 # cat f
273 revision 1
273 revision 1
274 space
274 space
275 # hg stat
275 # hg stat
276 M f
276 M f
277 ? f.orig
277 ? f.orig
278 # hg resolve --list
278 # hg resolve --list
279 U f
279 U f
280
280
281 but true.executable set to cat found in PATH works:
281 but true.executable set to cat found in PATH works:
282
282
283 $ echo "true.executable=cat" >> .hg/hgrc
283 $ echo "true.executable=cat" >> .hg/hgrc
284 $ beforemerge
284 $ beforemerge
285 [merge-tools]
285 [merge-tools]
286 false.whatever=
286 false.whatever=
287 true.priority=1
287 true.priority=1
288 true.executable=cat
288 true.executable=cat
289 # hg update -C 1
289 # hg update -C 1
290 $ hg merge -r 2
290 $ hg merge -r 2
291 merging f
291 merging f
292 revision 1
292 revision 1
293 space
293 space
294 revision 0
294 revision 0
295 space
295 space
296 revision 2
296 revision 2
297 space
297 space
298 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
298 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
299 (branch merge, don't forget to commit)
299 (branch merge, don't forget to commit)
300 $ aftermerge
300 $ aftermerge
301 # cat f
301 # cat f
302 revision 1
302 revision 1
303 space
303 space
304 # hg stat
304 # hg stat
305 M f
305 M f
306 # hg resolve --list
306 # hg resolve --list
307 R f
307 R f
308
308
309 and true.executable set to cat with path works:
309 and true.executable set to cat with path works:
310
310
311 $ beforemerge
311 $ beforemerge
312 [merge-tools]
312 [merge-tools]
313 false.whatever=
313 false.whatever=
314 true.priority=1
314 true.priority=1
315 true.executable=cat
315 true.executable=cat
316 # hg update -C 1
316 # hg update -C 1
317 $ hg merge -r 2 --config merge-tools.true.executable=cat
317 $ hg merge -r 2 --config merge-tools.true.executable=cat
318 merging f
318 merging f
319 revision 1
319 revision 1
320 space
320 space
321 revision 0
321 revision 0
322 space
322 space
323 revision 2
323 revision 2
324 space
324 space
325 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
325 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
326 (branch merge, don't forget to commit)
326 (branch merge, don't forget to commit)
327 $ aftermerge
327 $ aftermerge
328 # cat f
328 # cat f
329 revision 1
329 revision 1
330 space
330 space
331 # hg stat
331 # hg stat
332 M f
332 M f
333 # hg resolve --list
333 # hg resolve --list
334 R f
334 R f
335
335
336 executable set to python script that succeeds:
336 executable set to python script that succeeds:
337
337
338 $ cat > "$TESTTMP/myworkingmerge.py" <<EOF
338 $ cat > "$TESTTMP/myworkingmerge.py" <<EOF
339 > def myworkingmergefn(ui, repo, args, **kwargs):
339 > def myworkingmergefn(ui, repo, args, **kwargs):
340 > return False
340 > return False
341 > EOF
341 > EOF
342 $ beforemerge
342 $ beforemerge
343 [merge-tools]
343 [merge-tools]
344 false.whatever=
344 false.whatever=
345 true.priority=1
345 true.priority=1
346 true.executable=cat
346 true.executable=cat
347 # hg update -C 1
347 # hg update -C 1
348 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py:myworkingmergefn"
348 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py:myworkingmergefn"
349 merging f
349 merging f
350 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
350 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
351 (branch merge, don't forget to commit)
351 (branch merge, don't forget to commit)
352 $ aftermerge
352 $ aftermerge
353 # cat f
353 # cat f
354 revision 1
354 revision 1
355 space
355 space
356 # hg stat
356 # hg stat
357 M f
357 M f
358 # hg resolve --list
358 # hg resolve --list
359 R f
359 R f
360
360
361 executable set to python script that fails:
361 executable set to python script that fails:
362
362
363 $ cat > "$TESTTMP/mybrokenmerge.py" <<EOF
363 $ cat > "$TESTTMP/mybrokenmerge.py" <<EOF
364 > def mybrokenmergefn(ui, repo, args, **kwargs):
364 > def mybrokenmergefn(ui, repo, args, **kwargs):
365 > ui.write(b"some fail message\n")
365 > ui.write(b"some fail message\n")
366 > return True
366 > return True
367 > EOF
367 > EOF
368 $ beforemerge
368 $ beforemerge
369 [merge-tools]
369 [merge-tools]
370 false.whatever=
370 false.whatever=
371 true.priority=1
371 true.priority=1
372 true.executable=cat
372 true.executable=cat
373 # hg update -C 1
373 # hg update -C 1
374 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/mybrokenmerge.py:mybrokenmergefn"
374 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/mybrokenmerge.py:mybrokenmergefn"
375 merging f
375 merging f
376 some fail message
376 some fail message
377 abort: $TESTTMP/mybrokenmerge.py hook failed
377 abort: $TESTTMP/mybrokenmerge.py hook failed
378 [255]
378 [255]
379 $ aftermerge
379 $ aftermerge
380 # cat f
380 # cat f
381 revision 1
381 revision 1
382 space
382 space
383 # hg stat
383 # hg stat
384 ? f.orig
384 ? f.orig
385 # hg resolve --list
385 # hg resolve --list
386 U f
386 U f
387
387
388 executable set to python script that is missing function:
388 executable set to python script that is missing function:
389
389
390 $ beforemerge
390 $ beforemerge
391 [merge-tools]
391 [merge-tools]
392 false.whatever=
392 false.whatever=
393 true.priority=1
393 true.priority=1
394 true.executable=cat
394 true.executable=cat
395 # hg update -C 1
395 # hg update -C 1
396 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py:missingFunction"
396 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py:missingFunction"
397 merging f
397 merging f
398 abort: $TESTTMP/myworkingmerge.py does not have function: missingFunction
398 abort: $TESTTMP/myworkingmerge.py does not have function: missingFunction
399 [255]
399 [255]
400 $ aftermerge
400 $ aftermerge
401 # cat f
401 # cat f
402 revision 1
402 revision 1
403 space
403 space
404 # hg stat
404 # hg stat
405 ? f.orig
405 ? f.orig
406 # hg resolve --list
406 # hg resolve --list
407 U f
407 U f
408
408
409 executable set to missing python script:
409 executable set to missing python script:
410
410
411 $ beforemerge
411 $ beforemerge
412 [merge-tools]
412 [merge-tools]
413 false.whatever=
413 false.whatever=
414 true.priority=1
414 true.priority=1
415 true.executable=cat
415 true.executable=cat
416 # hg update -C 1
416 # hg update -C 1
417 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/missingpythonscript.py:mergefn"
417 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/missingpythonscript.py:mergefn"
418 merging f
418 merging f
419 abort: loading python merge script failed: $TESTTMP/missingpythonscript.py
419 abort: loading python merge script failed: $TESTTMP/missingpythonscript.py
420 [255]
420 [255]
421 $ aftermerge
421 $ aftermerge
422 # cat f
422 # cat f
423 revision 1
423 revision 1
424 space
424 space
425 # hg stat
425 # hg stat
426 ? f.orig
426 ? f.orig
427 # hg resolve --list
427 # hg resolve --list
428 U f
428 U f
429
429
430 executable set to python script but callable function is missing:
430 executable set to python script but callable function is missing:
431
431
432 $ beforemerge
432 $ beforemerge
433 [merge-tools]
433 [merge-tools]
434 false.whatever=
434 false.whatever=
435 true.priority=1
435 true.priority=1
436 true.executable=cat
436 true.executable=cat
437 # hg update -C 1
437 # hg update -C 1
438 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py"
438 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py"
439 abort: invalid 'python:' syntax: python:$TESTTMP/myworkingmerge.py
439 abort: invalid 'python:' syntax: python:$TESTTMP/myworkingmerge.py
440 [255]
440 [255]
441 $ aftermerge
441 $ aftermerge
442 # cat f
442 # cat f
443 revision 1
443 revision 1
444 space
444 space
445 # hg stat
445 # hg stat
446 # hg resolve --list
446 # hg resolve --list
447 U f
447 U f
448
448
449 executable set to python script but callable function is empty string:
449 executable set to python script but callable function is empty string:
450
450
451 $ beforemerge
451 $ beforemerge
452 [merge-tools]
452 [merge-tools]
453 false.whatever=
453 false.whatever=
454 true.priority=1
454 true.priority=1
455 true.executable=cat
455 true.executable=cat
456 # hg update -C 1
456 # hg update -C 1
457 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py:"
457 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py:"
458 abort: invalid 'python:' syntax: python:$TESTTMP/myworkingmerge.py:
458 abort: invalid 'python:' syntax: python:$TESTTMP/myworkingmerge.py:
459 [255]
459 [255]
460 $ aftermerge
460 $ aftermerge
461 # cat f
461 # cat f
462 revision 1
462 revision 1
463 space
463 space
464 # hg stat
464 # hg stat
465 # hg resolve --list
465 # hg resolve --list
466 U f
466 U f
467
467
468 executable set to python script but callable function is missing and path contains colon:
468 executable set to python script but callable function is missing and path contains colon:
469
469
470 $ beforemerge
470 $ beforemerge
471 [merge-tools]
471 [merge-tools]
472 false.whatever=
472 false.whatever=
473 true.priority=1
473 true.priority=1
474 true.executable=cat
474 true.executable=cat
475 # hg update -C 1
475 # hg update -C 1
476 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/some:dir/myworkingmerge.py"
476 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/some:dir/myworkingmerge.py"
477 abort: invalid 'python:' syntax: python:$TESTTMP/some:dir/myworkingmerge.py
477 abort: invalid 'python:' syntax: python:$TESTTMP/some:dir/myworkingmerge.py
478 [255]
478 [255]
479 $ aftermerge
479 $ aftermerge
480 # cat f
480 # cat f
481 revision 1
481 revision 1
482 space
482 space
483 # hg stat
483 # hg stat
484 # hg resolve --list
484 # hg resolve --list
485 U f
485 U f
486
486
487 executable set to python script filename that contains spaces:
487 executable set to python script filename that contains spaces:
488
488
489 $ mkdir -p "$TESTTMP/my path"
489 $ mkdir -p "$TESTTMP/my path"
490 $ cat > "$TESTTMP/my path/my working merge with spaces in filename.py" <<EOF
490 $ cat > "$TESTTMP/my path/my working merge with spaces in filename.py" <<EOF
491 > def myworkingmergefn(ui, repo, args, **kwargs):
491 > def myworkingmergefn(ui, repo, args, **kwargs):
492 > return False
492 > return False
493 > EOF
493 > EOF
494 $ beforemerge
494 $ beforemerge
495 [merge-tools]
495 [merge-tools]
496 false.whatever=
496 false.whatever=
497 true.priority=1
497 true.priority=1
498 true.executable=cat
498 true.executable=cat
499 # hg update -C 1
499 # hg update -C 1
500 $ hg merge -r 2 --config "merge-tools.true.executable=python:$TESTTMP/my path/my working merge with spaces in filename.py:myworkingmergefn"
500 $ hg merge -r 2 --config "merge-tools.true.executable=python:$TESTTMP/my path/my working merge with spaces in filename.py:myworkingmergefn"
501 merging f
501 merging f
502 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
502 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
503 (branch merge, don't forget to commit)
503 (branch merge, don't forget to commit)
504 $ aftermerge
504 $ aftermerge
505 # cat f
505 # cat f
506 revision 1
506 revision 1
507 space
507 space
508 # hg stat
508 # hg stat
509 M f
509 M f
510 # hg resolve --list
510 # hg resolve --list
511 R f
511 R f
512
512
513 #if unix-permissions
513 #if unix-permissions
514
514
515 environment variables in true.executable are handled:
515 environment variables in true.executable are handled:
516
516
517 $ echo 'echo "custom merge tool"' > .hg/merge.sh
517 $ echo 'echo "custom merge tool"' > .hg/merge.sh
518 $ beforemerge
518 $ beforemerge
519 [merge-tools]
519 [merge-tools]
520 false.whatever=
520 false.whatever=
521 true.priority=1
521 true.priority=1
522 true.executable=cat
522 true.executable=cat
523 # hg update -C 1
523 # hg update -C 1
524 $ hg --config merge-tools.true.executable='sh' \
524 $ hg --config merge-tools.true.executable='sh' \
525 > --config merge-tools.true.args=.hg/merge.sh \
525 > --config merge-tools.true.args=.hg/merge.sh \
526 > merge -r 2
526 > merge -r 2
527 merging f
527 merging f
528 custom merge tool
528 custom merge tool
529 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
529 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
530 (branch merge, don't forget to commit)
530 (branch merge, don't forget to commit)
531 $ aftermerge
531 $ aftermerge
532 # cat f
532 # cat f
533 revision 1
533 revision 1
534 space
534 space
535 # hg stat
535 # hg stat
536 M f
536 M f
537 # hg resolve --list
537 # hg resolve --list
538 R f
538 R f
539
539
540 #endif
540 #endif
541
541
542 Tool selection and merge-patterns
542 Tool selection and merge-patterns
543
543
544 merge-patterns specifies new tool false:
544 merge-patterns specifies new tool false:
545
545
546 $ beforemerge
546 $ beforemerge
547 [merge-tools]
547 [merge-tools]
548 false.whatever=
548 false.whatever=
549 true.priority=1
549 true.priority=1
550 true.executable=cat
550 true.executable=cat
551 # hg update -C 1
551 # hg update -C 1
552 $ hg merge -r 2 --config merge-patterns.f=false
552 $ hg merge -r 2 --config merge-patterns.f=false
553 merging f
553 merging f
554 merging f failed!
554 merging f failed!
555 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
555 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
556 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
556 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
557 [1]
557 [1]
558 $ aftermerge
558 $ aftermerge
559 # cat f
559 # cat f
560 revision 1
560 revision 1
561 space
561 space
562 # hg stat
562 # hg stat
563 M f
563 M f
564 ? f.orig
564 ? f.orig
565 # hg resolve --list
565 # hg resolve --list
566 U f
566 U f
567
567
568 merge-patterns specifies executable not found in PATH and gets warning:
568 merge-patterns specifies executable not found in PATH and gets warning:
569
569
570 $ beforemerge
570 $ beforemerge
571 [merge-tools]
571 [merge-tools]
572 false.whatever=
572 false.whatever=
573 true.priority=1
573 true.priority=1
574 true.executable=cat
574 true.executable=cat
575 # hg update -C 1
575 # hg update -C 1
576 $ hg merge -r 2 --config merge-patterns.f=true --config merge-tools.true.executable=nonexistentmergetool
576 $ hg merge -r 2 --config merge-patterns.f=true --config merge-tools.true.executable=nonexistentmergetool
577 couldn't find merge tool true (for pattern f)
577 couldn't find merge tool true (for pattern f)
578 merging f
578 merging f
579 couldn't find merge tool true (for pattern f)
579 couldn't find merge tool true (for pattern f)
580 merging f failed!
580 merging f failed!
581 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
581 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
582 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
582 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
583 [1]
583 [1]
584 $ aftermerge
584 $ aftermerge
585 # cat f
585 # cat f
586 revision 1
586 revision 1
587 space
587 space
588 # hg stat
588 # hg stat
589 M f
589 M f
590 ? f.orig
590 ? f.orig
591 # hg resolve --list
591 # hg resolve --list
592 U f
592 U f
593
593
594 merge-patterns specifies executable with bogus path and gets warning:
594 merge-patterns specifies executable with bogus path and gets warning:
595
595
596 $ beforemerge
596 $ beforemerge
597 [merge-tools]
597 [merge-tools]
598 false.whatever=
598 false.whatever=
599 true.priority=1
599 true.priority=1
600 true.executable=cat
600 true.executable=cat
601 # hg update -C 1
601 # hg update -C 1
602 $ hg merge -r 2 --config merge-patterns.f=true --config merge-tools.true.executable=/nonexistent/mergetool
602 $ hg merge -r 2 --config merge-patterns.f=true --config merge-tools.true.executable=/nonexistent/mergetool
603 couldn't find merge tool true (for pattern f)
603 couldn't find merge tool true (for pattern f)
604 merging f
604 merging f
605 couldn't find merge tool true (for pattern f)
605 couldn't find merge tool true (for pattern f)
606 merging f failed!
606 merging f failed!
607 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
607 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
608 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
608 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
609 [1]
609 [1]
610 $ aftermerge
610 $ aftermerge
611 # cat f
611 # cat f
612 revision 1
612 revision 1
613 space
613 space
614 # hg stat
614 # hg stat
615 M f
615 M f
616 ? f.orig
616 ? f.orig
617 # hg resolve --list
617 # hg resolve --list
618 U f
618 U f
619
619
620 ui.merge overrules priority
620 ui.merge overrules priority
621
621
622 ui.merge specifies false:
622 ui.merge specifies false:
623
623
624 $ beforemerge
624 $ beforemerge
625 [merge-tools]
625 [merge-tools]
626 false.whatever=
626 false.whatever=
627 true.priority=1
627 true.priority=1
628 true.executable=cat
628 true.executable=cat
629 # hg update -C 1
629 # hg update -C 1
630 $ hg merge -r 2 --config ui.merge=false
630 $ hg merge -r 2 --config ui.merge=false
631 merging f
631 merging f
632 merging f failed!
632 merging f failed!
633 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
633 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
634 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
634 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
635 [1]
635 [1]
636 $ aftermerge
636 $ aftermerge
637 # cat f
637 # cat f
638 revision 1
638 revision 1
639 space
639 space
640 # hg stat
640 # hg stat
641 M f
641 M f
642 ? f.orig
642 ? f.orig
643 # hg resolve --list
643 # hg resolve --list
644 U f
644 U f
645
645
646 ui.merge specifies internal:fail:
646 ui.merge specifies internal:fail:
647
647
648 $ beforemerge
648 $ beforemerge
649 [merge-tools]
649 [merge-tools]
650 false.whatever=
650 false.whatever=
651 true.priority=1
651 true.priority=1
652 true.executable=cat
652 true.executable=cat
653 # hg update -C 1
653 # hg update -C 1
654 $ hg merge -r 2 --config ui.merge=internal:fail
654 $ hg merge -r 2 --config ui.merge=internal:fail
655 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
655 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
656 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
656 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
657 [1]
657 [1]
658 $ aftermerge
658 $ aftermerge
659 # cat f
659 # cat f
660 revision 1
660 revision 1
661 space
661 space
662 # hg stat
662 # hg stat
663 M f
663 M f
664 # hg resolve --list
664 # hg resolve --list
665 U f
665 U f
666
666
667 ui.merge specifies :local (without internal prefix):
667 ui.merge specifies :local (without internal prefix):
668
668
669 $ beforemerge
669 $ beforemerge
670 [merge-tools]
670 [merge-tools]
671 false.whatever=
671 false.whatever=
672 true.priority=1
672 true.priority=1
673 true.executable=cat
673 true.executable=cat
674 # hg update -C 1
674 # hg update -C 1
675 $ hg merge -r 2 --config ui.merge=:local
675 $ hg merge -r 2 --config ui.merge=:local
676 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
676 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
677 (branch merge, don't forget to commit)
677 (branch merge, don't forget to commit)
678 $ aftermerge
678 $ aftermerge
679 # cat f
679 # cat f
680 revision 1
680 revision 1
681 space
681 space
682 # hg stat
682 # hg stat
683 M f
683 M f
684 # hg resolve --list
684 # hg resolve --list
685 R f
685 R f
686
686
687 ui.merge specifies internal:other:
687 ui.merge specifies internal:other:
688
688
689 $ beforemerge
689 $ beforemerge
690 [merge-tools]
690 [merge-tools]
691 false.whatever=
691 false.whatever=
692 true.priority=1
692 true.priority=1
693 true.executable=cat
693 true.executable=cat
694 # hg update -C 1
694 # hg update -C 1
695 $ hg merge -r 2 --config ui.merge=internal:other
695 $ hg merge -r 2 --config ui.merge=internal:other
696 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
696 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
697 (branch merge, don't forget to commit)
697 (branch merge, don't forget to commit)
698 $ aftermerge
698 $ aftermerge
699 # cat f
699 # cat f
700 revision 2
700 revision 2
701 space
701 space
702 # hg stat
702 # hg stat
703 M f
703 M f
704 # hg resolve --list
704 # hg resolve --list
705 R f
705 R f
706
706
707 ui.merge specifies internal:prompt:
707 ui.merge specifies internal:prompt:
708
708
709 $ beforemerge
709 $ beforemerge
710 [merge-tools]
710 [merge-tools]
711 false.whatever=
711 false.whatever=
712 true.priority=1
712 true.priority=1
713 true.executable=cat
713 true.executable=cat
714 # hg update -C 1
714 # hg update -C 1
715 $ hg merge -r 2 --config ui.merge=internal:prompt
715 $ hg merge -r 2 --config ui.merge=internal:prompt
716 file 'f' needs to be resolved.
716 file 'f' needs to be resolved.
717 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
717 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
718 What do you want to do? u
718 What do you want to do? u
719 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
719 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
720 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
720 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
721 [1]
721 [1]
722 $ aftermerge
722 $ aftermerge
723 # cat f
723 # cat f
724 revision 1
724 revision 1
725 space
725 space
726 # hg stat
726 # hg stat
727 M f
727 M f
728 # hg resolve --list
728 # hg resolve --list
729 U f
729 U f
730
730
731 ui.merge specifies :prompt, with 'leave unresolved' chosen
731 ui.merge specifies :prompt, with 'leave unresolved' chosen
732
732
733 $ beforemerge
733 $ beforemerge
734 [merge-tools]
734 [merge-tools]
735 false.whatever=
735 false.whatever=
736 true.priority=1
736 true.priority=1
737 true.executable=cat
737 true.executable=cat
738 # hg update -C 1
738 # hg update -C 1
739 $ hg merge -r 2 --config ui.merge=:prompt --config ui.interactive=True << EOF
739 $ hg merge -r 2 --config ui.merge=:prompt --config ui.interactive=True << EOF
740 > u
740 > u
741 > EOF
741 > EOF
742 file 'f' needs to be resolved.
742 file 'f' needs to be resolved.
743 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
743 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
744 What do you want to do? u
744 What do you want to do? u
745 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
745 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
746 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
746 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
747 [1]
747 [1]
748 $ aftermerge
748 $ aftermerge
749 # cat f
749 # cat f
750 revision 1
750 revision 1
751 space
751 space
752 # hg stat
752 # hg stat
753 M f
753 M f
754 # hg resolve --list
754 # hg resolve --list
755 U f
755 U f
756
756
757 prompt with EOF
757 prompt with EOF
758
758
759 $ beforemerge
759 $ beforemerge
760 [merge-tools]
760 [merge-tools]
761 false.whatever=
761 false.whatever=
762 true.priority=1
762 true.priority=1
763 true.executable=cat
763 true.executable=cat
764 # hg update -C 1
764 # hg update -C 1
765 $ hg merge -r 2 --config ui.merge=internal:prompt --config ui.interactive=true
765 $ hg merge -r 2 --config ui.merge=internal:prompt --config ui.interactive=true
766 file 'f' needs to be resolved.
766 file 'f' needs to be resolved.
767 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
767 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
768 What do you want to do?
768 What do you want to do?
769 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
769 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
770 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
770 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
771 [1]
771 [1]
772 $ aftermerge
772 $ aftermerge
773 # cat f
773 # cat f
774 revision 1
774 revision 1
775 space
775 space
776 # hg stat
776 # hg stat
777 M f
777 M f
778 # hg resolve --list
778 # hg resolve --list
779 U f
779 U f
780 $ hg resolve --all --config ui.merge=internal:prompt --config ui.interactive=true
780 $ hg resolve --all --config ui.merge=internal:prompt --config ui.interactive=true
781 file 'f' needs to be resolved.
781 file 'f' needs to be resolved.
782 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
782 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
783 What do you want to do?
783 What do you want to do?
784 [1]
784 [1]
785 $ aftermerge
785 $ aftermerge
786 # cat f
786 # cat f
787 revision 1
787 revision 1
788 space
788 space
789 # hg stat
789 # hg stat
790 M f
790 M f
791 ? f.orig
791 ? f.orig
792 # hg resolve --list
792 # hg resolve --list
793 U f
793 U f
794 $ rm f
794 $ rm f
795 $ hg resolve --all --config ui.merge=internal:prompt --config ui.interactive=true
795 $ hg resolve --all --config ui.merge=internal:prompt --config ui.interactive=true
796 file 'f' needs to be resolved.
796 file 'f' needs to be resolved.
797 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
797 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
798 What do you want to do?
798 What do you want to do?
799 [1]
799 [1]
800 $ aftermerge
800 $ aftermerge
801 # cat f
801 # cat f
802 revision 1
802 revision 1
803 space
803 space
804 # hg stat
804 # hg stat
805 M f
805 M f
806 # hg resolve --list
806 # hg resolve --list
807 U f
807 U f
808 $ hg resolve --all --config ui.merge=internal:prompt
808 $ hg resolve --all --config ui.merge=internal:prompt
809 file 'f' needs to be resolved.
809 file 'f' needs to be resolved.
810 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
810 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
811 What do you want to do? u
811 What do you want to do? u
812 [1]
812 [1]
813 $ aftermerge
813 $ aftermerge
814 # cat f
814 # cat f
815 revision 1
815 revision 1
816 space
816 space
817 # hg stat
817 # hg stat
818 M f
818 M f
819 ? f.orig
819 ? f.orig
820 # hg resolve --list
820 # hg resolve --list
821 U f
821 U f
822
822
823 ui.merge specifies internal:dump:
823 ui.merge specifies internal:dump:
824
824
825 $ beforemerge
825 $ beforemerge
826 [merge-tools]
826 [merge-tools]
827 false.whatever=
827 false.whatever=
828 true.priority=1
828 true.priority=1
829 true.executable=cat
829 true.executable=cat
830 # hg update -C 1
830 # hg update -C 1
831 $ hg merge -r 2 --config ui.merge=internal:dump
831 $ hg merge -r 2 --config ui.merge=internal:dump
832 merging f
832 merging f
833 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
833 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
834 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
834 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
835 [1]
835 [1]
836 $ aftermerge
836 $ aftermerge
837 # cat f
837 # cat f
838 revision 1
838 revision 1
839 space
839 space
840 # hg stat
840 # hg stat
841 M f
841 M f
842 ? f.base
842 ? f.base
843 ? f.local
843 ? f.local
844 ? f.orig
844 ? f.orig
845 ? f.other
845 ? f.other
846 # hg resolve --list
846 # hg resolve --list
847 U f
847 U f
848
848
849 f.base:
849 f.base:
850
850
851 $ cat f.base
851 $ cat f.base
852 revision 0
852 revision 0
853 space
853 space
854
854
855 f.local:
855 f.local:
856
856
857 $ cat f.local
857 $ cat f.local
858 revision 1
858 revision 1
859 space
859 space
860
860
861 f.other:
861 f.other:
862
862
863 $ cat f.other
863 $ cat f.other
864 revision 2
864 revision 2
865 space
865 space
866 $ rm f.base f.local f.other
866 $ rm f.base f.local f.other
867
867
868 check that internal:dump doesn't dump files if premerge runs
868 check that internal:dump doesn't dump files if premerge runs
869 successfully
869 successfully
870
870
871 $ beforemerge
871 $ beforemerge
872 [merge-tools]
872 [merge-tools]
873 false.whatever=
873 false.whatever=
874 true.priority=1
874 true.priority=1
875 true.executable=cat
875 true.executable=cat
876 # hg update -C 1
876 # hg update -C 1
877 $ hg merge -r 3 --config ui.merge=internal:dump
877 $ hg merge -r 3 --config ui.merge=internal:dump
878 merging f
878 merging f
879 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
879 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
880 (branch merge, don't forget to commit)
880 (branch merge, don't forget to commit)
881
881
882 $ aftermerge
882 $ aftermerge
883 # cat f
883 # cat f
884 revision 1
884 revision 1
885 space
885 space
886 revision 3
886 revision 3
887 # hg stat
887 # hg stat
888 M f
888 M f
889 # hg resolve --list
889 # hg resolve --list
890 R f
890 R f
891
891
892 check that internal:forcedump dumps files, even if local and other can
892 check that internal:forcedump dumps files, even if local and other can
893 be merged easily
893 be merged easily
894
894
895 $ beforemerge
895 $ beforemerge
896 [merge-tools]
896 [merge-tools]
897 false.whatever=
897 false.whatever=
898 true.priority=1
898 true.priority=1
899 true.executable=cat
899 true.executable=cat
900 # hg update -C 1
900 # hg update -C 1
901 $ hg merge -r 3 --config ui.merge=internal:forcedump
901 $ hg merge -r 3 --config ui.merge=internal:forcedump
902 merging f
902 merging f
903 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
903 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
904 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
904 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
905 [1]
905 [1]
906 $ aftermerge
906 $ aftermerge
907 # cat f
907 # cat f
908 revision 1
908 revision 1
909 space
909 space
910 # hg stat
910 # hg stat
911 M f
911 M f
912 ? f.base
912 ? f.base
913 ? f.local
913 ? f.local
914 ? f.orig
914 ? f.orig
915 ? f.other
915 ? f.other
916 # hg resolve --list
916 # hg resolve --list
917 U f
917 U f
918
918
919 $ cat f.base
919 $ cat f.base
920 revision 0
920 revision 0
921 space
921 space
922
922
923 $ cat f.local
923 $ cat f.local
924 revision 1
924 revision 1
925 space
925 space
926
926
927 $ cat f.other
927 $ cat f.other
928 revision 0
928 revision 0
929 space
929 space
930 revision 3
930 revision 3
931
931
932 $ rm -f f.base f.local f.other
932 $ rm -f f.base f.local f.other
933
933
934 ui.merge specifies internal:other but is overruled by pattern for false:
934 ui.merge specifies internal:other but is overruled by pattern for false:
935
935
936 $ beforemerge
936 $ beforemerge
937 [merge-tools]
937 [merge-tools]
938 false.whatever=
938 false.whatever=
939 true.priority=1
939 true.priority=1
940 true.executable=cat
940 true.executable=cat
941 # hg update -C 1
941 # hg update -C 1
942 $ hg merge -r 2 --config ui.merge=internal:other --config merge-patterns.f=false
942 $ hg merge -r 2 --config ui.merge=internal:other --config merge-patterns.f=false
943 merging f
943 merging f
944 merging f failed!
944 merging f failed!
945 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
945 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
946 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
946 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
947 [1]
947 [1]
948 $ aftermerge
948 $ aftermerge
949 # cat f
949 # cat f
950 revision 1
950 revision 1
951 space
951 space
952 # hg stat
952 # hg stat
953 M f
953 M f
954 ? f.orig
954 ? f.orig
955 # hg resolve --list
955 # hg resolve --list
956 U f
956 U f
957
957
958 Premerge
958 Premerge
959
959
960 ui.merge specifies internal:other but is overruled by --tool=false
960 ui.merge specifies internal:other but is overruled by --tool=false
961
961
962 $ beforemerge
962 $ beforemerge
963 [merge-tools]
963 [merge-tools]
964 false.whatever=
964 false.whatever=
965 true.priority=1
965 true.priority=1
966 true.executable=cat
966 true.executable=cat
967 # hg update -C 1
967 # hg update -C 1
968 $ hg merge -r 2 --config ui.merge=internal:other --tool=false
968 $ hg merge -r 2 --config ui.merge=internal:other --tool=false
969 merging f
969 merging f
970 merging f failed!
970 merging f failed!
971 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
971 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
972 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
972 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
973 [1]
973 [1]
974 $ aftermerge
974 $ aftermerge
975 # cat f
975 # cat f
976 revision 1
976 revision 1
977 space
977 space
978 # hg stat
978 # hg stat
979 M f
979 M f
980 ? f.orig
980 ? f.orig
981 # hg resolve --list
981 # hg resolve --list
982 U f
982 U f
983
983
984 HGMERGE specifies internal:other but is overruled by --tool=false
984 HGMERGE specifies internal:other but is overruled by --tool=false
985
985
986 $ HGMERGE=internal:other ; export HGMERGE
986 $ HGMERGE=internal:other ; export HGMERGE
987 $ beforemerge
987 $ beforemerge
988 [merge-tools]
988 [merge-tools]
989 false.whatever=
989 false.whatever=
990 true.priority=1
990 true.priority=1
991 true.executable=cat
991 true.executable=cat
992 # hg update -C 1
992 # hg update -C 1
993 $ hg merge -r 2 --tool=false
993 $ hg merge -r 2 --tool=false
994 merging f
994 merging f
995 merging f failed!
995 merging f failed!
996 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
996 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
997 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
997 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
998 [1]
998 [1]
999 $ aftermerge
999 $ aftermerge
1000 # cat f
1000 # cat f
1001 revision 1
1001 revision 1
1002 space
1002 space
1003 # hg stat
1003 # hg stat
1004 M f
1004 M f
1005 ? f.orig
1005 ? f.orig
1006 # hg resolve --list
1006 # hg resolve --list
1007 U f
1007 U f
1008
1008
1009 $ unset HGMERGE # make sure HGMERGE doesn't interfere with remaining tests
1009 $ unset HGMERGE # make sure HGMERGE doesn't interfere with remaining tests
1010
1010
1011 update is a merge ...
1011 update is a merge ...
1012
1012
1013 (this also tests that files reverted with '--rev REV' are treated as
1013 (this also tests that files reverted with '--rev REV' are treated as
1014 "modified", even if none of mode, size and timestamp of them isn't
1014 "modified", even if none of mode, size and timestamp of them isn't
1015 changed on the filesystem (see also issue4583))
1015 changed on the filesystem (see also issue4583))
1016
1016
1017 $ cat >> $HGRCPATH <<EOF
1017 $ cat >> $HGRCPATH <<EOF
1018 > [fakedirstatewritetime]
1018 > [fakedirstatewritetime]
1019 > # emulate invoking dirstate.write() via repo.status()
1019 > # emulate invoking dirstate.write() via repo.status()
1020 > # at 2000-01-01 00:00
1020 > # at 2000-01-01 00:00
1021 > fakenow = 200001010000
1021 > fakenow = 200001010000
1022 > EOF
1022 > EOF
1023
1023
1024 $ beforemerge
1024 $ beforemerge
1025 [merge-tools]
1025 [merge-tools]
1026 false.whatever=
1026 false.whatever=
1027 true.priority=1
1027 true.priority=1
1028 true.executable=cat
1028 true.executable=cat
1029 # hg update -C 1
1029 # hg update -C 1
1030 $ hg update -q 0
1030 $ hg update -q 0
1031 $ f -s f
1031 $ f -s f
1032 f: size=17
1032 f: size=17
1033 $ touch -t 200001010000 f
1033 $ touch -t 200001010000 f
1034 $ hg debugrebuildstate
1034 $ hg debugrebuildstate
1035 $ cat >> $HGRCPATH <<EOF
1035 $ cat >> $HGRCPATH <<EOF
1036 > [extensions]
1036 > [extensions]
1037 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
1037 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
1038 > EOF
1038 > EOF
1039 $ hg revert -q -r 1 .
1039 $ hg revert -q -r 1 .
1040 $ cat >> $HGRCPATH <<EOF
1040 $ cat >> $HGRCPATH <<EOF
1041 > [extensions]
1041 > [extensions]
1042 > fakedirstatewritetime = !
1042 > fakedirstatewritetime = !
1043 > EOF
1043 > EOF
1044 $ f -s f
1044 $ f -s f
1045 f: size=17
1045 f: size=17
1046 $ touch -t 200001010000 f
1046 $ touch -t 200001010000 f
1047 $ hg status f
1047 $ hg status f
1048 M f
1048 M f
1049 $ hg update -r 2
1049 $ hg update -r 2
1050 merging f
1050 merging f
1051 revision 1
1051 revision 1
1052 space
1052 space
1053 revision 0
1053 revision 0
1054 space
1054 space
1055 revision 2
1055 revision 2
1056 space
1056 space
1057 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1057 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1058 $ aftermerge
1058 $ aftermerge
1059 # cat f
1059 # cat f
1060 revision 1
1060 revision 1
1061 space
1061 space
1062 # hg stat
1062 # hg stat
1063 M f
1063 M f
1064 # hg resolve --list
1064 # hg resolve --list
1065 R f
1065 R f
1066
1066
1067 update should also have --tool
1067 update should also have --tool
1068
1068
1069 $ beforemerge
1069 $ beforemerge
1070 [merge-tools]
1070 [merge-tools]
1071 false.whatever=
1071 false.whatever=
1072 true.priority=1
1072 true.priority=1
1073 true.executable=cat
1073 true.executable=cat
1074 # hg update -C 1
1074 # hg update -C 1
1075 $ hg update -q 0
1075 $ hg update -q 0
1076 $ f -s f
1076 $ f -s f
1077 f: size=17
1077 f: size=17
1078 $ touch -t 200001010000 f
1078 $ touch -t 200001010000 f
1079 $ hg debugrebuildstate
1079 $ hg debugrebuildstate
1080 $ cat >> $HGRCPATH <<EOF
1080 $ cat >> $HGRCPATH <<EOF
1081 > [extensions]
1081 > [extensions]
1082 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
1082 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
1083 > EOF
1083 > EOF
1084 $ hg revert -q -r 1 .
1084 $ hg revert -q -r 1 .
1085 $ cat >> $HGRCPATH <<EOF
1085 $ cat >> $HGRCPATH <<EOF
1086 > [extensions]
1086 > [extensions]
1087 > fakedirstatewritetime = !
1087 > fakedirstatewritetime = !
1088 > EOF
1088 > EOF
1089 $ f -s f
1089 $ f -s f
1090 f: size=17
1090 f: size=17
1091 $ touch -t 200001010000 f
1091 $ touch -t 200001010000 f
1092 $ hg status f
1092 $ hg status f
1093 M f
1093 M f
1094 $ hg update -r 2 --tool false
1094 $ hg update -r 2 --tool false
1095 merging f
1095 merging f
1096 merging f failed!
1096 merging f failed!
1097 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1097 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1098 use 'hg resolve' to retry unresolved file merges
1098 use 'hg resolve' to retry unresolved file merges
1099 [1]
1099 [1]
1100 $ aftermerge
1100 $ aftermerge
1101 # cat f
1101 # cat f
1102 revision 1
1102 revision 1
1103 space
1103 space
1104 # hg stat
1104 # hg stat
1105 M f
1105 M f
1106 ? f.orig
1106 ? f.orig
1107 # hg resolve --list
1107 # hg resolve --list
1108 U f
1108 U f
1109
1109
1110 Default is silent simplemerge:
1110 Default is silent simplemerge:
1111
1111
1112 $ beforemerge
1112 $ beforemerge
1113 [merge-tools]
1113 [merge-tools]
1114 false.whatever=
1114 false.whatever=
1115 true.priority=1
1115 true.priority=1
1116 true.executable=cat
1116 true.executable=cat
1117 # hg update -C 1
1117 # hg update -C 1
1118 $ hg merge -r 3
1118 $ hg merge -r 3
1119 merging f
1119 merging f
1120 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1120 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1121 (branch merge, don't forget to commit)
1121 (branch merge, don't forget to commit)
1122 $ aftermerge
1122 $ aftermerge
1123 # cat f
1123 # cat f
1124 revision 1
1124 revision 1
1125 space
1125 space
1126 revision 3
1126 revision 3
1127 # hg stat
1127 # hg stat
1128 M f
1128 M f
1129 # hg resolve --list
1129 # hg resolve --list
1130 R f
1130 R f
1131
1131
1132 .premerge=True is same:
1132 .premerge=True is same:
1133
1133
1134 $ beforemerge
1134 $ beforemerge
1135 [merge-tools]
1135 [merge-tools]
1136 false.whatever=
1136 false.whatever=
1137 true.priority=1
1137 true.priority=1
1138 true.executable=cat
1138 true.executable=cat
1139 # hg update -C 1
1139 # hg update -C 1
1140 $ hg merge -r 3 --config merge-tools.true.premerge=True
1140 $ hg merge -r 3 --config merge-tools.true.premerge=True
1141 merging f
1141 merging f
1142 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1142 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1143 (branch merge, don't forget to commit)
1143 (branch merge, don't forget to commit)
1144 $ aftermerge
1144 $ aftermerge
1145 # cat f
1145 # cat f
1146 revision 1
1146 revision 1
1147 space
1147 space
1148 revision 3
1148 revision 3
1149 # hg stat
1149 # hg stat
1150 M f
1150 M f
1151 # hg resolve --list
1151 # hg resolve --list
1152 R f
1152 R f
1153
1153
1154 .premerge=False executes merge-tool:
1154 .premerge=False executes merge-tool:
1155
1155
1156 $ beforemerge
1156 $ beforemerge
1157 [merge-tools]
1157 [merge-tools]
1158 false.whatever=
1158 false.whatever=
1159 true.priority=1
1159 true.priority=1
1160 true.executable=cat
1160 true.executable=cat
1161 # hg update -C 1
1161 # hg update -C 1
1162 $ hg merge -r 3 --config merge-tools.true.premerge=False
1162 $ hg merge -r 3 --config merge-tools.true.premerge=False
1163 merging f
1163 merging f
1164 revision 1
1164 revision 1
1165 space
1165 space
1166 revision 0
1166 revision 0
1167 space
1167 space
1168 revision 0
1168 revision 0
1169 space
1169 space
1170 revision 3
1170 revision 3
1171 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1171 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1172 (branch merge, don't forget to commit)
1172 (branch merge, don't forget to commit)
1173 $ aftermerge
1173 $ aftermerge
1174 # cat f
1174 # cat f
1175 revision 1
1175 revision 1
1176 space
1176 space
1177 # hg stat
1177 # hg stat
1178 M f
1178 M f
1179 # hg resolve --list
1179 # hg resolve --list
1180 R f
1180 R f
1181
1181
1182 premerge=keep keeps conflict markers in:
1182 premerge=keep keeps conflict markers in:
1183
1183
1184 $ beforemerge
1184 $ beforemerge
1185 [merge-tools]
1185 [merge-tools]
1186 false.whatever=
1186 false.whatever=
1187 true.priority=1
1187 true.priority=1
1188 true.executable=cat
1188 true.executable=cat
1189 # hg update -C 1
1189 # hg update -C 1
1190 $ hg merge -r 4 --config merge-tools.true.premerge=keep
1190 $ hg merge -r 4 --config merge-tools.true.premerge=keep
1191 merging f
1191 merging f
1192 <<<<<<< working copy: ef83787e2614 - test: revision 1
1192 <<<<<<< working copy: ef83787e2614 - test: revision 1
1193 revision 1
1193 revision 1
1194 space
1194 space
1195 =======
1195 =======
1196 revision 4
1196 revision 4
1197 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1197 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1198 revision 0
1198 revision 0
1199 space
1199 space
1200 revision 4
1200 revision 4
1201 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1201 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1202 (branch merge, don't forget to commit)
1202 (branch merge, don't forget to commit)
1203 $ aftermerge
1203 $ aftermerge
1204 # cat f
1204 # cat f
1205 <<<<<<< working copy: ef83787e2614 - test: revision 1
1205 <<<<<<< working copy: ef83787e2614 - test: revision 1
1206 revision 1
1206 revision 1
1207 space
1207 space
1208 =======
1208 =======
1209 revision 4
1209 revision 4
1210 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1210 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1211 # hg stat
1211 # hg stat
1212 M f
1212 M f
1213 # hg resolve --list
1213 # hg resolve --list
1214 R f
1214 R f
1215
1215
1216 premerge=keep-merge3 keeps conflict markers with base content:
1216 premerge=keep-merge3 keeps conflict markers with base content:
1217
1217
1218 $ beforemerge
1218 $ beforemerge
1219 [merge-tools]
1219 [merge-tools]
1220 false.whatever=
1220 false.whatever=
1221 true.priority=1
1221 true.priority=1
1222 true.executable=cat
1222 true.executable=cat
1223 # hg update -C 1
1223 # hg update -C 1
1224 $ hg merge -r 4 --config merge-tools.true.premerge=keep-merge3
1224 $ hg merge -r 4 --config merge-tools.true.premerge=keep-merge3
1225 merging f
1225 merging f
1226 <<<<<<< working copy: ef83787e2614 - test: revision 1
1226 <<<<<<< working copy: ef83787e2614 - test: revision 1
1227 revision 1
1227 revision 1
1228 space
1228 space
1229 ||||||| base
1229 ||||||| base
1230 revision 0
1230 revision 0
1231 space
1231 space
1232 =======
1232 =======
1233 revision 4
1233 revision 4
1234 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1234 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1235 revision 0
1235 revision 0
1236 space
1236 space
1237 revision 4
1237 revision 4
1238 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1238 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1239 (branch merge, don't forget to commit)
1239 (branch merge, don't forget to commit)
1240 $ aftermerge
1240 $ aftermerge
1241 # cat f
1241 # cat f
1242 <<<<<<< working copy: ef83787e2614 - test: revision 1
1242 <<<<<<< working copy: ef83787e2614 - test: revision 1
1243 revision 1
1243 revision 1
1244 space
1244 space
1245 ||||||| base
1245 ||||||| base
1246 revision 0
1246 revision 0
1247 space
1247 space
1248 =======
1248 =======
1249 revision 4
1249 revision 4
1250 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1250 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1251 # hg stat
1251 # hg stat
1252 M f
1252 M f
1253 # hg resolve --list
1253 # hg resolve --list
1254 R f
1254 R f
1255
1255
1256 premerge=keep respects ui.mergemarkers=basic:
1256 premerge=keep respects ui.mergemarkers=basic:
1257
1257
1258 $ beforemerge
1258 $ beforemerge
1259 [merge-tools]
1259 [merge-tools]
1260 false.whatever=
1260 false.whatever=
1261 true.priority=1
1261 true.priority=1
1262 true.executable=cat
1262 true.executable=cat
1263 # hg update -C 1
1263 # hg update -C 1
1264 $ hg merge -r 4 --config merge-tools.true.premerge=keep --config ui.mergemarkers=basic
1264 $ hg merge -r 4 --config merge-tools.true.premerge=keep --config ui.mergemarkers=basic
1265 merging f
1265 merging f
1266 <<<<<<< working copy
1266 <<<<<<< working copy
1267 revision 1
1267 revision 1
1268 space
1268 space
1269 =======
1269 =======
1270 revision 4
1270 revision 4
1271 >>>>>>> merge rev
1271 >>>>>>> merge rev
1272 revision 0
1272 revision 0
1273 space
1273 space
1274 revision 4
1274 revision 4
1275 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1275 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1276 (branch merge, don't forget to commit)
1276 (branch merge, don't forget to commit)
1277 $ aftermerge
1277 $ aftermerge
1278 # cat f
1278 # cat f
1279 <<<<<<< working copy
1279 <<<<<<< working copy
1280 revision 1
1280 revision 1
1281 space
1281 space
1282 =======
1282 =======
1283 revision 4
1283 revision 4
1284 >>>>>>> merge rev
1284 >>>>>>> merge rev
1285 # hg stat
1285 # hg stat
1286 M f
1286 M f
1287 # hg resolve --list
1287 # hg resolve --list
1288 R f
1288 R f
1289
1289
1290 premerge=keep ignores ui.mergemarkers=basic if true.mergemarkers=detailed:
1290 premerge=keep ignores ui.mergemarkers=basic if true.mergemarkers=detailed:
1291
1291
1292 $ beforemerge
1292 $ beforemerge
1293 [merge-tools]
1293 [merge-tools]
1294 false.whatever=
1294 false.whatever=
1295 true.priority=1
1295 true.priority=1
1296 true.executable=cat
1296 true.executable=cat
1297 # hg update -C 1
1297 # hg update -C 1
1298 $ hg merge -r 4 --config merge-tools.true.premerge=keep \
1298 $ hg merge -r 4 --config merge-tools.true.premerge=keep \
1299 > --config ui.mergemarkers=basic \
1299 > --config ui.mergemarkers=basic \
1300 > --config merge-tools.true.mergemarkers=detailed
1300 > --config merge-tools.true.mergemarkers=detailed
1301 merging f
1301 merging f
1302 <<<<<<< working copy: ef83787e2614 - test: revision 1
1302 <<<<<<< working copy: ef83787e2614 - test: revision 1
1303 revision 1
1303 revision 1
1304 space
1304 space
1305 =======
1305 =======
1306 revision 4
1306 revision 4
1307 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1307 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1308 revision 0
1308 revision 0
1309 space
1309 space
1310 revision 4
1310 revision 4
1311 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1311 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1312 (branch merge, don't forget to commit)
1312 (branch merge, don't forget to commit)
1313 $ aftermerge
1313 $ aftermerge
1314 # cat f
1314 # cat f
1315 <<<<<<< working copy: ef83787e2614 - test: revision 1
1315 <<<<<<< working copy: ef83787e2614 - test: revision 1
1316 revision 1
1316 revision 1
1317 space
1317 space
1318 =======
1318 =======
1319 revision 4
1319 revision 4
1320 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1320 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1321 # hg stat
1321 # hg stat
1322 M f
1322 M f
1323 # hg resolve --list
1323 # hg resolve --list
1324 R f
1324 R f
1325
1325
1326 premerge=keep respects ui.mergemarkertemplate instead of
1326 premerge=keep respects ui.mergemarkertemplate instead of
1327 true.mergemarkertemplate if true.mergemarkers=basic:
1327 true.mergemarkertemplate if true.mergemarkers=basic:
1328
1328
1329 $ beforemerge
1329 $ beforemerge
1330 [merge-tools]
1330 [merge-tools]
1331 false.whatever=
1331 false.whatever=
1332 true.priority=1
1332 true.priority=1
1333 true.executable=cat
1333 true.executable=cat
1334 # hg update -C 1
1334 # hg update -C 1
1335 $ hg merge -r 4 --config merge-tools.true.premerge=keep \
1335 $ hg merge -r 4 --config merge-tools.true.premerge=keep \
1336 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1336 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1337 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}'
1337 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}'
1338 merging f
1338 merging f
1339 <<<<<<< working copy: uitmpl 1
1339 <<<<<<< working copy: uitmpl 1
1340 revision 1
1340 revision 1
1341 space
1341 space
1342 =======
1342 =======
1343 revision 4
1343 revision 4
1344 >>>>>>> merge rev: uitmpl 4
1344 >>>>>>> merge rev: uitmpl 4
1345 revision 0
1345 revision 0
1346 space
1346 space
1347 revision 4
1347 revision 4
1348 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1348 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1349 (branch merge, don't forget to commit)
1349 (branch merge, don't forget to commit)
1350 $ aftermerge
1350 $ aftermerge
1351 # cat f
1351 # cat f
1352 <<<<<<< working copy: uitmpl 1
1352 <<<<<<< working copy: uitmpl 1
1353 revision 1
1353 revision 1
1354 space
1354 space
1355 =======
1355 =======
1356 revision 4
1356 revision 4
1357 >>>>>>> merge rev: uitmpl 4
1357 >>>>>>> merge rev: uitmpl 4
1358 # hg stat
1358 # hg stat
1359 M f
1359 M f
1360 # hg resolve --list
1360 # hg resolve --list
1361 R f
1361 R f
1362
1362
1363 premerge=keep respects true.mergemarkertemplate instead of
1363 premerge=keep respects true.mergemarkertemplate instead of
1364 true.mergemarkertemplate if true.mergemarkers=detailed:
1364 true.mergemarkertemplate if true.mergemarkers=detailed:
1365
1365
1366 $ beforemerge
1366 $ beforemerge
1367 [merge-tools]
1367 [merge-tools]
1368 false.whatever=
1368 false.whatever=
1369 true.priority=1
1369 true.priority=1
1370 true.executable=cat
1370 true.executable=cat
1371 # hg update -C 1
1371 # hg update -C 1
1372 $ hg merge -r 4 --config merge-tools.true.premerge=keep \
1372 $ hg merge -r 4 --config merge-tools.true.premerge=keep \
1373 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1373 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1374 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1374 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1375 > --config merge-tools.true.mergemarkers=detailed
1375 > --config merge-tools.true.mergemarkers=detailed
1376 merging f
1376 merging f
1377 <<<<<<< working copy: tooltmpl ef83787e2614
1377 <<<<<<< working copy: tooltmpl ef83787e2614
1378 revision 1
1378 revision 1
1379 space
1379 space
1380 =======
1380 =======
1381 revision 4
1381 revision 4
1382 >>>>>>> merge rev: tooltmpl 81448d39c9a0
1382 >>>>>>> merge rev: tooltmpl 81448d39c9a0
1383 revision 0
1383 revision 0
1384 space
1384 space
1385 revision 4
1385 revision 4
1386 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1386 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1387 (branch merge, don't forget to commit)
1387 (branch merge, don't forget to commit)
1388 $ aftermerge
1388 $ aftermerge
1389 # cat f
1389 # cat f
1390 <<<<<<< working copy: tooltmpl ef83787e2614
1390 <<<<<<< working copy: tooltmpl ef83787e2614
1391 revision 1
1391 revision 1
1392 space
1392 space
1393 =======
1393 =======
1394 revision 4
1394 revision 4
1395 >>>>>>> merge rev: tooltmpl 81448d39c9a0
1395 >>>>>>> merge rev: tooltmpl 81448d39c9a0
1396 # hg stat
1396 # hg stat
1397 M f
1397 M f
1398 # hg resolve --list
1398 # hg resolve --list
1399 R f
1399 R f
1400
1400
1401 Tool execution
1401 Tool execution
1402
1402
1403 set tools.args explicit to include $base $local $other $output:
1403 set tools.args explicit to include $base $local $other $output:
1404
1404
1405 $ beforemerge
1405 $ beforemerge
1406 [merge-tools]
1406 [merge-tools]
1407 false.whatever=
1407 false.whatever=
1408 true.priority=1
1408 true.priority=1
1409 true.executable=cat
1409 true.executable=cat
1410 # hg update -C 1
1410 # hg update -C 1
1411 $ hg merge -r 2 --config merge-tools.true.executable=head --config merge-tools.true.args='$base $local $other $output' \
1411 $ hg merge -r 2 --config merge-tools.true.executable=head --config merge-tools.true.args='$base $local $other $output' \
1412 > | sed 's,==> .* <==,==> ... <==,g'
1412 > | sed 's,==> .* <==,==> ... <==,g'
1413 merging f
1413 merging f
1414 ==> ... <==
1414 ==> ... <==
1415 revision 0
1415 revision 0
1416 space
1416 space
1417
1417
1418 ==> ... <==
1418 ==> ... <==
1419 revision 1
1419 revision 1
1420 space
1420 space
1421
1421
1422 ==> ... <==
1422 ==> ... <==
1423 revision 2
1423 revision 2
1424 space
1424 space
1425
1425
1426 ==> ... <==
1426 ==> ... <==
1427 revision 1
1427 revision 1
1428 space
1428 space
1429 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1429 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1430 (branch merge, don't forget to commit)
1430 (branch merge, don't forget to commit)
1431 $ aftermerge
1431 $ aftermerge
1432 # cat f
1432 # cat f
1433 revision 1
1433 revision 1
1434 space
1434 space
1435 # hg stat
1435 # hg stat
1436 M f
1436 M f
1437 # hg resolve --list
1437 # hg resolve --list
1438 R f
1438 R f
1439
1439
1440 Merge with "echo mergeresult > $local":
1440 Merge with "echo mergeresult > $local":
1441
1441
1442 $ beforemerge
1442 $ beforemerge
1443 [merge-tools]
1443 [merge-tools]
1444 false.whatever=
1444 false.whatever=
1445 true.priority=1
1445 true.priority=1
1446 true.executable=cat
1446 true.executable=cat
1447 # hg update -C 1
1447 # hg update -C 1
1448 $ hg merge -r 2 --config merge-tools.true.executable=echo --config merge-tools.true.args='mergeresult > $local'
1448 $ hg merge -r 2 --config merge-tools.true.executable=echo --config merge-tools.true.args='mergeresult > $local'
1449 merging f
1449 merging f
1450 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1450 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1451 (branch merge, don't forget to commit)
1451 (branch merge, don't forget to commit)
1452 $ aftermerge
1452 $ aftermerge
1453 # cat f
1453 # cat f
1454 mergeresult
1454 mergeresult
1455 # hg stat
1455 # hg stat
1456 M f
1456 M f
1457 # hg resolve --list
1457 # hg resolve --list
1458 R f
1458 R f
1459
1459
1460 - and $local is the file f:
1460 - and $local is the file f:
1461
1461
1462 $ beforemerge
1462 $ beforemerge
1463 [merge-tools]
1463 [merge-tools]
1464 false.whatever=
1464 false.whatever=
1465 true.priority=1
1465 true.priority=1
1466 true.executable=cat
1466 true.executable=cat
1467 # hg update -C 1
1467 # hg update -C 1
1468 $ hg merge -r 2 --config merge-tools.true.executable=echo --config merge-tools.true.args='mergeresult > f'
1468 $ hg merge -r 2 --config merge-tools.true.executable=echo --config merge-tools.true.args='mergeresult > f'
1469 merging f
1469 merging f
1470 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1470 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1471 (branch merge, don't forget to commit)
1471 (branch merge, don't forget to commit)
1472 $ aftermerge
1472 $ aftermerge
1473 # cat f
1473 # cat f
1474 mergeresult
1474 mergeresult
1475 # hg stat
1475 # hg stat
1476 M f
1476 M f
1477 # hg resolve --list
1477 # hg resolve --list
1478 R f
1478 R f
1479
1479
1480 Merge with "echo mergeresult > $output" - the variable is a bit magic:
1480 Merge with "echo mergeresult > $output" - the variable is a bit magic:
1481
1481
1482 $ beforemerge
1482 $ beforemerge
1483 [merge-tools]
1483 [merge-tools]
1484 false.whatever=
1484 false.whatever=
1485 true.priority=1
1485 true.priority=1
1486 true.executable=cat
1486 true.executable=cat
1487 # hg update -C 1
1487 # hg update -C 1
1488 $ hg merge -r 2 --config merge-tools.true.executable=echo --config merge-tools.true.args='mergeresult > $output'
1488 $ hg merge -r 2 --config merge-tools.true.executable=echo --config merge-tools.true.args='mergeresult > $output'
1489 merging f
1489 merging f
1490 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1490 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1491 (branch merge, don't forget to commit)
1491 (branch merge, don't forget to commit)
1492 $ aftermerge
1492 $ aftermerge
1493 # cat f
1493 # cat f
1494 mergeresult
1494 mergeresult
1495 # hg stat
1495 # hg stat
1496 M f
1496 M f
1497 # hg resolve --list
1497 # hg resolve --list
1498 R f
1498 R f
1499
1499
1500 Merge using tool with a path that must be quoted:
1500 Merge using tool with a path that must be quoted:
1501
1501
1502 $ beforemerge
1502 $ beforemerge
1503 [merge-tools]
1503 [merge-tools]
1504 false.whatever=
1504 false.whatever=
1505 true.priority=1
1505 true.priority=1
1506 true.executable=cat
1506 true.executable=cat
1507 # hg update -C 1
1507 # hg update -C 1
1508 $ cat <<EOF > 'my merge tool'
1508 $ cat <<EOF > 'my merge tool'
1509 > cat "\$1" "\$2" "\$3" > "\$4"
1509 > cat "\$1" "\$2" "\$3" > "\$4"
1510 > EOF
1510 > EOF
1511 $ hg --config merge-tools.true.executable='sh' \
1511 $ hg --config merge-tools.true.executable='sh' \
1512 > --config merge-tools.true.args='"./my merge tool" $base $local $other $output' \
1512 > --config merge-tools.true.args='"./my merge tool" $base $local $other $output' \
1513 > merge -r 2
1513 > merge -r 2
1514 merging f
1514 merging f
1515 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1515 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1516 (branch merge, don't forget to commit)
1516 (branch merge, don't forget to commit)
1517 $ rm -f 'my merge tool'
1517 $ rm -f 'my merge tool'
1518 $ aftermerge
1518 $ aftermerge
1519 # cat f
1519 # cat f
1520 revision 0
1520 revision 0
1521 space
1521 space
1522 revision 1
1522 revision 1
1523 space
1523 space
1524 revision 2
1524 revision 2
1525 space
1525 space
1526 # hg stat
1526 # hg stat
1527 M f
1527 M f
1528 # hg resolve --list
1528 # hg resolve --list
1529 R f
1529 R f
1530
1530
1531 Merge using a tool that supports labellocal, labelother, and labelbase, checking
1531 Merge using a tool that supports labellocal, labelother, and labelbase, checking
1532 that they're quoted properly as well. This is using the default 'basic'
1532 that they're quoted properly as well. This is using the default 'basic'
1533 mergemarkers even though ui.mergemarkers is 'detailed', so it's ignoring both
1533 mergemarkers even though ui.mergemarkers is 'detailed', so it's ignoring both
1534 mergemarkertemplate settings:
1534 mergemarkertemplate settings:
1535
1535
1536 $ beforemerge
1536 $ beforemerge
1537 [merge-tools]
1537 [merge-tools]
1538 false.whatever=
1538 false.whatever=
1539 true.priority=1
1539 true.priority=1
1540 true.executable=cat
1540 true.executable=cat
1541 # hg update -C 1
1541 # hg update -C 1
1542 $ cat <<EOF > printargs_merge_tool
1542 $ cat <<EOF > printargs_merge_tool
1543 > while test \$# -gt 0; do echo arg: \"\$1\"; shift; done
1543 > while test \$# -gt 0; do echo arg: \"\$1\"; shift; done
1544 > EOF
1544 > EOF
1545 $ hg --config merge-tools.true.executable='sh' \
1545 $ hg --config merge-tools.true.executable='sh' \
1546 > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \
1546 > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \
1547 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1547 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1548 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1548 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1549 > --config ui.mergemarkers=detailed \
1549 > --config ui.mergemarkers=detailed \
1550 > merge -r 2
1550 > merge -r 2
1551 merging f
1551 merging f
1552 arg: "ll:working copy"
1552 arg: "ll:working copy"
1553 arg: "lo:"
1553 arg: "lo:"
1554 arg: "merge rev"
1554 arg: "merge rev"
1555 arg: "lb:base: */f~base.*" (glob)
1555 arg: "lb:base: */f~base.*" (glob)
1556 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1556 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1557 (branch merge, don't forget to commit)
1557 (branch merge, don't forget to commit)
1558 $ rm -f 'printargs_merge_tool'
1558 $ rm -f 'printargs_merge_tool'
1559
1559
1560 Same test with experimental.mergetempdirprefix set:
1560 Same test with experimental.mergetempdirprefix set:
1561
1561
1562 $ beforemerge
1562 $ beforemerge
1563 [merge-tools]
1563 [merge-tools]
1564 false.whatever=
1564 false.whatever=
1565 true.priority=1
1565 true.priority=1
1566 true.executable=cat
1566 true.executable=cat
1567 # hg update -C 1
1567 # hg update -C 1
1568 $ cat <<EOF > printargs_merge_tool
1568 $ cat <<EOF > printargs_merge_tool
1569 > while test \$# -gt 0; do echo arg: \"\$1\"; shift; done
1569 > while test \$# -gt 0; do echo arg: \"\$1\"; shift; done
1570 > EOF
1570 > EOF
1571 $ hg --config experimental.mergetempdirprefix=$TESTTMP/hgmerge. \
1571 $ hg --config experimental.mergetempdirprefix=$TESTTMP/hgmerge. \
1572 > --config merge-tools.true.executable='sh' \
1572 > --config merge-tools.true.executable='sh' \
1573 > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \
1573 > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \
1574 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1574 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1575 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1575 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1576 > --config ui.mergemarkers=detailed \
1576 > --config ui.mergemarkers=detailed \
1577 > merge -r 2
1577 > merge -r 2
1578 merging f
1578 merging f
1579 arg: "ll:working copy"
1579 arg: "ll:working copy"
1580 arg: "lo:"
1580 arg: "lo:"
1581 arg: "merge rev"
1581 arg: "merge rev"
1582 arg: "lb:base: */hgmerge.*/f~base" (glob)
1582 arg: "lb:base: */hgmerge.*/f~base" (glob)
1583 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1583 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1584 (branch merge, don't forget to commit)
1584 (branch merge, don't forget to commit)
1585 $ rm -f 'printargs_merge_tool'
1585 $ rm -f 'printargs_merge_tool'
1586
1586
1587 Merge using a tool that supports labellocal, labelother, and labelbase, checking
1587 Merge using a tool that supports labellocal, labelother, and labelbase, checking
1588 that they're quoted properly as well. This is using 'detailed' mergemarkers,
1588 that they're quoted properly as well. This is using 'detailed' mergemarkers,
1589 even though ui.mergemarkers is 'basic', and using the tool's
1589 even though ui.mergemarkers is 'basic', and using the tool's
1590 mergemarkertemplate:
1590 mergemarkertemplate:
1591
1591
1592 $ beforemerge
1592 $ beforemerge
1593 [merge-tools]
1593 [merge-tools]
1594 false.whatever=
1594 false.whatever=
1595 true.priority=1
1595 true.priority=1
1596 true.executable=cat
1596 true.executable=cat
1597 # hg update -C 1
1597 # hg update -C 1
1598 $ cat <<EOF > printargs_merge_tool
1598 $ cat <<EOF > printargs_merge_tool
1599 > while test \$# -gt 0; do echo arg: \"\$1\"; shift; done
1599 > while test \$# -gt 0; do echo arg: \"\$1\"; shift; done
1600 > EOF
1600 > EOF
1601 $ hg --config merge-tools.true.executable='sh' \
1601 $ hg --config merge-tools.true.executable='sh' \
1602 > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \
1602 > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \
1603 > --config merge-tools.true.mergemarkers=detailed \
1603 > --config merge-tools.true.mergemarkers=detailed \
1604 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1604 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1605 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1605 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1606 > --config ui.mergemarkers=basic \
1606 > --config ui.mergemarkers=basic \
1607 > merge -r 2
1607 > merge -r 2
1608 merging f
1608 merging f
1609 arg: "ll:working copy: tooltmpl ef83787e2614"
1609 arg: "ll:working copy: tooltmpl ef83787e2614"
1610 arg: "lo:"
1610 arg: "lo:"
1611 arg: "merge rev: tooltmpl 0185f4e0cf02"
1611 arg: "merge rev: tooltmpl 0185f4e0cf02"
1612 arg: "lb:base: */f~base.*" (glob)
1612 arg: "lb:base: */f~base.*" (glob)
1613 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1613 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1614 (branch merge, don't forget to commit)
1614 (branch merge, don't forget to commit)
1615 $ rm -f 'printargs_merge_tool'
1615 $ rm -f 'printargs_merge_tool'
1616
1616
1617 The merge tool still gets labellocal and labelother as 'basic' even when
1617 The merge tool still gets labellocal and labelother as 'basic' even when
1618 premerge=keep is used and has 'detailed' markers:
1618 premerge=keep is used and has 'detailed' markers:
1619
1619
1620 $ beforemerge
1620 $ beforemerge
1621 [merge-tools]
1621 [merge-tools]
1622 false.whatever=
1622 false.whatever=
1623 true.priority=1
1623 true.priority=1
1624 true.executable=cat
1624 true.executable=cat
1625 # hg update -C 1
1625 # hg update -C 1
1626 $ cat <<EOF > mytool
1626 $ cat <<EOF > mytool
1627 > echo labellocal: \"\$1\"
1627 > echo labellocal: \"\$1\"
1628 > echo labelother: \"\$2\"
1628 > echo labelother: \"\$2\"
1629 > echo "output (arg)": \"\$3\"
1629 > echo "output (arg)": \"\$3\"
1630 > echo "output (contents)":
1630 > echo "output (contents)":
1631 > cat "\$3"
1631 > cat "\$3"
1632 > EOF
1632 > EOF
1633 $ hg --config merge-tools.true.executable='sh' \
1633 $ hg --config merge-tools.true.executable='sh' \
1634 > --config merge-tools.true.args='mytool $labellocal $labelother $output' \
1634 > --config merge-tools.true.args='mytool $labellocal $labelother $output' \
1635 > --config merge-tools.true.premerge=keep \
1635 > --config merge-tools.true.premerge=keep \
1636 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1636 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1637 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1637 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1638 > --config ui.mergemarkers=detailed \
1638 > --config ui.mergemarkers=detailed \
1639 > merge -r 2
1639 > merge -r 2
1640 merging f
1640 merging f
1641 labellocal: "working copy"
1641 labellocal: "working copy"
1642 labelother: "merge rev"
1642 labelother: "merge rev"
1643 output (arg): "$TESTTMP/repo/f"
1643 output (arg): "$TESTTMP/repo/f"
1644 output (contents):
1644 output (contents):
1645 <<<<<<< working copy: uitmpl 1
1645 <<<<<<< working copy: uitmpl 1
1646 revision 1
1646 revision 1
1647 =======
1647 =======
1648 revision 2
1648 revision 2
1649 >>>>>>> merge rev: uitmpl 2
1649 >>>>>>> merge rev: uitmpl 2
1650 space
1650 space
1651 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1651 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1652 (branch merge, don't forget to commit)
1652 (branch merge, don't forget to commit)
1653 $ rm -f 'mytool'
1653 $ rm -f 'mytool'
1654
1654
1655 premerge=keep uses the *tool's* mergemarkertemplate if tool's
1655 premerge=keep uses the *tool's* mergemarkertemplate if tool's
1656 mergemarkers=detailed; labellocal and labelother also use the tool's template
1656 mergemarkers=detailed; labellocal and labelother also use the tool's template
1657
1657
1658 $ beforemerge
1658 $ beforemerge
1659 [merge-tools]
1659 [merge-tools]
1660 false.whatever=
1660 false.whatever=
1661 true.priority=1
1661 true.priority=1
1662 true.executable=cat
1662 true.executable=cat
1663 # hg update -C 1
1663 # hg update -C 1
1664 $ cat <<EOF > mytool
1664 $ cat <<EOF > mytool
1665 > echo labellocal: \"\$1\"
1665 > echo labellocal: \"\$1\"
1666 > echo labelother: \"\$2\"
1666 > echo labelother: \"\$2\"
1667 > echo "output (arg)": \"\$3\"
1667 > echo "output (arg)": \"\$3\"
1668 > echo "output (contents)":
1668 > echo "output (contents)":
1669 > cat "\$3"
1669 > cat "\$3"
1670 > EOF
1670 > EOF
1671 $ hg --config merge-tools.true.executable='sh' \
1671 $ hg --config merge-tools.true.executable='sh' \
1672 > --config merge-tools.true.args='mytool $labellocal $labelother $output' \
1672 > --config merge-tools.true.args='mytool $labellocal $labelother $output' \
1673 > --config merge-tools.true.premerge=keep \
1673 > --config merge-tools.true.premerge=keep \
1674 > --config merge-tools.true.mergemarkers=detailed \
1674 > --config merge-tools.true.mergemarkers=detailed \
1675 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1675 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1676 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1676 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1677 > --config ui.mergemarkers=detailed \
1677 > --config ui.mergemarkers=detailed \
1678 > merge -r 2
1678 > merge -r 2
1679 merging f
1679 merging f
1680 labellocal: "working copy: tooltmpl ef83787e2614"
1680 labellocal: "working copy: tooltmpl ef83787e2614"
1681 labelother: "merge rev: tooltmpl 0185f4e0cf02"
1681 labelother: "merge rev: tooltmpl 0185f4e0cf02"
1682 output (arg): "$TESTTMP/repo/f"
1682 output (arg): "$TESTTMP/repo/f"
1683 output (contents):
1683 output (contents):
1684 <<<<<<< working copy: tooltmpl ef83787e2614
1684 <<<<<<< working copy: tooltmpl ef83787e2614
1685 revision 1
1685 revision 1
1686 =======
1686 =======
1687 revision 2
1687 revision 2
1688 >>>>>>> merge rev: tooltmpl 0185f4e0cf02
1688 >>>>>>> merge rev: tooltmpl 0185f4e0cf02
1689 space
1689 space
1690 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1690 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1691 (branch merge, don't forget to commit)
1691 (branch merge, don't forget to commit)
1692 $ rm -f 'mytool'
1692 $ rm -f 'mytool'
1693
1693
1694 Issue3581: Merging a filename that needs to be quoted
1694 Issue3581: Merging a filename that needs to be quoted
1695 (This test doesn't work on Windows filesystems even on Linux, so check
1695 (This test doesn't work on Windows filesystems even on Linux, so check
1696 for Unix-like permission)
1696 for Unix-like permission)
1697
1697
1698 #if unix-permissions
1698 #if unix-permissions
1699 $ beforemerge
1699 $ beforemerge
1700 [merge-tools]
1700 [merge-tools]
1701 false.whatever=
1701 false.whatever=
1702 true.priority=1
1702 true.priority=1
1703 true.executable=cat
1703 true.executable=cat
1704 # hg update -C 1
1704 # hg update -C 1
1705 $ echo "revision 5" > '"; exit 1; echo "'
1705 $ echo "revision 5" > '"; exit 1; echo "'
1706 $ hg commit -Am "revision 5"
1706 $ hg commit -Am "revision 5"
1707 adding "; exit 1; echo "
1707 adding "; exit 1; echo "
1708 warning: filename contains '"', which is reserved on Windows: '"; exit 1; echo "'
1708 warning: filename contains '"', which is reserved on Windows: '"; exit 1; echo "'
1709 $ hg update -C 1 > /dev/null
1709 $ hg update -C 1 > /dev/null
1710 $ echo "revision 6" > '"; exit 1; echo "'
1710 $ echo "revision 6" > '"; exit 1; echo "'
1711 $ hg commit -Am "revision 6"
1711 $ hg commit -Am "revision 6"
1712 adding "; exit 1; echo "
1712 adding "; exit 1; echo "
1713 warning: filename contains '"', which is reserved on Windows: '"; exit 1; echo "'
1713 warning: filename contains '"', which is reserved on Windows: '"; exit 1; echo "'
1714 created new head
1714 created new head
1715 $ hg merge --config merge-tools.true.executable="true" -r 5
1715 $ hg merge --config merge-tools.true.executable="true" -r 5
1716 merging "; exit 1; echo "
1716 merging "; exit 1; echo "
1717 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1717 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1718 (branch merge, don't forget to commit)
1718 (branch merge, don't forget to commit)
1719 $ hg update -C 1 > /dev/null
1719 $ hg update -C 1 > /dev/null
1720
1720
1721 #else
1721 #else
1722
1722
1723 Match the non-portable filename commits above for test stability
1723 Match the non-portable filename commits above for test stability
1724
1724
1725 $ hg import --bypass -q - << EOF
1725 $ hg import --bypass -q - << EOF
1726 > # HG changeset patch
1726 > # HG changeset patch
1727 > revision 5
1727 > revision 5
1728 >
1728 >
1729 > diff --git a/"; exit 1; echo " b/"; exit 1; echo "
1729 > diff --git a/"; exit 1; echo " b/"; exit 1; echo "
1730 > new file mode 100644
1730 > new file mode 100644
1731 > --- /dev/null
1731 > --- /dev/null
1732 > +++ b/"; exit 1; echo "
1732 > +++ b/"; exit 1; echo "
1733 > @@ -0,0 +1,1 @@
1733 > @@ -0,0 +1,1 @@
1734 > +revision 5
1734 > +revision 5
1735 > EOF
1735 > EOF
1736
1736
1737 $ hg import --bypass -q - << EOF
1737 $ hg import --bypass -q - << EOF
1738 > # HG changeset patch
1738 > # HG changeset patch
1739 > revision 6
1739 > revision 6
1740 >
1740 >
1741 > diff --git a/"; exit 1; echo " b/"; exit 1; echo "
1741 > diff --git a/"; exit 1; echo " b/"; exit 1; echo "
1742 > new file mode 100644
1742 > new file mode 100644
1743 > --- /dev/null
1743 > --- /dev/null
1744 > +++ b/"; exit 1; echo "
1744 > +++ b/"; exit 1; echo "
1745 > @@ -0,0 +1,1 @@
1745 > @@ -0,0 +1,1 @@
1746 > +revision 6
1746 > +revision 6
1747 > EOF
1747 > EOF
1748
1748
1749 #endif
1749 #endif
1750
1750
1751 Merge post-processing
1751 Merge post-processing
1752
1752
1753 cat is a bad merge-tool and doesn't change:
1753 cat is a bad merge-tool and doesn't change:
1754
1754
1755 $ beforemerge
1755 $ beforemerge
1756 [merge-tools]
1756 [merge-tools]
1757 false.whatever=
1757 false.whatever=
1758 true.priority=1
1758 true.priority=1
1759 true.executable=cat
1759 true.executable=cat
1760 # hg update -C 1
1760 # hg update -C 1
1761 $ hg merge -y -r 2 --config merge-tools.true.checkchanged=1
1761 $ hg merge -y -r 2 --config merge-tools.true.checkchanged=1
1762 merging f
1762 merging f
1763 revision 1
1763 revision 1
1764 space
1764 space
1765 revision 0
1765 revision 0
1766 space
1766 space
1767 revision 2
1767 revision 2
1768 space
1768 space
1769 output file f appears unchanged
1769 output file f appears unchanged
1770 was merge successful (yn)? n
1770 was merge successful (yn)? n
1771 merging f failed!
1771 merging f failed!
1772 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1772 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1773 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
1773 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
1774 [1]
1774 [1]
1775 $ aftermerge
1775 $ aftermerge
1776 # cat f
1776 # cat f
1777 revision 1
1777 revision 1
1778 space
1778 space
1779 # hg stat
1779 # hg stat
1780 M f
1780 M f
1781 ? f.orig
1781 ? f.orig
1782 # hg resolve --list
1782 # hg resolve --list
1783 U f
1783 U f
1784
1784
1785 missingbinary is a merge-tool that doesn't exist:
1785 missingbinary is a merge-tool that doesn't exist:
1786
1786
1787 $ echo "missingbinary.executable=doesnotexist" >> .hg/hgrc
1787 $ echo "missingbinary.executable=doesnotexist" >> .hg/hgrc
1788 $ beforemerge
1788 $ beforemerge
1789 [merge-tools]
1789 [merge-tools]
1790 false.whatever=
1790 false.whatever=
1791 true.priority=1
1791 true.priority=1
1792 true.executable=cat
1792 true.executable=cat
1793 missingbinary.executable=doesnotexist
1793 missingbinary.executable=doesnotexist
1794 # hg update -C 1
1794 # hg update -C 1
1795 $ hg merge -y -r 2 --config ui.merge=missingbinary
1795 $ hg merge -y -r 2 --config ui.merge=missingbinary
1796 couldn't find merge tool missingbinary (for pattern f)
1796 couldn't find merge tool missingbinary (for pattern f)
1797 merging f
1797 merging f
1798 couldn't find merge tool missingbinary (for pattern f)
1798 couldn't find merge tool missingbinary (for pattern f)
1799 revision 1
1799 revision 1
1800 space
1800 space
1801 revision 0
1801 revision 0
1802 space
1802 space
1803 revision 2
1803 revision 2
1804 space
1804 space
1805 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1805 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1806 (branch merge, don't forget to commit)
1806 (branch merge, don't forget to commit)
1807
1807
1808 $ hg update -q -C 1
1808 $ hg update -q -C 1
1809 $ rm f
1809 $ rm f
1810
1810
1811 internal merge cannot handle symlinks and shouldn't try:
1811 internal merge cannot handle symlinks and shouldn't try:
1812
1812
1813 #if symlink
1813 #if symlink
1814
1814
1815 $ ln -s symlink f
1815 $ ln -s symlink f
1816 $ hg commit -qm 'f is symlink'
1816 $ hg commit -qm 'f is symlink'
1817
1817
1818 #else
1818 #else
1819
1819
1820 $ hg import --bypass -q - << EOF
1820 $ hg import --bypass -q - << EOF
1821 > # HG changeset patch
1821 > # HG changeset patch
1822 > f is symlink
1822 > f is symlink
1823 >
1823 >
1824 > diff --git a/f b/f
1824 > diff --git a/f b/f
1825 > old mode 100644
1825 > old mode 100644
1826 > new mode 120000
1826 > new mode 120000
1827 > --- a/f
1827 > --- a/f
1828 > +++ b/f
1828 > +++ b/f
1829 > @@ -1,2 +1,1 @@
1829 > @@ -1,2 +1,1 @@
1830 > -revision 1
1830 > -revision 1
1831 > -space
1831 > -space
1832 > +symlink
1832 > +symlink
1833 > \ No newline at end of file
1833 > \ No newline at end of file
1834 > EOF
1834 > EOF
1835
1835
1836 Resolve 'other [destination] changed f which local [working copy] deleted' prompt
1836 Resolve 'other [destination] changed f which local [working copy] deleted' prompt
1837 $ hg up -q -C --config ui.interactive=True << EOF
1837 $ hg up -q -C --config ui.interactive=True << EOF
1838 > c
1838 > c
1839 > EOF
1839 > EOF
1840
1840
1841 #endif
1841 #endif
1842
1842
1843 $ hg merge -r 2 --tool internal:merge
1843 $ hg merge -r 2 --tool internal:merge
1844 merging f
1844 merging f
1845 warning: internal :merge cannot merge symlinks for f
1845 warning: internal :merge cannot merge symlinks for f
1846 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
1846 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
1847 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1847 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1848 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
1848 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
1849 [1]
1849 [1]
1850
1850
1851 Verify naming of temporary files and that extension is preserved:
1851 Verify naming of temporary files and that extension is preserved:
1852
1852
1853 $ hg update -q -C 1
1853 $ hg update -q -C 1
1854 $ hg mv f f.txt
1854 $ hg mv f f.txt
1855 $ hg ci -qm "f.txt"
1855 $ hg ci -qm "f.txt"
1856 $ hg update -q -C 2
1856 $ hg update -q -C 2
1857 $ hg merge -y -r tip --tool echo --config merge-tools.echo.args='$base $local $other $output'
1857 $ hg merge -y -r tip --tool echo --config merge-tools.echo.args='$base $local $other $output'
1858 merging f and f.txt to f.txt
1858 merging f and f.txt to f.txt
1859 */f~base.* */f~local.*.txt */f~other.*.txt $TESTTMP/repo/f.txt (glob)
1859 */f~base.* */f~local.*.txt */f~other.*.txt $TESTTMP/repo/f.txt (glob)
1860 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1860 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1861 (branch merge, don't forget to commit)
1861 (branch merge, don't forget to commit)
1862
1862
1863 Verify naming of temporary files and that extension is preserved
1863 Verify naming of temporary files and that extension is preserved
1864 (experimental.mergetempdirprefix version):
1864 (experimental.mergetempdirprefix version):
1865
1865
1866 $ hg update -q -C 1
1866 $ hg update -q -C 1
1867 $ hg mv f f.txt
1867 $ hg mv f f.txt
1868 $ hg ci -qm "f.txt"
1868 $ hg ci -qm "f.txt"
1869 $ hg update -q -C 2
1869 $ hg update -q -C 2
1870 $ hg merge -y -r tip --tool echo \
1870 $ hg merge -y -r tip --tool echo \
1871 > --config merge-tools.echo.args='$base $local $other $output' \
1871 > --config merge-tools.echo.args='$base $local $other $output' \
1872 > --config experimental.mergetempdirprefix=$TESTTMP/hgmerge.
1872 > --config experimental.mergetempdirprefix=$TESTTMP/hgmerge.
1873 merging f and f.txt to f.txt
1873 merging f and f.txt to f.txt
1874 $TESTTMP/hgmerge.*/f~base $TESTTMP/hgmerge.*/f~local.txt $TESTTMP/hgmerge.*/f~other.txt $TESTTMP/repo/f.txt (glob)
1874 $TESTTMP/hgmerge.*/f~base $TESTTMP/hgmerge.*/f~local.txt $TESTTMP/hgmerge.*/f~other.txt $TESTTMP/repo/f.txt (glob)
1875 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1875 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1876 (branch merge, don't forget to commit)
1876 (branch merge, don't forget to commit)
1877
1877
1878 Binary files capability checking
1878 Binary files capability checking
1879
1879
1880 $ hg update -q -C 0
1880 $ hg update -q -C 0
1881 $ python <<EOF
1881 $ python <<EOF
1882 > with open('b', 'wb') as fp:
1882 > with open('b', 'wb') as fp:
1883 > fp.write(b'\x00\x01\x02\x03')
1883 > fp.write(b'\x00\x01\x02\x03')
1884 > EOF
1884 > EOF
1885 $ hg add b
1885 $ hg add b
1886 $ hg commit -qm "add binary file (#1)"
1886 $ hg commit -qm "add binary file (#1)"
1887
1887
1888 $ hg update -q -C 0
1888 $ hg update -q -C 0
1889 $ python <<EOF
1889 $ python <<EOF
1890 > with open('b', 'wb') as fp:
1890 > with open('b', 'wb') as fp:
1891 > fp.write(b'\x03\x02\x01\x00')
1891 > fp.write(b'\x03\x02\x01\x00')
1892 > EOF
1892 > EOF
1893 $ hg add b
1893 $ hg add b
1894 $ hg commit -qm "add binary file (#2)"
1894 $ hg commit -qm "add binary file (#2)"
1895
1895
1896 By default, binary files capability of internal merge tools is not
1896 By default, binary files capability of internal merge tools is not
1897 checked strictly.
1897 checked strictly.
1898
1898
1899 (for merge-patterns, chosen unintentionally)
1899 (for merge-patterns, chosen unintentionally)
1900
1900
1901 $ hg merge 9 \
1901 $ hg merge 9 \
1902 > --config merge-patterns.b=:merge-other \
1902 > --config merge-patterns.b=:merge-other \
1903 > --config merge-patterns.re:[a-z]=:other
1903 > --config merge-patterns.re:[a-z]=:other
1904 warning: check merge-patterns configurations, if ':merge-other' for binary file 'b' is unintentional
1904 warning: check merge-patterns configurations, if ':merge-other' for binary file 'b' is unintentional
1905 (see 'hg help merge-tools' for binary files capability)
1905 (see 'hg help merge-tools' for binary files capability)
1906 merging b
1906 merging b
1907 warning: b looks like a binary file.
1907 warning: b looks like a binary file.
1908 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1908 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1909 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
1909 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
1910 [1]
1910 [1]
1911 $ hg merge --abort -q
1911 $ hg merge --abort -q
1912
1912
1913 (for ui.merge, ignored unintentionally)
1913 (for ui.merge, ignored unintentionally)
1914
1914
1915 $ hg merge 9 \
1915 $ hg merge 9 \
1916 > --config merge-tools.:other.binary=true \
1916 > --config merge-tools.:other.binary=true \
1917 > --config ui.merge=:other
1917 > --config ui.merge=:other
1918 tool :other (for pattern b) can't handle binary
1918 tool :other (for pattern b) can't handle binary
1919 tool true can't handle binary
1919 tool true can't handle binary
1920 tool :other can't handle binary
1920 tool :other can't handle binary
1921 tool false can't handle binary
1921 tool false can't handle binary
1922 no tool found to merge b
1922 no tool found to merge b
1923 file 'b' needs to be resolved.
1923 file 'b' needs to be resolved.
1924 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
1924 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
1925 What do you want to do? u
1925 What do you want to do? u
1926 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1926 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1927 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
1927 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
1928 [1]
1928 [1]
1929 $ hg merge --abort -q
1929 $ hg merge --abort -q
1930
1930
1931 With merge.strict-capability-check=true, binary files capability of
1931 With merge.strict-capability-check=true, binary files capability of
1932 internal merge tools is checked strictly.
1932 internal merge tools is checked strictly.
1933
1933
1934 $ f --hexdump b
1934 $ f --hexdump b
1935 b:
1935 b:
1936 0000: 03 02 01 00 |....|
1936 0000: 03 02 01 00 |....|
1937
1937
1938 (for merge-patterns)
1938 (for merge-patterns)
1939
1939
1940 $ hg merge 9 --config merge.strict-capability-check=true \
1940 $ hg merge 9 --config merge.strict-capability-check=true \
1941 > --config merge-tools.:merge-other.binary=true \
1941 > --config merge-tools.:merge-other.binary=true \
1942 > --config merge-patterns.b=:merge-other \
1942 > --config merge-patterns.b=:merge-other \
1943 > --config merge-patterns.re:[a-z]=:other
1943 > --config merge-patterns.re:[a-z]=:other
1944 tool :merge-other (for pattern b) can't handle binary
1944 tool :merge-other (for pattern b) can't handle binary
1945 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1945 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1946 (branch merge, don't forget to commit)
1946 (branch merge, don't forget to commit)
1947 $ f --hexdump b
1947 $ f --hexdump b
1948 b:
1948 b:
1949 0000: 00 01 02 03 |....|
1949 0000: 00 01 02 03 |....|
1950 $ hg merge --abort -q
1950 $ hg merge --abort -q
1951
1951
1952 (for ui.merge)
1952 (for ui.merge)
1953
1953
1954 $ hg merge 9 --config merge.strict-capability-check=true \
1954 $ hg merge 9 --config merge.strict-capability-check=true \
1955 > --config ui.merge=:other
1955 > --config ui.merge=:other
1956 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1956 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1957 (branch merge, don't forget to commit)
1957 (branch merge, don't forget to commit)
1958 $ f --hexdump b
1958 $ f --hexdump b
1959 b:
1959 b:
1960 0000: 00 01 02 03 |....|
1960 0000: 00 01 02 03 |....|
1961 $ hg merge --abort -q
1961 $ hg merge --abort -q
1962
1962
1963 Check that the extra information is printed correctly
1963 Check that the extra information is printed correctly
1964
1964
1965 $ hg merge 9 \
1965 $ hg merge 9 \
1966 > --config merge-tools.testecho.executable='echo' \
1966 > --config merge-tools.testecho.executable='echo' \
1967 > --config merge-tools.testecho.args='merge runs here ...' \
1967 > --config merge-tools.testecho.args='merge runs here ...' \
1968 > --config merge-tools.testecho.binary=True \
1968 > --config merge-tools.testecho.binary=True \
1969 > --config ui.merge=testecho \
1969 > --config ui.merge=testecho \
1970 > --config ui.pre-merge-tool-output-template='\n{label("extmerge.running_merge_tool", "Running merge tool for {path} ({toolpath}):")}\n{separate("\n", extmerge_section(local), extmerge_section(base), extmerge_section(other))}\n' \
1970 > --config ui.pre-merge-tool-output-template='\n{label("extmerge.running_merge_tool", "Running merge tool for {path} ({toolpath}):")}\n{separate("\n", extmerge_section(local), extmerge_section(base), extmerge_section(other))}\n' \
1971 > --config 'templatealias.extmerge_section(sect)="- {pad("{sect.name} ({sect.label})", 20, left=True)}: {revset(sect.node)%"{rev}:{shortest(node,8)} {desc|firstline} {separate(" ", tags, bookmarks, branch)}"}"'
1971 > --config 'templatealias.extmerge_section(sect)="- {pad("{sect.name} ({sect.label})", 20, left=True)}: {revset(sect.node)%"{rev}:{shortest(node,8)} {desc|firstline} {separate(" ", tags, bookmarks, branch)}"}"'
1972 merging b
1972 merging b
1973
1973
1974 Running merge tool for b ("*/bin/echo.exe"): (glob) (windows !)
1974 Running merge tool for b ("*/bin/echo.exe"): (glob) (windows !)
1975 Running merge tool for b (*/bin/echo): (glob) (no-windows !)
1975 Running merge tool for b (*/bin/echo): (glob) (no-windows !)
1976 - local (working copy): 10:2d1f533d add binary file (#2) tip default
1976 - local (working copy): 10:2d1f533d add binary file (#2) tip default
1977 - base (base): -1:00000000 default
1977 - base (base): -1:00000000 default
1978 - other (merge rev): 9:1e7ad7d7 add binary file (#1) default
1978 - other (merge rev): 9:1e7ad7d7 add binary file (#1) default
1979 merge runs here ...
1979 merge runs here ...
1980 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1980 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1981 (branch merge, don't forget to commit)
1981 (branch merge, don't forget to commit)
1982
1982
1983 Check that debugpicktool examines which merge tool is chosen for
1983 Check that debugpicktool examines which merge tool is chosen for
1984 specified file as expected
1984 specified file as expected
1985
1985
1986 $ beforemerge
1986 $ beforemerge
1987 [merge-tools]
1987 [merge-tools]
1988 false.whatever=
1988 false.whatever=
1989 true.priority=1
1989 true.priority=1
1990 true.executable=cat
1990 true.executable=cat
1991 missingbinary.executable=doesnotexist
1991 missingbinary.executable=doesnotexist
1992 # hg update -C 1
1992 # hg update -C 1
1993
1993
1994 (default behavior: checking files in the working parent context)
1994 (default behavior: checking files in the working parent context)
1995
1995
1996 $ hg manifest
1996 $ hg manifest
1997 f
1997 f
1998 $ hg debugpickmergetool
1998 $ hg debugpickmergetool
1999 f = true
1999 f = true
2000
2000
2001 (-X/-I and file patterns limmit examination targets)
2001 (-X/-I and file patterns limmit examination targets)
2002
2002
2003 $ hg debugpickmergetool -X f
2003 $ hg debugpickmergetool -X f
2004 $ hg debugpickmergetool unknown
2004 $ hg debugpickmergetool unknown
2005 unknown: no such file in rev ef83787e2614
2005 unknown: no such file in rev ef83787e2614
2006
2006
2007 (--changedelete emulates merging change and delete)
2007 (--changedelete emulates merging change and delete)
2008
2008
2009 $ hg debugpickmergetool --changedelete
2009 $ hg debugpickmergetool --changedelete
2010 f = :prompt
2010 f = :prompt
2011
2011
2012 (-r REV causes checking files in specified revision)
2012 (-r REV causes checking files in specified revision)
2013
2013
2014 $ hg manifest -r 8
2014 $ hg manifest -r 8
2015 f.txt
2015 f.txt
2016 $ hg debugpickmergetool -r 8
2016 $ hg debugpickmergetool -r 8
2017 f.txt = true
2017 f.txt = true
2018
2018
2019 #if symlink
2019 #if symlink
2020
2020
2021 (symlink causes chosing :prompt)
2021 (symlink causes chosing :prompt)
2022
2022
2023 $ hg debugpickmergetool -r 6d00b3726f6e
2023 $ hg debugpickmergetool -r 6d00b3726f6e
2024 f = :prompt
2024 f = :prompt
2025
2025
2026 (by default, it is assumed that no internal merge tools has symlinks
2026 (by default, it is assumed that no internal merge tools has symlinks
2027 capability)
2027 capability)
2028
2028
2029 $ hg debugpickmergetool \
2029 $ hg debugpickmergetool \
2030 > -r 6d00b3726f6e \
2030 > -r 6d00b3726f6e \
2031 > --config merge-tools.:merge-other.symlink=true \
2031 > --config merge-tools.:merge-other.symlink=true \
2032 > --config merge-patterns.f=:merge-other \
2032 > --config merge-patterns.f=:merge-other \
2033 > --config merge-patterns.re:[f]=:merge-local \
2033 > --config merge-patterns.re:[f]=:merge-local \
2034 > --config merge-patterns.re:[a-z]=:other
2034 > --config merge-patterns.re:[a-z]=:other
2035 f = :prompt
2035 f = :prompt
2036
2036
2037 $ hg debugpickmergetool \
2037 $ hg debugpickmergetool \
2038 > -r 6d00b3726f6e \
2038 > -r 6d00b3726f6e \
2039 > --config merge-tools.:other.symlink=true \
2039 > --config merge-tools.:other.symlink=true \
2040 > --config ui.merge=:other
2040 > --config ui.merge=:other
2041 f = :prompt
2041 f = :prompt
2042
2042
2043 (with strict-capability-check=true, actual symlink capabilities are
2043 (with strict-capability-check=true, actual symlink capabilities are
2044 checked striclty)
2044 checked striclty)
2045
2045
2046 $ hg debugpickmergetool --config merge.strict-capability-check=true \
2046 $ hg debugpickmergetool --config merge.strict-capability-check=true \
2047 > -r 6d00b3726f6e \
2047 > -r 6d00b3726f6e \
2048 > --config merge-tools.:merge-other.symlink=true \
2048 > --config merge-tools.:merge-other.symlink=true \
2049 > --config merge-patterns.f=:merge-other \
2049 > --config merge-patterns.f=:merge-other \
2050 > --config merge-patterns.re:[f]=:merge-local \
2050 > --config merge-patterns.re:[f]=:merge-local \
2051 > --config merge-patterns.re:[a-z]=:other
2051 > --config merge-patterns.re:[a-z]=:other
2052 f = :other
2052 f = :other
2053
2053
2054 $ hg debugpickmergetool --config merge.strict-capability-check=true \
2054 $ hg debugpickmergetool --config merge.strict-capability-check=true \
2055 > -r 6d00b3726f6e \
2055 > -r 6d00b3726f6e \
2056 > --config ui.merge=:other
2056 > --config ui.merge=:other
2057 f = :other
2057 f = :other
2058
2058
2059 $ hg debugpickmergetool --config merge.strict-capability-check=true \
2059 $ hg debugpickmergetool --config merge.strict-capability-check=true \
2060 > -r 6d00b3726f6e \
2060 > -r 6d00b3726f6e \
2061 > --config merge-tools.:merge-other.symlink=true \
2061 > --config merge-tools.:merge-other.symlink=true \
2062 > --config ui.merge=:merge-other
2062 > --config ui.merge=:merge-other
2063 f = :prompt
2063 f = :prompt
2064
2064
2065 #endif
2065 #endif
2066
2066
2067 (--verbose shows some configurations)
2067 (--verbose shows some configurations)
2068
2068
2069 $ hg debugpickmergetool --tool foobar -v
2069 $ hg debugpickmergetool --tool foobar -v
2070 with --tool 'foobar'
2070 with --tool 'foobar'
2071 f = foobar
2071 f = foobar
2072
2072
2073 $ HGMERGE=false hg debugpickmergetool -v
2073 $ HGMERGE=false hg debugpickmergetool -v
2074 with HGMERGE='false'
2074 with HGMERGE='false'
2075 f = false
2075 f = false
2076
2076
2077 $ hg debugpickmergetool --config ui.merge=false -v
2077 $ hg debugpickmergetool --config ui.merge=false -v
2078 with ui.merge='false'
2078 with ui.merge='false'
2079 f = false
2079 f = false
2080
2080
2081 (--debug shows errors detected intermediately)
2081 (--debug shows errors detected intermediately)
2082
2082
2083 $ hg debugpickmergetool --config merge-patterns.f=true --config merge-tools.true.executable=nonexistentmergetool --debug f
2083 $ hg debugpickmergetool --config merge-patterns.f=true --config merge-tools.true.executable=nonexistentmergetool --debug f
2084 couldn't find merge tool true (for pattern f)
2084 couldn't find merge tool true (for pattern f)
2085 couldn't find merge tool true
2085 couldn't find merge tool true
2086 f = false
2086 f = false
2087
2087
2088 $ cd ..
2088 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now