##// END OF EJS Templates
merge with i18n
Alexander Sauta -
r22065:45a01832 merge stable
parent child Browse files
Show More

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

@@ -1,93 +1,94 b''
1 1 35fb62a3a673d5322f6274a44ba6456e5e4b3b37 0 iD8DBQBEYmO2ywK+sNU5EO8RAnaYAKCO7x15xUn5mnhqWNXqk/ehlhRt2QCfRDfY0LrUq2q4oK/KypuJYPHgq1A=
2 2 2be3001847cb18a23c403439d9e7d0ace30804e9 0 iD8DBQBExUbjywK+sNU5EO8RAhzxAKCtyHAQUzcTSZTqlfJ0by6vhREwWQCghaQFHfkfN0l9/40EowNhuMOKnJk=
3 3 36a957364b1b89c150f2d0e60a99befe0ee08bd3 0 iD8DBQBFfL2QywK+sNU5EO8RAjYFAKCoGlaWRTeMsjdmxAjUYx6diZxOBwCfY6IpBYsKvPTwB3oktnPt5Rmrlys=
4 4 27230c29bfec36d5540fbe1c976810aefecfd1d2 0 iD8DBQBFheweywK+sNU5EO8RAt7VAKCrqJQWT2/uo2RWf0ZI4bLp6v82jACgjrMdsaTbxRsypcmEsdPhlG6/8F4=
5 5 fb4b6d5fe100b0886f8bc3d6731ec0e5ed5c4694 0 iD8DBQBGgHicywK+sNU5EO8RAgNxAJ0VG8ixAaeudx4sZbhngI1syu49HQCeNUJQfWBgA8bkJ2pvsFpNxwYaX3I=
6 6 23889160905a1b09fffe1c07378e9fc1827606eb 0 iD8DBQBHGTzoywK+sNU5EO8RAr/UAJ0Y8s4jQtzgS+G9vM8z6CWBThZ8fwCcCT5XDj2XwxKkz/0s6UELwjsO3LU=
7 7 bae2e9c838e90a393bae3973a7850280413e091a 0 iD8DBQBH6DO5ywK+sNU5EO8RAsfrAJ0e4r9c9GF/MJsM7Xjd3NesLRC3+ACffj6+6HXdZf8cswAoFPO+DY00oD0=
8 8 d5cbbe2c49cee22a9fbeb9ea41daa0ac4e26b846 0 iD8DBQBINdwsywK+sNU5EO8RAjIUAKCPmlFJSpsPAAUKF+iNHAwVnwmzeQCdEXrL27CWclXuUKdbQC8De7LICtE=
9 9 d2375bbee6d47e62ba8e415c86e83a465dc4dce9 0 iD8DBQBIo1wpywK+sNU5EO8RAmRNAJ94x3OFt6blbqu/yBoypm/AJ44fuACfUaldXcV5z9tht97hSp22DVTEPGc=
10 10 2a67430f92f15ea5159c26b09ec4839a0c549a26 0 iEYEABECAAYFAkk1hykACgkQywK+sNU5EO85QACeNJNUanjc2tl4wUoPHNuv+lSj0ZMAoIm93wSTc/feyYnO2YCaQ1iyd9Nu
11 11 3773e510d433969e277b1863c317b674cbee2065 0 iEYEABECAAYFAklNbbAACgkQywK+sNU5EO8o+gCfeb2/lfIJZMvyDA1m+G1CsBAxfFsAoIa6iAMG8SBY7hW1Q85Yf/LXEvaE
12 12 11a4eb81fb4f4742451591489e2797dc47903277 0 iEYEABECAAYFAklcAnsACgkQywK+sNU5EO+uXwCbBVHNNsLy1g7BlAyQJwadYVyHOXoAoKvtAVO71+bv7EbVoukwTzT+P4Sx
13 13 11efa41037e280d08cfb07c09ad485df30fb0ea8 0 iEYEABECAAYFAkmvJRQACgkQywK+sNU5EO9XZwCeLMgDgPSMWMm6vgjL4lDs2pEc5+0AnRxfiFbpbBfuEFTqKz9nbzeyoBlx
14 14 02981000012e3adf40c4849bd7b3d5618f9ce82d 0 iEYEABECAAYFAknEH3wACgkQywK+sNU5EO+uXwCeI+LbLMmhjU1lKSfU3UWJHjjUC7oAoIZLvYDGOL/tNZFUuatc3RnZ2eje
15 15 196d40e7c885fa6e95f89134809b3ec7bdbca34b 0 iEYEABECAAYFAkpL2X4ACgkQywK+sNU5EO9FOwCfXJycjyKJXsvQqKkHrglwOQhEKS4An36GfKzptfN8b1qNc3+ya/5c2WOM
16 16 3ef6c14a1e8e83a31226f5881b7fe6095bbfa6f6 0 iEYEABECAAYFAkpopLIACgkQywK+sNU5EO8QSgCfZ0ztsd071rOa2lhmp9Fyue/WoI0AoLTei80/xrhRlB8L/rZEf2KBl8dA
17 17 31ec469f9b556f11819937cf68ee53f2be927ebf 0 iEYEABECAAYFAksBuxAACgkQywK+sNU5EO+mBwCfagB+A0txzWZ6dRpug3LEoK7Z1QsAoKpbk8vsLjv6/oRDicSk/qBu33+m
18 18 439d7ea6fe3aa4ab9ec274a68846779153789de9 0 iEYEABECAAYFAksVw0kACgkQywK+sNU5EO/oZwCfdfBEkgp38xq6wN2F4nj+SzofrJIAnjmxt04vaJSeOOeHylHvk6lzuQsw
19 19 296a0b14a68621f6990c54fdba0083f6f20935bf 0 iEYEABECAAYFAks+jCoACgkQywK+sNU5EO9J8wCeMUGF9E/gS2UBsqIz56WS4HMPRPUAoI5J95mwEIK8Clrl7qFRidNI6APq
20 20 4aa619c4c2c09907034d9824ebb1dd0e878206eb 0 iEYEABECAAYFAktm9IsACgkQywK+sNU5EO9XGgCgk4HclRQhexEtooPE5GcUCdB6M8EAn2ptOhMVbIoO+JncA+tNACPFXh0O
21 21 ff2704a8ded37fbebd8b6eb5ec733731d725da8a 0 iEYEABECAAYFAkuRoSQACgkQywK+sNU5EO//3QCeJDc5r2uFyFCtAlpSA27DEE5rrxAAn2FSwTy9fhrB3QAdDQlwkEZcQzDh
22 22 2b01dab594167bc0dd33331dbaa6dca3dca1b3aa 0 iEYEABECAAYFAku1IwIACgkQywK+sNU5EO9MjgCdHLVwkTZlNHxhcznZKBL1rjN+J7cAoLLWi9LTL6f/TgBaPSKOy1ublbaW
23 23 39f725929f0c48c5fb3b90c071fc3066012456ca 0 iEYEABECAAYFAkvclvsACgkQywK+sNU5EO9FSwCeL9i5x8ALW/LE5+lCX6MFEAe4MhwAn1ev5o6SX6GrNdDfKweiemfO2VBk
24 24 fdcf80f26604f233dc4d8f0a5ef9d7470e317e8a 0 iEYEABECAAYFAkvsKTkACgkQywK+sNU5EO9qEACgiSiRGvTG2vXGJ65tUSOIYihTuFAAnRzRIqEVSw8M8/RGeUXRps0IzaCO
25 25 24fe2629c6fd0c74c90bd066e77387c2b02e8437 0 iEYEABECAAYFAkwFLRsACgkQywK+sNU5EO+pJACgp13tPI+pbwKZV+LeMjcQ4H6tCZYAoJebzhd6a8yYx6qiwpJxA9BXZNXy
26 26 f786fc4b8764cd2a5526d259cf2f94d8a66924d9 0 iEYEABECAAYFAkwsyxcACgkQywK+sNU5EO+crACfUpNAF57PmClkSri9nJcBjb2goN4AniPCNaKvnki7TnUsi1u2oxltpKKL
27 27 bf1774d95bde614af3956d92b20e2a0c68c5fec7 0 iEYEABECAAYFAkxVwccACgkQywK+sNU5EO+oFQCeJzwZ+we1fIIyBGCddHceOUAN++cAnjvT6A8ZWW0zV21NXIFF1qQmjxJd
28 28 c00f03a4982e467fb6b6bd45908767db6df4771d 0 iEYEABECAAYFAkxXDqsACgkQywK+sNU5EO/GJACfT9Rz4hZOxPQEs91JwtmfjevO84gAmwSmtfo5mmWSm8gtTUebCcdTv0Kf
29 29 ff5cec76b1c5b6be9c3bb923aae8c3c6d079d6b9 0 iD8DBQBMdo+qywK+sNU5EO8RAqQpAJ975BL2CCAiWMz9SXthNQ9xG181IwCgp4O+KViHPkufZVFn2aTKMNvcr1A=
30 30 93d8bff78c96fe7e33237b257558ee97290048a4 0 iD8DBQBMpfvdywK+sNU5EO8RAsxVAJ0UaL1XB51C76JUBhafc9GBefuMxwCdEWkTOzwvE0SarJBe9i008jhbqW4=
31 31 333421b9e0f96c7bc788e5667c146a58a9440a55 0 iD8DBQBMz0HOywK+sNU5EO8RAlsEAJ0USh6yOG7OrWkADGunVt9QimBQnwCbBqeMnKgSbwEw8jZwE3Iz1mdrYlo=
32 32 4438875ec01bd0fc32be92b0872eb6daeed4d44f 0 iD8DBQBM4WYUywK+sNU5EO8RAhCVAJ0dJswachwFAHALmk1x0RJehxzqPQCbBNskP9n/X689jB+btNTZTyKU/fw=
33 33 6aff4f144ad356311318b0011df0bb21f2c97429 0 iD8DBQBM9uxXywK+sNU5EO8RAv+4AKCDj4qKP16GdPaq1tP6BUwpM/M1OACfRyzLPp/qiiN8xJTWoWYSe/XjJug=
34 34 e3bf16703e2601de99e563cdb3a5d50b64e6d320 0 iD8DBQBNH8WqywK+sNU5EO8RAiQTAJ9sBO+TeiGro4si77VVaQaA6jcRUgCfSA28dBbjj0oFoQwvPoZjANiZBH8=
35 35 a6c855c32ea081da3c3b8ff628f1847ff271482f 0 iD8DBQBNSJJ+ywK+sNU5EO8RAoJaAKCweDEF70fu+r1Zn7pYDXdlk5RuSgCeO9gK/eit8Lin/1n3pO7aYguFLok=
36 36 2b2155623ee2559caf288fd333f30475966c4525 0 iD8DBQBNSJeBywK+sNU5EO8RAm1KAJ4hW9Cm9nHaaGJguchBaPLlAr+O3wCgqgmMok8bdAS06N6PL60PSTM//Gg=
37 37 2616325766e3504c8ae7c84bd15ee610901fe91d 0 iD8DBQBNbWy9ywK+sNU5EO8RAlWCAJ4mW8HbzjJj9GpK98muX7k+7EvEHwCfaTLbC/DH3QEsZBhEP+M8tzL6RU4=
38 38 aa1f3be38ab127280761889d2dca906ca465b5f4 0 iD8DBQBNeQq7ywK+sNU5EO8RAlEOAJ4tlEDdetE9lKfjGgjbkcR8PrC3egCfXCfF3qNVvU/2YYjpgvRwevjvDy0=
39 39 b032bec2c0a651ca0ddecb65714bfe6770f67d70 0 iD8DBQBNlg5kywK+sNU5EO8RAnGEAJ9gmEx6MfaR4XcG2m/93vwtfyzs3gCgltzx8/YdHPwqDwRX/WbpYgi33is=
40 40 3cb1e95676ad089596bd81d0937cad37d6e3b7fb 0 iD8DBQBNvTy4ywK+sNU5EO8RAmp8AJ9QnxK4jTJ7G722MyeBxf0UXEdGwACgtlM7BKtNQfbEH/fOW5y+45W88VI=
41 41 733af5d9f6b22387913e1d11350fb8cb7c1487dd 0 iD8DBQBN5q/8ywK+sNU5EO8RArRGAKCNGT94GKIYtSuwZ57z1sQbcw6uLACfffpbMV4NAPMl8womAwg+7ZPKnIU=
42 42 de9eb6b1da4fc522b1cab16d86ca166204c24f25 0 iD8DBQBODhfhywK+sNU5EO8RAr2+AJ4ugbAj8ae8/K0bYZzx3sascIAg1QCeK3b+zbbVVqd3b7CDpwFnaX8kTd4=
43 43 4a43e23b8c55b4566b8200bf69fe2158485a2634 0 iD8DBQBONzIMywK+sNU5EO8RAj5SAJ0aPS3+JHnyI6bHB2Fl0LImbDmagwCdGbDLp1S7TFobxXudOH49bX45Iik=
44 44 d629f1e89021103f1753addcef6b310e4435b184 0 iD8DBQBOWAsBywK+sNU5EO8RAht4AJwJl9oNFopuGkj5m8aKuf7bqPkoAQCeNrEm7UhFsZKYT5iUOjnMV7s2LaM=
45 45 351a9292e430e35766c552066ed3e87c557b803b 0 iD8DBQBOh3zUywK+sNU5EO8RApFMAKCD3Y/u3avDFndznwqfG5UeTHMlvACfUivPIVQZyDZnhZMq0UhC6zhCEQg=
46 46 384082750f2c51dc917d85a7145748330fa6ef4d 0 iD8DBQBOmd+OywK+sNU5EO8RAgDgAJ9V/X+G7VLwhTpHrZNiOHabzSyzYQCdE2kKfIevJUYB9QLAWCWP6DPwrwI=
47 47 41453d55b481ddfcc1dacb445179649e24ca861d 0 iD8DBQBOsFhpywK+sNU5EO8RAqM6AKCyfxUae3/zLuiLdQz+JR78690eMACfQ6JTBQib4AbE+rUDdkeFYg9K/+4=
48 48 195dbd1cef0c2f9f8bcf4ea303238105f716bda3 0 iD8DBQBO1/fWywK+sNU5EO8RAmoPAKCR5lpv1D6JLURHD8KVLSV4GRVEBgCgnd0Sy78ligNfqAMafmACRDvj7vo=
49 49 6344043924497cd06d781d9014c66802285072e4 0 iD8DBQBPALgmywK+sNU5EO8RAlfhAJ9nYOdWnhfVDHYtDTJAyJtXBAQS9wCgnefoSQt7QABkbGxM+Q85UYEBuD0=
50 50 db33555eafeaf9df1e18950e29439eaa706d399b 0 iD8DBQBPGdzxywK+sNU5EO8RAppkAJ9jOXhUVE/97CPgiMA0pMGiIYnesQCfengAszcBiSiKGugiI8Okc9ghU+Y=
51 51 2aa5b51f310fb3befd26bed99c02267f5c12c734 0 iD8DBQBPKZ9bywK+sNU5EO8RAt1TAJ45r1eJ0YqSkInzrrayg4TVCh0SnQCgm0GA/Ua74jnnDwVQ60lAwROuz1Q=
52 52 53e2cd303ecf8ca7c7eeebd785c34e5ed6b0f4a4 0 iD8DBQBPT/fvywK+sNU5EO8RAnfYAKCn7d0vwqIb100YfWm1F7nFD5B+FACeM02YHpQLSNsztrBCObtqcnfod7Q=
53 53 b9bd95e61b49c221c4cca24e6da7c946fc02f992 0 iD8DBQBPeLsIywK+sNU5EO8RAvpNAKCtKe2gitz8dYn52IRF0hFOPCR7AQCfRJL/RWCFweu2T1vH/mUOCf8SXXc=
54 54 d9e2f09d5488c395ae9ddbb320ceacd24757e055 0 iD8DBQBPju/dywK+sNU5EO8RArBYAJ9xtifdbk+hCOJO8OZa4JfHX8OYZQCeKPMBaBWiT8N/WHoOm1XU0q+iono=
55 55 00182b3d087909e3c3ae44761efecdde8f319ef3 0 iD8DBQBPoFhIywK+sNU5EO8RAhzhAKCBj1n2jxPTkZNJJ5pSp3soa+XHIgCgsZZpAQxOpXwCp0eCdNGe0+pmxmg=
56 56 5983de86462c5a9f42a3ad0f5e90ce5b1d221d25 0 iD8DBQBPovNWywK+sNU5EO8RAhgiAJ980T91FdPTRMmVONDhpkMsZwVIMACgg3bKvoWSeuCW28llUhAJtUjrMv0=
57 57 85a358df5bbbe404ca25730c9c459b34263441dc 0 iD8DBQBPyZsWywK+sNU5EO8RAnpLAJ48qrGDJRT+pteS0mSQ11haqHstPwCdG4ccGbk+0JHb7aNy8/NRGAOqn9w=
58 58 b013baa3898e117959984fc64c29d8c784d2f28b 0 iD8DBQBP8QOPywK+sNU5EO8RAqimAKCFRSx0lvG6y8vne2IhNG062Hn0dACeMLI5/zhpWpHBIVeAAquYfx2XFeA=
59 59 7f5094bb3f423fc799e471aac2aee81a7ce57a0b 0 iD8DBQBQGiL8ywK+sNU5EO8RAq5oAJ4rMMCPx6O+OuzNXVOexogedWz/QgCeIiIxLd76I4pXO48tdXhr0hQcBuM=
60 60 072209ae4ddb654eb2d5fd35bff358c738414432 0 iD8DBQBQQkq0ywK+sNU5EO8RArDTAJ9nk5CySnNAjAXYvqvx4uWCw9ThZwCgqmFRehH/l+oTwj3f8nw8u8qTCdc=
61 61 b3f0f9a39c4e1d0250048cd803ab03542d6f140a 0 iD8DBQBQamltywK+sNU5EO8RAlsqAJ4qF/m6aFu4mJCOKTiAP5RvZFK02ACfawYShUZO6OXEFfveU0aAxDR0M1k=
62 62 d118a4f4fd16d9b558ec3f3e87bfee772861d2b7 0 iD8DBQBQgPV5ywK+sNU5EO8RArylAJ0abcx5NlDjyv3ZDWpAfRIHyRsJtQCgn4TMuEayqgxzrvadQZHdTEU2g38=
63 63 195ad823b5d58c68903a6153a25e3fb4ed25239d 0 iD8DBQBQkuT9ywK+sNU5EO8RAhB4AKCeerItoK2Jipm2cVf4euGofAa/WACeJj3TVd4pFILpb+ogj7ebweFLJi0=
64 64 0c10cf8191469e7c3c8844922e17e71a176cb7cb 0 iD8DBQBQvQWoywK+sNU5EO8RAnq3AJoCn98u4geFx5YaQaeh99gFhCd7bQCgjoBwBSUyOvGd0yBy60E3Vv3VZhM=
65 65 a4765077b65e6ae29ba42bab7834717b5072d5ba 0 iD8DBQBQ486sywK+sNU5EO8RAhmJAJ90aLfLKZhmcZN7kqphigQJxiFOQACeJ5IUZxjGKH4xzi3MrgIcx9n+dB0=
66 66 f5fbe15ca7449f2c9a3cf817c86d0ae68b307214 0 iD8DBQBQ+yuYywK+sNU5EO8RAm9JAJoD/UciWvpGeKBcpGtZJBFJVcL/HACghDXSgQ+xQDjB+6uGrdgAQsRR1Lg=
67 67 a6088c05e43a8aee0472ca3a4f6f8d7dd914ebbf 0 iD8DBQBRDDROywK+sNU5EO8RAh75AJ9uJCGoCWnP0Lv/+XuYs4hvUl+sAgCcD36QgAnuw8IQXrvv684BAXAnHcA=
68 68 7511d4df752e61fe7ae4f3682e0a0008573b0402 0 iD8DBQBRFYaoywK+sNU5EO8RAuErAJoDyhXn+lptU3+AevVdwAIeNFyR2gCdHzPHyWd+JDeWCUR+pSOBi8O2ppM=
69 69 5b7175377babacce80a6c1e12366d8032a6d4340 0 iD8DBQBRMCYgywK+sNU5EO8RAq1/AKCWKlt9ysibyQgYwoxxIOZv5J8rpwCcDSHQaaf1fFZUTnQsOePwcM2Y/Sg=
70 70 50c922c1b5145dab8baefefb0437d363b6a6c21c 0 iD8DBQBRWnUnywK+sNU5EO8RAuQRAJwM42cJqJPeqJ0jVNdMqKMDqr4dSACeP0cRVGz1gitMuV0x8f3mrZrqc7I=
71 71 8a7bd2dccd44ed571afe7424cd7f95594f27c092 0 iD8DBQBRXfBvywK+sNU5EO8RAn+LAKCsMmflbuXjYRxlzFwId5ptm8TZcwCdGkyLbZcASBOkzQUm/WW1qfknJHU=
72 72 292cd385856d98bacb2c3086f8897bc660c2beea 0 iD8DBQBRcM0BywK+sNU5EO8RAjp4AKCJBykQbvXhKuvLSMxKx3a2TBiXcACfbr/kLg5GlZTF/XDPmY+PyHgI/GM=
73 73 23f785b38af38d2fca6b8f3db56b8007a84cd73a 0 iD8DBQBRgZwNywK+sNU5EO8RAmO4AJ4u2ILGuimRP6MJgE2t65LZ5dAdkACgiENEstIdrlFC80p+sWKD81kKIYI=
74 74 ddc7a6be20212d18f3e27d9d7e6f079a66d96f21 0 iD8DBQBRkswvywK+sNU5EO8RAiYYAJsHTHyHbJeAgmGvBTmDrfcKu4doUgCeLm7eGBjx7yAPUvEtxef8rAkQmXI=
75 75 cceaf7af4c9e9e6fa2dbfdcfe9856c5da69c4ffd 0 iD8DBQBRqnFLywK+sNU5EO8RAsWNAJ9RR6t+y1DLFc2HeH0eN9VfZAKF9gCeJ8ezvhtKq/LMs0/nvcgKQc/d5jk=
76 76 009794acc6e37a650f0fae37872e733382ac1c0c 0 iD8DBQBR0guxywK+sNU5EO8RArNkAKCq9pMihVzP8Os5kCmgbWpe5C37wgCgqzuPZTHvAsXF5wTyaSTMVa9Ccq4=
77 77 f0d7721d7322dcfb5af33599c2543f27335334bb 0 iD8DBQBR8taaywK+sNU5EO8RAqeEAJ4idDhhDuEsgsUjeQgWNj498matHACfT67gSF5w0ylsrBx1Hb52HkGXDm0=
78 78 f37b5a17e6a0ee17afde2cdde5393dd74715fb58 0 iD8DBQBR+ymFywK+sNU5EO8RAuSdAJkBMcd9DAZ3rWE9WGKPm2YZ8LBoXACfXn/wbEsVy7ZgJoUwiWmHSnQaWCI=
79 79 335a558f81dc73afeab4d7be63617392b130117f 0 iQIVAwUAUiZrIyBXgaxoKi1yAQK2iw//cquNqqSkc8Re5/TZT9I6NH+lh6DbOKjJP0Xl1Wqq0K+KSIUgZG4G32ovaEb2l5X0uY+3unRPiZ0ebl0YSw4Fb2ZiPIADXLBTOYRrY2Wwd3tpJeGI6wEgZt3SfcITV/g7NJrCjT3FlYoSOIayrExM80InSdcEM0Q3Rx6HKzY2acyxzgZeAtAW5ohFvHilSvY6p5Gcm4+QptMxvw45GPdreUmjeXZxNXNXZ8P+MjMz/QJbai/N7PjmK8lqnhkBsT48Ng/KhhmOkGntNJ2/ImBWLFGcWngSvJ7sfWwnyhndvGhe0Hq1NcCf7I8TjNDxU5TR+m+uW7xjXdLoDbUjBdX4sKXnh8ZjbYiODKBOrrDq25cf8nA/tnpKyE/qsVy60kOk6loY4XKiYmn1V49Ta0emmDx0hqo3HgxHHsHX0NDnGdWGol7cPRET0RzVobKq1A0jnrhPooWidvLh9bPzLonrWDo+ib+DuySoRkuYUK4pgZJ2mbg6daFOBEZygkSyRB8bo1UQUP7EgQDrWe4khb/5GHEfDkrQz3qu/sXvc0Ir1mOUWBFPHC2DjjCn/oMJuUkG1SwM8l2Bfv7h67ssES6YQ2+RjOix4yid7EXS/Ogl45PzCIPSI5+BbNs10JhE0w5uErBHlF53EDTe/TSLc+GU6DB6PP6dH912Njdr3jpNSUQ=
80 80 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
@@ -1,106 +1,107 b''
1 1 d40cc5aacc31ed673d9b5b24f98bee78c283062c 0.4f
2 2 1c590d34bf61e2ea12c71738e5a746cd74586157 0.4e
3 3 7eca4cfa8aad5fce9a04f7d8acadcd0452e2f34e 0.4d
4 4 b4d0c3786ad3e47beacf8412157326a32b6d25a4 0.4c
5 5 f40273b0ad7b3a6d3012fd37736d0611f41ecf54 0.5
6 6 0a28dfe59f8fab54a5118c5be4f40da34a53cdb7 0.5b
7 7 12e0fdbc57a0be78f0e817fd1d170a3615cd35da 0.6
8 8 4ccf3de52989b14c3d84e1097f59e39a992e00bd 0.6b
9 9 eac9c8efcd9bd8244e72fb6821f769f450457a32 0.6c
10 10 979c049974485125e1f9357f6bbe9c1b548a64c3 0.7
11 11 3a56574f329a368d645853e0f9e09472aee62349 0.8
12 12 6a03cff2b0f5d30281e6addefe96b993582f2eac 0.8.1
13 13 35fb62a3a673d5322f6274a44ba6456e5e4b3b37 0.9
14 14 2be3001847cb18a23c403439d9e7d0ace30804e9 0.9.1
15 15 36a957364b1b89c150f2d0e60a99befe0ee08bd3 0.9.2
16 16 27230c29bfec36d5540fbe1c976810aefecfd1d2 0.9.3
17 17 fb4b6d5fe100b0886f8bc3d6731ec0e5ed5c4694 0.9.4
18 18 23889160905a1b09fffe1c07378e9fc1827606eb 0.9.5
19 19 bae2e9c838e90a393bae3973a7850280413e091a 1.0
20 20 d5cbbe2c49cee22a9fbeb9ea41daa0ac4e26b846 1.0.1
21 21 d2375bbee6d47e62ba8e415c86e83a465dc4dce9 1.0.2
22 22 2a67430f92f15ea5159c26b09ec4839a0c549a26 1.1
23 23 3773e510d433969e277b1863c317b674cbee2065 1.1.1
24 24 11a4eb81fb4f4742451591489e2797dc47903277 1.1.2
25 25 11efa41037e280d08cfb07c09ad485df30fb0ea8 1.2
26 26 02981000012e3adf40c4849bd7b3d5618f9ce82d 1.2.1
27 27 196d40e7c885fa6e95f89134809b3ec7bdbca34b 1.3
28 28 3ef6c14a1e8e83a31226f5881b7fe6095bbfa6f6 1.3.1
29 29 31ec469f9b556f11819937cf68ee53f2be927ebf 1.4
30 30 439d7ea6fe3aa4ab9ec274a68846779153789de9 1.4.1
31 31 296a0b14a68621f6990c54fdba0083f6f20935bf 1.4.2
32 32 4aa619c4c2c09907034d9824ebb1dd0e878206eb 1.4.3
33 33 ff2704a8ded37fbebd8b6eb5ec733731d725da8a 1.5
34 34 2b01dab594167bc0dd33331dbaa6dca3dca1b3aa 1.5.1
35 35 39f725929f0c48c5fb3b90c071fc3066012456ca 1.5.2
36 36 fdcf80f26604f233dc4d8f0a5ef9d7470e317e8a 1.5.3
37 37 24fe2629c6fd0c74c90bd066e77387c2b02e8437 1.5.4
38 38 f786fc4b8764cd2a5526d259cf2f94d8a66924d9 1.6
39 39 bf1774d95bde614af3956d92b20e2a0c68c5fec7 1.6.1
40 40 c00f03a4982e467fb6b6bd45908767db6df4771d 1.6.2
41 41 ff5cec76b1c5b6be9c3bb923aae8c3c6d079d6b9 1.6.3
42 42 93d8bff78c96fe7e33237b257558ee97290048a4 1.6.4
43 43 333421b9e0f96c7bc788e5667c146a58a9440a55 1.7
44 44 4438875ec01bd0fc32be92b0872eb6daeed4d44f 1.7.1
45 45 6aff4f144ad356311318b0011df0bb21f2c97429 1.7.2
46 46 e3bf16703e2601de99e563cdb3a5d50b64e6d320 1.7.3
47 47 a6c855c32ea081da3c3b8ff628f1847ff271482f 1.7.4
48 48 2b2155623ee2559caf288fd333f30475966c4525 1.7.5
49 49 2616325766e3504c8ae7c84bd15ee610901fe91d 1.8
50 50 aa1f3be38ab127280761889d2dca906ca465b5f4 1.8.1
51 51 b032bec2c0a651ca0ddecb65714bfe6770f67d70 1.8.2
52 52 3cb1e95676ad089596bd81d0937cad37d6e3b7fb 1.8.3
53 53 733af5d9f6b22387913e1d11350fb8cb7c1487dd 1.8.4
54 54 de9eb6b1da4fc522b1cab16d86ca166204c24f25 1.9
55 55 4a43e23b8c55b4566b8200bf69fe2158485a2634 1.9.1
56 56 d629f1e89021103f1753addcef6b310e4435b184 1.9.2
57 57 351a9292e430e35766c552066ed3e87c557b803b 1.9.3
58 58 384082750f2c51dc917d85a7145748330fa6ef4d 2.0-rc
59 59 41453d55b481ddfcc1dacb445179649e24ca861d 2.0
60 60 195dbd1cef0c2f9f8bcf4ea303238105f716bda3 2.0.1
61 61 6344043924497cd06d781d9014c66802285072e4 2.0.2
62 62 db33555eafeaf9df1e18950e29439eaa706d399b 2.1-rc
63 63 2aa5b51f310fb3befd26bed99c02267f5c12c734 2.1
64 64 53e2cd303ecf8ca7c7eeebd785c34e5ed6b0f4a4 2.1.1
65 65 b9bd95e61b49c221c4cca24e6da7c946fc02f992 2.1.2
66 66 d9e2f09d5488c395ae9ddbb320ceacd24757e055 2.2-rc
67 67 00182b3d087909e3c3ae44761efecdde8f319ef3 2.2
68 68 5983de86462c5a9f42a3ad0f5e90ce5b1d221d25 2.2.1
69 69 85a358df5bbbe404ca25730c9c459b34263441dc 2.2.2
70 70 b013baa3898e117959984fc64c29d8c784d2f28b 2.2.3
71 71 a06e2681dd1786e2354d84a5fa9c1c88dd4fa3e0 2.3-rc
72 72 7f5094bb3f423fc799e471aac2aee81a7ce57a0b 2.3
73 73 072209ae4ddb654eb2d5fd35bff358c738414432 2.3.1
74 74 b3f0f9a39c4e1d0250048cd803ab03542d6f140a 2.3.2
75 75 d118a4f4fd16d9b558ec3f3e87bfee772861d2b7 2.4-rc
76 76 195ad823b5d58c68903a6153a25e3fb4ed25239d 2.4
77 77 0c10cf8191469e7c3c8844922e17e71a176cb7cb 2.4.1
78 78 a4765077b65e6ae29ba42bab7834717b5072d5ba 2.4.2
79 79 f5fbe15ca7449f2c9a3cf817c86d0ae68b307214 2.5-rc
80 80 a6088c05e43a8aee0472ca3a4f6f8d7dd914ebbf 2.5
81 81 7511d4df752e61fe7ae4f3682e0a0008573b0402 2.5.1
82 82 5b7175377babacce80a6c1e12366d8032a6d4340 2.5.2
83 83 50c922c1b5145dab8baefefb0437d363b6a6c21c 2.5.3
84 84 8a7bd2dccd44ed571afe7424cd7f95594f27c092 2.5.4
85 85 292cd385856d98bacb2c3086f8897bc660c2beea 2.6-rc
86 86 23f785b38af38d2fca6b8f3db56b8007a84cd73a 2.6
87 87 ddc7a6be20212d18f3e27d9d7e6f079a66d96f21 2.6.1
88 88 cceaf7af4c9e9e6fa2dbfdcfe9856c5da69c4ffd 2.6.2
89 89 009794acc6e37a650f0fae37872e733382ac1c0c 2.6.3
90 90 f0d7721d7322dcfb5af33599c2543f27335334bb 2.7-rc
91 91 f37b5a17e6a0ee17afde2cdde5393dd74715fb58 2.7
92 92 335a558f81dc73afeab4d7be63617392b130117f 2.7.1
93 93 e7fa36d2ad3a7944a52dca126458d6f482db3524 2.7.2
94 94 1596f2d8f2421314b1ddead8f7d0c91009358994 2.8-rc
95 95 d825e4025e39d1c39db943cdc89818abd0a87c27 2.8
96 96 209e04a06467e2969c0cc6501335be0406d46ef0 2.8.1
97 97 ca387377df7a3a67dbb90b6336b781cdadc3ef41 2.8.2
98 98 8862469e16f9236208581b20de5f96bd13cc039d 2.9-rc
99 99 3cec5134e9c4bceab6a00c60f52a4f80677a78f2 2.9
100 100 b96cb15ec9e04d8ac5ee08b34fcbbe4200588965 2.9.1
101 101 3f83fc5cfe715d292069ee8417c83804f6c6c1e4 2.9.2
102 102 564f55b251224f16508dd1311452db7780dafe2b 3.0-rc
103 103 2195ac506c6ababe86985b932f4948837c0891b5 3.0
104 104 269c80ee5b3cb3684fa8edc61501b3506d02eb10 3.0.1
105 105 2d8cd3d0e83c7336c0cb45a9f88638363f993848 3.0.2
106 106 6c36dc6cd61a0e1b563f1d51e55bdf4dacf12162 3.1-rc
107 3178e49892020336491cdc6945885c4de26ffa8b 3.1
@@ -1,342 +1,343 b''
1 1 # git.py - git support for the convert extension
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import os
9 9 import subprocess
10 10 from mercurial import util, config
11 11 from mercurial.node import hex, nullid
12 12 from mercurial.i18n import _
13 13
14 14 from common import NoRepo, commit, converter_source, checktool
15 15
16 16 class submodule(object):
17 17 def __init__(self, path, node, url):
18 18 self.path = path
19 19 self.node = node
20 20 self.url = url
21 21
22 22 def hgsub(self):
23 23 return "%s = [git]%s" % (self.path, self.url)
24 24
25 25 def hgsubstate(self):
26 26 return "%s %s" % (self.node, self.path)
27 27
28 28 class convert_git(converter_source):
29 29 # Windows does not support GIT_DIR= construct while other systems
30 30 # cannot remove environment variable. Just assume none have
31 31 # both issues.
32 32 if util.safehasattr(os, 'unsetenv'):
33 33 def gitopen(self, s, err=None):
34 34 prevgitdir = os.environ.get('GIT_DIR')
35 35 os.environ['GIT_DIR'] = self.path
36 36 try:
37 37 if err == subprocess.PIPE:
38 38 (stdin, stdout, stderr) = util.popen3(s)
39 39 return stdout
40 40 elif err == subprocess.STDOUT:
41 41 return self.popen_with_stderr(s)
42 42 else:
43 43 return util.popen(s, 'rb')
44 44 finally:
45 45 if prevgitdir is None:
46 46 del os.environ['GIT_DIR']
47 47 else:
48 48 os.environ['GIT_DIR'] = prevgitdir
49 49
50 50 def gitpipe(self, s):
51 51 prevgitdir = os.environ.get('GIT_DIR')
52 52 os.environ['GIT_DIR'] = self.path
53 53 try:
54 54 return util.popen3(s)
55 55 finally:
56 56 if prevgitdir is None:
57 57 del os.environ['GIT_DIR']
58 58 else:
59 59 os.environ['GIT_DIR'] = prevgitdir
60 60
61 61 else:
62 62 def gitopen(self, s, err=None):
63 63 if err == subprocess.PIPE:
64 64 (sin, so, se) = util.popen3('GIT_DIR=%s %s' % (self.path, s))
65 65 return so
66 66 elif err == subprocess.STDOUT:
67 67 return self.popen_with_stderr(s)
68 68 else:
69 69 return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
70 70
71 71 def gitpipe(self, s):
72 72 return util.popen3('GIT_DIR=%s %s' % (self.path, s))
73 73
74 74 def popen_with_stderr(self, s):
75 75 p = subprocess.Popen(s, shell=True, bufsize=-1,
76 76 close_fds=util.closefds,
77 77 stdin=subprocess.PIPE,
78 78 stdout=subprocess.PIPE,
79 79 stderr=subprocess.STDOUT,
80 80 universal_newlines=False,
81 81 env=None)
82 82 return p.stdout
83 83
84 84 def gitread(self, s):
85 85 fh = self.gitopen(s)
86 86 data = fh.read()
87 87 return data, fh.close()
88 88
89 89 def __init__(self, ui, path, rev=None):
90 90 super(convert_git, self).__init__(ui, path, rev=rev)
91 91
92 92 if os.path.isdir(path + "/.git"):
93 93 path += "/.git"
94 94 if not os.path.exists(path + "/objects"):
95 95 raise NoRepo(_("%s does not look like a Git repository") % path)
96 96
97 97 checktool('git', 'git')
98 98
99 99 self.path = path
100 100 self.submodules = []
101 101
102 102 self.catfilepipe = self.gitpipe('git cat-file --batch')
103 103
104 104 def after(self):
105 105 for f in self.catfilepipe:
106 106 f.close()
107 107
108 108 def getheads(self):
109 109 if not self.rev:
110 110 heads, ret = self.gitread('git rev-parse --branches --remotes')
111 111 heads = heads.splitlines()
112 112 else:
113 113 heads, ret = self.gitread("git rev-parse --verify %s" % self.rev)
114 114 heads = [heads[:-1]]
115 115 if ret:
116 116 raise util.Abort(_('cannot retrieve git heads'))
117 117 return heads
118 118
119 119 def catfile(self, rev, type):
120 120 if rev == hex(nullid):
121 121 raise IOError
122 122 self.catfilepipe[0].write(rev+'\n')
123 123 self.catfilepipe[0].flush()
124 124 info = self.catfilepipe[1].readline().split()
125 125 if info[1] != type:
126 126 raise util.Abort(_('cannot read %r object at %s') % (type, rev))
127 127 size = int(info[2])
128 128 data = self.catfilepipe[1].read(size)
129 129 if len(data) < size:
130 raise util.Abort(_('cannot read %r object at %s: %s') % (type, rev))
130 raise util.Abort(_('cannot read %r object at %s: unexpected size')
131 % (type, rev))
131 132 # read the trailing newline
132 133 self.catfilepipe[1].read(1)
133 134 return data
134 135
135 136 def getfile(self, name, rev):
136 137 if rev == hex(nullid):
137 138 raise IOError
138 139 if name == '.hgsub':
139 140 data = '\n'.join([m.hgsub() for m in self.submoditer()])
140 141 mode = ''
141 142 elif name == '.hgsubstate':
142 143 data = '\n'.join([m.hgsubstate() for m in self.submoditer()])
143 144 mode = ''
144 145 else:
145 146 data = self.catfile(rev, "blob")
146 147 mode = self.modecache[(name, rev)]
147 148 return data, mode
148 149
149 150 def submoditer(self):
150 151 null = hex(nullid)
151 152 for m in sorted(self.submodules, key=lambda p: p.path):
152 153 if m.node != null:
153 154 yield m
154 155
155 156 def parsegitmodules(self, content):
156 157 """Parse the formatted .gitmodules file, example file format:
157 158 [submodule "sub"]\n
158 159 \tpath = sub\n
159 160 \turl = git://giturl\n
160 161 """
161 162 self.submodules = []
162 163 c = config.config()
163 164 # Each item in .gitmodules starts with \t that cant be parsed
164 165 c.parse('.gitmodules', content.replace('\t',''))
165 166 for sec in c.sections():
166 167 s = c[sec]
167 168 if 'url' in s and 'path' in s:
168 169 self.submodules.append(submodule(s['path'], '', s['url']))
169 170
170 171 def retrievegitmodules(self, version):
171 172 modules, ret = self.gitread("git show %s:%s" % (version, '.gitmodules'))
172 173 if ret:
173 174 raise util.Abort(_('cannot read submodules config file in %s') %
174 175 version)
175 176 self.parsegitmodules(modules)
176 177 for m in self.submodules:
177 178 node, ret = self.gitread("git rev-parse %s:%s" % (version, m.path))
178 179 if ret:
179 180 continue
180 181 m.node = node.strip()
181 182
182 183 def getchanges(self, version):
183 184 self.modecache = {}
184 185 fh = self.gitopen("git diff-tree -z --root -m -r %s" % version)
185 186 changes = []
186 187 seen = set()
187 188 entry = None
188 189 subexists = False
189 190 subdeleted = False
190 191 for l in fh.read().split('\x00'):
191 192 if not entry:
192 193 if not l.startswith(':'):
193 194 continue
194 195 entry = l
195 196 continue
196 197 f = l
197 198 if f not in seen:
198 199 seen.add(f)
199 200 entry = entry.split()
200 201 h = entry[3]
201 202 p = (entry[1] == "100755")
202 203 s = (entry[1] == "120000")
203 204
204 205 if f == '.gitmodules':
205 206 subexists = True
206 207 if entry[4] == 'D':
207 208 subdeleted = True
208 209 changes.append(('.hgsub', hex(nullid)))
209 210 else:
210 211 changes.append(('.hgsub', ''))
211 212 elif entry[1] == '160000' or entry[0] == ':160000':
212 213 subexists = True
213 214 else:
214 215 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
215 216 changes.append((f, h))
216 217 entry = None
217 218 if fh.close():
218 219 raise util.Abort(_('cannot read changes in %s') % version)
219 220
220 221 if subexists:
221 222 if subdeleted:
222 223 changes.append(('.hgsubstate', hex(nullid)))
223 224 else:
224 225 self.retrievegitmodules(version)
225 226 changes.append(('.hgsubstate', ''))
226 227 return (changes, {})
227 228
228 229 def getcommit(self, version):
229 230 c = self.catfile(version, "commit") # read the commit hash
230 231 end = c.find("\n\n")
231 232 message = c[end + 2:]
232 233 message = self.recode(message)
233 234 l = c[:end].splitlines()
234 235 parents = []
235 236 author = committer = None
236 237 for e in l[1:]:
237 238 n, v = e.split(" ", 1)
238 239 if n == "author":
239 240 p = v.split()
240 241 tm, tz = p[-2:]
241 242 author = " ".join(p[:-2])
242 243 if author[0] == "<": author = author[1:-1]
243 244 author = self.recode(author)
244 245 if n == "committer":
245 246 p = v.split()
246 247 tm, tz = p[-2:]
247 248 committer = " ".join(p[:-2])
248 249 if committer[0] == "<": committer = committer[1:-1]
249 250 committer = self.recode(committer)
250 251 if n == "parent":
251 252 parents.append(v)
252 253
253 254 if committer and committer != author:
254 255 message += "\ncommitter: %s\n" % committer
255 256 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
256 257 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
257 258 date = tm + " " + str(tz)
258 259
259 260 c = commit(parents=parents, date=date, author=author, desc=message,
260 261 rev=version)
261 262 return c
262 263
263 264 def gettags(self):
264 265 tags = {}
265 266 alltags = {}
266 267 fh = self.gitopen('git ls-remote --tags "%s"' % self.path,
267 268 err=subprocess.STDOUT)
268 269 prefix = 'refs/tags/'
269 270
270 271 # Build complete list of tags, both annotated and bare ones
271 272 for line in fh:
272 273 line = line.strip()
273 274 if line.startswith("error:") or line.startswith("fatal:"):
274 275 raise util.Abort(_('cannot read tags from %s') % self.path)
275 276 node, tag = line.split(None, 1)
276 277 if not tag.startswith(prefix):
277 278 continue
278 279 alltags[tag[len(prefix):]] = node
279 280 if fh.close():
280 281 raise util.Abort(_('cannot read tags from %s') % self.path)
281 282
282 283 # Filter out tag objects for annotated tag refs
283 284 for tag in alltags:
284 285 if tag.endswith('^{}'):
285 286 tags[tag[:-3]] = alltags[tag]
286 287 else:
287 288 if tag + '^{}' in alltags:
288 289 continue
289 290 else:
290 291 tags[tag] = alltags[tag]
291 292
292 293 return tags
293 294
294 295 def getchangedfiles(self, version, i):
295 296 changes = []
296 297 if i is None:
297 298 fh = self.gitopen("git diff-tree --root -m -r %s" % version)
298 299 for l in fh:
299 300 if "\t" not in l:
300 301 continue
301 302 m, f = l[:-1].split("\t")
302 303 changes.append(f)
303 304 else:
304 305 fh = self.gitopen('git diff-tree --name-only --root -r %s '
305 306 '"%s^%s" --' % (version, version, i + 1))
306 307 changes = [f.rstrip('\n') for f in fh]
307 308 if fh.close():
308 309 raise util.Abort(_('cannot read changes in %s') % version)
309 310
310 311 return changes
311 312
312 313 def getbookmarks(self):
313 314 bookmarks = {}
314 315
315 316 # Interesting references in git are prefixed
316 317 prefix = 'refs/heads/'
317 318 prefixlen = len(prefix)
318 319
319 320 # factor two commands
320 321 gitcmd = { 'remote/': 'git ls-remote --heads origin',
321 322 '': 'git show-ref'}
322 323
323 324 # Origin heads
324 325 for reftype in gitcmd:
325 326 try:
326 327 fh = self.gitopen(gitcmd[reftype], err=subprocess.PIPE)
327 328 for line in fh:
328 329 line = line.strip()
329 330 rev, name = line.split(None, 1)
330 331 if not name.startswith(prefix):
331 332 continue
332 333 name = '%s%s' % (reftype, name[prefixlen:])
333 334 bookmarks[name] = rev
334 335 except Exception:
335 336 pass
336 337
337 338 return bookmarks
338 339
339 340 def checkrevformat(self, revstr, mapname='splicemap'):
340 341 """ git revision string is a 40 byte hex """
341 342 self.checkhexformat(revstr, mapname)
342 343
@@ -1,155 +1,155 b''
1 1 # fetch.py - pull and merge remote changes
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''pull, update and merge in one command (DEPRECATED)'''
9 9
10 10 from mercurial.i18n import _
11 11 from mercurial.node import nullid, short
12 12 from mercurial import commands, cmdutil, hg, util, error
13 13 from mercurial.lock import release
14 14
15 15 cmdtable = {}
16 16 command = cmdutil.command(cmdtable)
17 17 testedwith = 'internal'
18 18
19 19 @command('fetch',
20 20 [('r', 'rev', [],
21 21 _('a specific revision you would like to pull'), _('REV')),
22 ('e', 'edit', None, _('edit commit message')),
22 ('e', 'edit', None, _('invoke editor on commit messages')),
23 23 ('', 'force-editor', None, _('edit commit message (DEPRECATED)')),
24 24 ('', 'switch-parent', None, _('switch parents when merging')),
25 25 ] + commands.commitopts + commands.commitopts2 + commands.remoteopts,
26 26 _('hg fetch [SOURCE]'))
27 27 def fetch(ui, repo, source='default', **opts):
28 28 '''pull changes from a remote repository, merge new changes if needed.
29 29
30 30 This finds all changes from the repository at the specified path
31 31 or URL and adds them to the local repository.
32 32
33 33 If the pulled changes add a new branch head, the head is
34 34 automatically merged, and the result of the merge is committed.
35 35 Otherwise, the working directory is updated to include the new
36 36 changes.
37 37
38 38 When a merge is needed, the working directory is first updated to
39 39 the newly pulled changes. Local changes are then merged into the
40 40 pulled changes. To switch the merge order, use --switch-parent.
41 41
42 42 See :hg:`help dates` for a list of formats valid for -d/--date.
43 43
44 44 Returns 0 on success.
45 45 '''
46 46
47 47 date = opts.get('date')
48 48 if date:
49 49 opts['date'] = util.parsedate(date)
50 50
51 51 parent, p2 = repo.dirstate.parents()
52 52 branch = repo.dirstate.branch()
53 53 try:
54 54 branchnode = repo.branchtip(branch)
55 55 except error.RepoLookupError:
56 56 branchnode = None
57 57 if parent != branchnode:
58 58 raise util.Abort(_('working dir not at branch tip '
59 59 '(use "hg update" to check out branch tip)'))
60 60
61 61 if p2 != nullid:
62 62 raise util.Abort(_('outstanding uncommitted merge'))
63 63
64 64 wlock = lock = None
65 65 try:
66 66 wlock = repo.wlock()
67 67 lock = repo.lock()
68 68 mod, add, rem, del_ = repo.status()[:4]
69 69
70 70 if mod or add or rem:
71 71 raise util.Abort(_('outstanding uncommitted changes'))
72 72 if del_:
73 73 raise util.Abort(_('working directory is missing some files'))
74 74 bheads = repo.branchheads(branch)
75 75 bheads = [head for head in bheads if len(repo[head].children()) == 0]
76 76 if len(bheads) > 1:
77 77 raise util.Abort(_('multiple heads in this branch '
78 78 '(use "hg heads ." and "hg merge" to merge)'))
79 79
80 80 other = hg.peer(repo, opts, ui.expandpath(source))
81 81 ui.status(_('pulling from %s\n') %
82 82 util.hidepassword(ui.expandpath(source)))
83 83 revs = None
84 84 if opts['rev']:
85 85 try:
86 86 revs = [other.lookup(rev) for rev in opts['rev']]
87 87 except error.CapabilityError:
88 88 err = _("other repository doesn't support revision lookup, "
89 89 "so a rev cannot be specified.")
90 90 raise util.Abort(err)
91 91
92 92 # Are there any changes at all?
93 93 modheads = repo.pull(other, heads=revs)
94 94 if modheads == 0:
95 95 return 0
96 96
97 97 # Is this a simple fast-forward along the current branch?
98 98 newheads = repo.branchheads(branch)
99 99 newchildren = repo.changelog.nodesbetween([parent], newheads)[2]
100 100 if len(newheads) == 1 and len(newchildren):
101 101 if newchildren[0] != parent:
102 102 return hg.update(repo, newchildren[0])
103 103 else:
104 104 return 0
105 105
106 106 # Are there more than one additional branch heads?
107 107 newchildren = [n for n in newchildren if n != parent]
108 108 newparent = parent
109 109 if newchildren:
110 110 newparent = newchildren[0]
111 111 hg.clean(repo, newparent)
112 112 newheads = [n for n in newheads if n != newparent]
113 113 if len(newheads) > 1:
114 114 ui.status(_('not merging with %d other new branch heads '
115 115 '(use "hg heads ." and "hg merge" to merge them)\n') %
116 116 (len(newheads) - 1))
117 117 return 1
118 118
119 119 if not newheads:
120 120 return 0
121 121
122 122 # Otherwise, let's merge.
123 123 err = False
124 124 if newheads:
125 125 # By default, we consider the repository we're pulling
126 126 # *from* as authoritative, so we merge our changes into
127 127 # theirs.
128 128 if opts['switch_parent']:
129 129 firstparent, secondparent = newparent, newheads[0]
130 130 else:
131 131 firstparent, secondparent = newheads[0], newparent
132 132 ui.status(_('updating to %d:%s\n') %
133 133 (repo.changelog.rev(firstparent),
134 134 short(firstparent)))
135 135 hg.clean(repo, firstparent)
136 136 ui.status(_('merging with %d:%s\n') %
137 137 (repo.changelog.rev(secondparent), short(secondparent)))
138 138 err = hg.merge(repo, secondparent, remind=False)
139 139
140 140 if not err:
141 141 # we don't translate commit messages
142 142 message = (cmdutil.logmessage(ui, opts) or
143 143 ('Automated merge with %s' %
144 144 util.removeauth(other.url())))
145 145 editopt = opts.get('edit') or opts.get('force_editor')
146 146 n = repo.commit(message, opts['user'], opts['date'],
147 147 editor=cmdutil.getcommiteditor(edit=editopt))
148 148 ui.status(_('new changeset %d:%s merges remote changes '
149 149 'with local\n') % (repo.changelog.rev(n),
150 150 short(n)))
151 151
152 152 return err
153 153
154 154 finally:
155 155 release(lock, wlock)
@@ -1,297 +1,297 b''
1 1 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
2 2 #
3 3 # This software may be used and distributed according to the terms of the
4 4 # GNU General Public License version 2 or any later version.
5 5
6 6 '''commands to sign and verify changesets'''
7 7
8 8 import os, tempfile, binascii
9 9 from mercurial import util, commands, match, cmdutil
10 10 from mercurial import node as hgnode
11 11 from mercurial.i18n import _
12 12
13 13 cmdtable = {}
14 14 command = cmdutil.command(cmdtable)
15 15 testedwith = 'internal'
16 16
17 17 class gpg(object):
18 18 def __init__(self, path, key=None):
19 19 self.path = path
20 20 self.key = (key and " --local-user \"%s\"" % key) or ""
21 21
22 22 def sign(self, data):
23 23 gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key)
24 24 return util.filter(data, gpgcmd)
25 25
26 26 def verify(self, data, sig):
27 27 """ returns of the good and bad signatures"""
28 28 sigfile = datafile = None
29 29 try:
30 30 # create temporary files
31 31 fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig")
32 32 fp = os.fdopen(fd, 'wb')
33 33 fp.write(sig)
34 34 fp.close()
35 35 fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt")
36 36 fp = os.fdopen(fd, 'wb')
37 37 fp.write(data)
38 38 fp.close()
39 39 gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify "
40 40 "\"%s\" \"%s\"" % (self.path, sigfile, datafile))
41 41 ret = util.filter("", gpgcmd)
42 42 finally:
43 43 for f in (sigfile, datafile):
44 44 try:
45 45 if f:
46 46 os.unlink(f)
47 47 except OSError:
48 48 pass
49 49 keys = []
50 50 key, fingerprint = None, None
51 51 for l in ret.splitlines():
52 52 # see DETAILS in the gnupg documentation
53 53 # filter the logger output
54 54 if not l.startswith("[GNUPG:]"):
55 55 continue
56 56 l = l[9:]
57 57 if l.startswith("VALIDSIG"):
58 58 # fingerprint of the primary key
59 59 fingerprint = l.split()[10]
60 60 elif l.startswith("ERRSIG"):
61 61 key = l.split(" ", 3)[:2]
62 62 key.append("")
63 63 fingerprint = None
64 64 elif (l.startswith("GOODSIG") or
65 65 l.startswith("EXPSIG") or
66 66 l.startswith("EXPKEYSIG") or
67 67 l.startswith("BADSIG")):
68 68 if key is not None:
69 69 keys.append(key + [fingerprint])
70 70 key = l.split(" ", 2)
71 71 fingerprint = None
72 72 if key is not None:
73 73 keys.append(key + [fingerprint])
74 74 return keys
75 75
76 76 def newgpg(ui, **opts):
77 77 """create a new gpg instance"""
78 78 gpgpath = ui.config("gpg", "cmd", "gpg")
79 79 gpgkey = opts.get('key')
80 80 if not gpgkey:
81 81 gpgkey = ui.config("gpg", "key", None)
82 82 return gpg(gpgpath, gpgkey)
83 83
84 84 def sigwalk(repo):
85 85 """
86 86 walk over every sigs, yields a couple
87 87 ((node, version, sig), (filename, linenumber))
88 88 """
89 89 def parsefile(fileiter, context):
90 90 ln = 1
91 91 for l in fileiter:
92 92 if not l:
93 93 continue
94 94 yield (l.split(" ", 2), (context, ln))
95 95 ln += 1
96 96
97 97 # read the heads
98 98 fl = repo.file(".hgsigs")
99 99 for r in reversed(fl.heads()):
100 100 fn = ".hgsigs|%s" % hgnode.short(r)
101 101 for item in parsefile(fl.read(r).splitlines(), fn):
102 102 yield item
103 103 try:
104 104 # read local signatures
105 105 fn = "localsigs"
106 106 for item in parsefile(repo.opener(fn), fn):
107 107 yield item
108 108 except IOError:
109 109 pass
110 110
111 111 def getkeys(ui, repo, mygpg, sigdata, context):
112 112 """get the keys who signed a data"""
113 113 fn, ln = context
114 114 node, version, sig = sigdata
115 115 prefix = "%s:%d" % (fn, ln)
116 116 node = hgnode.bin(node)
117 117
118 118 data = node2txt(repo, node, version)
119 119 sig = binascii.a2b_base64(sig)
120 120 keys = mygpg.verify(data, sig)
121 121
122 122 validkeys = []
123 123 # warn for expired key and/or sigs
124 124 for key in keys:
125 125 if key[0] == "ERRSIG":
126 126 ui.write(_("%s Unknown key ID \"%s\"\n")
127 127 % (prefix, shortkey(ui, key[1][:15])))
128 128 continue
129 129 if key[0] == "BADSIG":
130 130 ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2]))
131 131 continue
132 132 if key[0] == "EXPSIG":
133 133 ui.write(_("%s Note: Signature has expired"
134 134 " (signed by: \"%s\")\n") % (prefix, key[2]))
135 135 elif key[0] == "EXPKEYSIG":
136 136 ui.write(_("%s Note: This key has expired"
137 137 " (signed by: \"%s\")\n") % (prefix, key[2]))
138 138 validkeys.append((key[1], key[2], key[3]))
139 139 return validkeys
140 140
141 141 @command("sigs", [], _('hg sigs'))
142 142 def sigs(ui, repo):
143 143 """list signed changesets"""
144 144 mygpg = newgpg(ui)
145 145 revs = {}
146 146
147 147 for data, context in sigwalk(repo):
148 148 node, version, sig = data
149 149 fn, ln = context
150 150 try:
151 151 n = repo.lookup(node)
152 152 except KeyError:
153 153 ui.warn(_("%s:%d node does not exist\n") % (fn, ln))
154 154 continue
155 155 r = repo.changelog.rev(n)
156 156 keys = getkeys(ui, repo, mygpg, data, context)
157 157 if not keys:
158 158 continue
159 159 revs.setdefault(r, [])
160 160 revs[r].extend(keys)
161 161 for rev in sorted(revs, reverse=True):
162 162 for k in revs[rev]:
163 163 r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev)))
164 164 ui.write("%-30s %s\n" % (keystr(ui, k), r))
165 165
166 166 @command("sigcheck", [], _('hg sigcheck REV'))
167 167 def check(ui, repo, rev):
168 168 """verify all the signatures there may be for a particular revision"""
169 169 mygpg = newgpg(ui)
170 170 rev = repo.lookup(rev)
171 171 hexrev = hgnode.hex(rev)
172 172 keys = []
173 173
174 174 for data, context in sigwalk(repo):
175 175 node, version, sig = data
176 176 if node == hexrev:
177 177 k = getkeys(ui, repo, mygpg, data, context)
178 178 if k:
179 179 keys.extend(k)
180 180
181 181 if not keys:
182 182 ui.write(_("no valid signature for %s\n") % hgnode.short(rev))
183 183 return
184 184
185 185 # print summary
186 186 ui.write("%s is signed by:\n" % hgnode.short(rev))
187 187 for key in keys:
188 188 ui.write(" %s\n" % keystr(ui, key))
189 189
190 190 def keystr(ui, key):
191 191 """associate a string to a key (username, comment)"""
192 192 keyid, user, fingerprint = key
193 193 comment = ui.config("gpg", fingerprint, None)
194 194 if comment:
195 195 return "%s (%s)" % (user, comment)
196 196 else:
197 197 return user
198 198
199 199 @command("sign",
200 200 [('l', 'local', None, _('make the signature local')),
201 201 ('f', 'force', None, _('sign even if the sigfile is modified')),
202 202 ('', 'no-commit', None, _('do not commit the sigfile after signing')),
203 203 ('k', 'key', '',
204 204 _('the key id to sign with'), _('ID')),
205 205 ('m', 'message', '',
206 _('commit message'), _('TEXT')),
206 _('use text as commit message'), _('TEXT')),
207 207 ('e', 'edit', False, _('invoke editor on commit messages')),
208 208 ] + commands.commitopts2,
209 209 _('hg sign [OPTION]... [REV]...'))
210 210 def sign(ui, repo, *revs, **opts):
211 211 """add a signature for the current or given revision
212 212
213 213 If no revision is given, the parent of the working directory is used,
214 214 or tip if no revision is checked out.
215 215
216 216 See :hg:`help dates` for a list of formats valid for -d/--date.
217 217 """
218 218
219 219 mygpg = newgpg(ui, **opts)
220 220 sigver = "0"
221 221 sigmessage = ""
222 222
223 223 date = opts.get('date')
224 224 if date:
225 225 opts['date'] = util.parsedate(date)
226 226
227 227 if revs:
228 228 nodes = [repo.lookup(n) for n in revs]
229 229 else:
230 230 nodes = [node for node in repo.dirstate.parents()
231 231 if node != hgnode.nullid]
232 232 if len(nodes) > 1:
233 233 raise util.Abort(_('uncommitted merge - please provide a '
234 234 'specific revision'))
235 235 if not nodes:
236 236 nodes = [repo.changelog.tip()]
237 237
238 238 for n in nodes:
239 239 hexnode = hgnode.hex(n)
240 240 ui.write(_("signing %d:%s\n") % (repo.changelog.rev(n),
241 241 hgnode.short(n)))
242 242 # build data
243 243 data = node2txt(repo, n, sigver)
244 244 sig = mygpg.sign(data)
245 245 if not sig:
246 246 raise util.Abort(_("error while signing"))
247 247 sig = binascii.b2a_base64(sig)
248 248 sig = sig.replace("\n", "")
249 249 sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
250 250
251 251 # write it
252 252 if opts['local']:
253 253 repo.opener.append("localsigs", sigmessage)
254 254 return
255 255
256 256 msigs = match.exact(repo.root, '', ['.hgsigs'])
257 257 s = repo.status(match=msigs, unknown=True, ignored=True)[:6]
258 258 if util.any(s) and not opts["force"]:
259 259 raise util.Abort(_("working copy of .hgsigs is changed "
260 260 "(please commit .hgsigs manually "
261 261 "or use --force)"))
262 262
263 263 sigsfile = repo.wfile(".hgsigs", "ab")
264 264 sigsfile.write(sigmessage)
265 265 sigsfile.close()
266 266
267 267 if '.hgsigs' not in repo.dirstate:
268 268 repo[None].add([".hgsigs"])
269 269
270 270 if opts["no_commit"]:
271 271 return
272 272
273 273 message = opts['message']
274 274 if not message:
275 275 # we don't translate commit messages
276 276 message = "\n".join(["Added signature for changeset %s"
277 277 % hgnode.short(n)
278 278 for n in nodes])
279 279 try:
280 280 repo.commit(message, opts['user'], opts['date'], match=msigs,
281 281 editor=cmdutil.getcommiteditor(**opts))
282 282 except ValueError, inst:
283 283 raise util.Abort(str(inst))
284 284
285 285 def shortkey(ui, key):
286 286 if len(key) != 16:
287 287 ui.debug("key ID \"%s\" format error\n" % key)
288 288 return key
289 289
290 290 return key[-8:]
291 291
292 292 def node2txt(repo, node, ver):
293 293 """map a manifest into some text"""
294 294 if ver == "0":
295 295 return "%s\n" % hgnode.hex(node)
296 296 else:
297 297 raise util.Abort(_("unknown signature version"))
@@ -1,928 +1,929 b''
1 1 # histedit.py - interactive history editing for mercurial
2 2 #
3 3 # Copyright 2009 Augie Fackler <raf@durin42.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 """interactive history editing
8 8
9 9 With this extension installed, Mercurial gains one new command: histedit. Usage
10 10 is as follows, assuming the following history::
11 11
12 12 @ 3[tip] 7c2fd3b9020c 2009-04-27 18:04 -0500 durin42
13 13 | Add delta
14 14 |
15 15 o 2 030b686bedc4 2009-04-27 18:04 -0500 durin42
16 16 | Add gamma
17 17 |
18 18 o 1 c561b4e977df 2009-04-27 18:04 -0500 durin42
19 19 | Add beta
20 20 |
21 21 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
22 22 Add alpha
23 23
24 24 If you were to run ``hg histedit c561b4e977df``, you would see the following
25 25 file open in your editor::
26 26
27 27 pick c561b4e977df Add beta
28 28 pick 030b686bedc4 Add gamma
29 29 pick 7c2fd3b9020c Add delta
30 30
31 31 # Edit history between c561b4e977df and 7c2fd3b9020c
32 32 #
33 33 # Commits are listed from least to most recent
34 34 #
35 35 # Commands:
36 36 # p, pick = use commit
37 37 # e, edit = use commit, but stop for amending
38 38 # f, fold = use commit, but combine it with the one above
39 39 # d, drop = remove commit from history
40 40 # m, mess = edit message without changing commit content
41 41 #
42 42
43 43 In this file, lines beginning with ``#`` are ignored. You must specify a rule
44 44 for each revision in your history. For example, if you had meant to add gamma
45 45 before beta, and then wanted to add delta in the same revision as beta, you
46 46 would reorganize the file to look like this::
47 47
48 48 pick 030b686bedc4 Add gamma
49 49 pick c561b4e977df Add beta
50 50 fold 7c2fd3b9020c Add delta
51 51
52 52 # Edit history between c561b4e977df and 7c2fd3b9020c
53 53 #
54 54 # Commits are listed from least to most recent
55 55 #
56 56 # Commands:
57 57 # p, pick = use commit
58 58 # e, edit = use commit, but stop for amending
59 59 # f, fold = use commit, but combine it with the one above
60 60 # d, drop = remove commit from history
61 61 # m, mess = edit message without changing commit content
62 62 #
63 63
64 64 At which point you close the editor and ``histedit`` starts working. When you
65 65 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
66 66 those revisions together, offering you a chance to clean up the commit message::
67 67
68 68 Add beta
69 69 ***
70 70 Add delta
71 71
72 72 Edit the commit message to your liking, then close the editor. For
73 73 this example, let's assume that the commit message was changed to
74 74 ``Add beta and delta.`` After histedit has run and had a chance to
75 75 remove any old or temporary revisions it needed, the history looks
76 76 like this::
77 77
78 78 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
79 79 | Add beta and delta.
80 80 |
81 81 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
82 82 | Add gamma
83 83 |
84 84 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
85 85 Add alpha
86 86
87 87 Note that ``histedit`` does *not* remove any revisions (even its own temporary
88 88 ones) until after it has completed all the editing operations, so it will
89 89 probably perform several strip operations when it's done. For the above example,
90 90 it had to run strip twice. Strip can be slow depending on a variety of factors,
91 91 so you might need to be a little patient. You can choose to keep the original
92 92 revisions by passing the ``--keep`` flag.
93 93
94 94 The ``edit`` operation will drop you back to a command prompt,
95 95 allowing you to edit files freely, or even use ``hg record`` to commit
96 96 some changes as a separate commit. When you're done, any remaining
97 97 uncommitted changes will be committed as well. When done, run ``hg
98 98 histedit --continue`` to finish this step. You'll be prompted for a
99 99 new commit message, but the default commit message will be the
100 100 original message for the ``edit`` ed revision.
101 101
102 102 The ``message`` operation will give you a chance to revise a commit
103 103 message without changing the contents. It's a shortcut for doing
104 104 ``edit`` immediately followed by `hg histedit --continue``.
105 105
106 106 If ``histedit`` encounters a conflict when moving a revision (while
107 107 handling ``pick`` or ``fold``), it'll stop in a similar manner to
108 108 ``edit`` with the difference that it won't prompt you for a commit
109 109 message when done. If you decide at this point that you don't like how
110 110 much work it will be to rearrange history, or that you made a mistake,
111 111 you can use ``hg histedit --abort`` to abandon the new changes you
112 112 have made and return to the state before you attempted to edit your
113 113 history.
114 114
115 115 If we clone the histedit-ed example repository above and add four more
116 116 changes, such that we have the following history::
117 117
118 118 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
119 119 | Add theta
120 120 |
121 121 o 5 140988835471 2009-04-27 18:04 -0500 stefan
122 122 | Add eta
123 123 |
124 124 o 4 122930637314 2009-04-27 18:04 -0500 stefan
125 125 | Add zeta
126 126 |
127 127 o 3 836302820282 2009-04-27 18:04 -0500 stefan
128 128 | Add epsilon
129 129 |
130 130 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
131 131 | Add beta and delta.
132 132 |
133 133 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
134 134 | Add gamma
135 135 |
136 136 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
137 137 Add alpha
138 138
139 139 If you run ``hg histedit --outgoing`` on the clone then it is the same
140 140 as running ``hg histedit 836302820282``. If you need plan to push to a
141 141 repository that Mercurial does not detect to be related to the source
142 142 repo, you can add a ``--force`` option.
143 143 """
144 144
145 145 try:
146 146 import cPickle as pickle
147 147 pickle.dump # import now
148 148 except ImportError:
149 149 import pickle
150 150 import os
151 151 import sys
152 152
153 153 from mercurial import cmdutil
154 154 from mercurial import discovery
155 155 from mercurial import error
156 156 from mercurial import copies
157 157 from mercurial import context
158 158 from mercurial import hg
159 159 from mercurial import node
160 160 from mercurial import repair
161 from mercurial import scmutil
161 162 from mercurial import util
162 163 from mercurial import obsolete
163 164 from mercurial import merge as mergemod
164 165 from mercurial.lock import release
165 166 from mercurial.i18n import _
166 167
167 168 cmdtable = {}
168 169 command = cmdutil.command(cmdtable)
169 170
170 171 testedwith = 'internal'
171 172
172 173 # i18n: command names and abbreviations must remain untranslated
173 174 editcomment = _("""# Edit history between %s and %s
174 175 #
175 176 # Commits are listed from least to most recent
176 177 #
177 178 # Commands:
178 179 # p, pick = use commit
179 180 # e, edit = use commit, but stop for amending
180 181 # f, fold = use commit, but combine it with the one above
181 182 # d, drop = remove commit from history
182 183 # m, mess = edit message without changing commit content
183 184 #
184 185 """)
185 186
186 187 def commitfuncfor(repo, src):
187 188 """Build a commit function for the replacement of <src>
188 189
189 190 This function ensure we apply the same treatment to all changesets.
190 191
191 192 - Add a 'histedit_source' entry in extra.
192 193
193 194 Note that fold have its own separated logic because its handling is a bit
194 195 different and not easily factored out of the fold method.
195 196 """
196 197 phasemin = src.phase()
197 198 def commitfunc(**kwargs):
198 199 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
199 200 try:
200 201 repo.ui.setconfig('phases', 'new-commit', phasemin,
201 202 'histedit')
202 203 extra = kwargs.get('extra', {}).copy()
203 204 extra['histedit_source'] = src.hex()
204 205 kwargs['extra'] = extra
205 206 return repo.commit(**kwargs)
206 207 finally:
207 208 repo.ui.restoreconfig(phasebackup)
208 209 return commitfunc
209 210
210 211
211 212
212 213 def applychanges(ui, repo, ctx, opts):
213 214 """Merge changeset from ctx (only) in the current working directory"""
214 215 wcpar = repo.dirstate.parents()[0]
215 216 if ctx.p1().node() == wcpar:
216 217 # edition ar "in place" we do not need to make any merge,
217 218 # just applies changes on parent for edition
218 219 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
219 220 stats = None
220 221 else:
221 222 try:
222 223 # ui.forcemerge is an internal variable, do not document
223 224 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
224 225 'histedit')
225 226 stats = mergemod.update(repo, ctx.node(), True, True, False,
226 227 ctx.p1().node())
227 228 finally:
228 229 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
229 230 repo.setparents(wcpar, node.nullid)
230 231 repo.dirstate.write()
231 232 # fix up dirstate for copies and renames
232 233 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
233 234 return stats
234 235
235 236 def collapse(repo, first, last, commitopts):
236 237 """collapse the set of revisions from first to last as new one.
237 238
238 239 Expected commit options are:
239 240 - message
240 241 - date
241 242 - username
242 243 Commit message is edited in all cases.
243 244
244 245 This function works in memory."""
245 246 ctxs = list(repo.set('%d::%d', first, last))
246 247 if not ctxs:
247 248 return None
248 249 base = first.parents()[0]
249 250
250 251 # commit a new version of the old changeset, including the update
251 252 # collect all files which might be affected
252 253 files = set()
253 254 for ctx in ctxs:
254 255 files.update(ctx.files())
255 256
256 257 # Recompute copies (avoid recording a -> b -> a)
257 258 copied = copies.pathcopies(base, last)
258 259
259 260 # prune files which were reverted by the updates
260 261 def samefile(f):
261 262 if f in last.manifest():
262 263 a = last.filectx(f)
263 264 if f in base.manifest():
264 265 b = base.filectx(f)
265 266 return (a.data() == b.data()
266 267 and a.flags() == b.flags())
267 268 else:
268 269 return False
269 270 else:
270 271 return f not in base.manifest()
271 272 files = [f for f in files if not samefile(f)]
272 273 # commit version of these files as defined by head
273 274 headmf = last.manifest()
274 275 def filectxfn(repo, ctx, path):
275 276 if path in headmf:
276 277 fctx = last[path]
277 278 flags = fctx.flags()
278 279 mctx = context.memfilectx(repo,
279 280 fctx.path(), fctx.data(),
280 281 islink='l' in flags,
281 282 isexec='x' in flags,
282 283 copied=copied.get(path))
283 284 return mctx
284 285 raise IOError()
285 286
286 287 if commitopts.get('message'):
287 288 message = commitopts['message']
288 289 else:
289 290 message = first.description()
290 291 user = commitopts.get('user')
291 292 date = commitopts.get('date')
292 293 extra = commitopts.get('extra')
293 294
294 295 parents = (first.p1().node(), first.p2().node())
295 296 new = context.memctx(repo,
296 297 parents=parents,
297 298 text=message,
298 299 files=files,
299 300 filectxfn=filectxfn,
300 301 user=user,
301 302 date=date,
302 303 extra=extra,
303 304 editor=cmdutil.getcommiteditor(edit=True))
304 305 return repo.commitctx(new)
305 306
306 307 def pick(ui, repo, ctx, ha, opts):
307 308 oldctx = repo[ha]
308 309 if oldctx.parents()[0] == ctx:
309 310 ui.debug('node %s unchanged\n' % ha)
310 311 return oldctx, []
311 312 hg.update(repo, ctx.node())
312 313 stats = applychanges(ui, repo, oldctx, opts)
313 314 if stats and stats[3] > 0:
314 315 raise error.InterventionRequired(_('Fix up the change and run '
315 316 'hg histedit --continue'))
316 317 # drop the second merge parent
317 318 commit = commitfuncfor(repo, oldctx)
318 319 n = commit(text=oldctx.description(), user=oldctx.user(),
319 320 date=oldctx.date(), extra=oldctx.extra())
320 321 if n is None:
321 322 ui.warn(_('%s: empty changeset\n')
322 323 % node.hex(ha))
323 324 return ctx, []
324 325 new = repo[n]
325 326 return new, [(oldctx.node(), (n,))]
326 327
327 328
328 329 def edit(ui, repo, ctx, ha, opts):
329 330 oldctx = repo[ha]
330 331 hg.update(repo, ctx.node())
331 332 applychanges(ui, repo, oldctx, opts)
332 333 raise error.InterventionRequired(
333 334 _('Make changes as needed, you may commit or record as needed now.\n'
334 335 'When you are finished, run hg histedit --continue to resume.'))
335 336
336 337 def fold(ui, repo, ctx, ha, opts):
337 338 oldctx = repo[ha]
338 339 hg.update(repo, ctx.node())
339 340 stats = applychanges(ui, repo, oldctx, opts)
340 341 if stats and stats[3] > 0:
341 342 raise error.InterventionRequired(
342 343 _('Fix up the change and run hg histedit --continue'))
343 344 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
344 345 date=oldctx.date(), extra=oldctx.extra())
345 346 if n is None:
346 347 ui.warn(_('%s: empty changeset')
347 348 % node.hex(ha))
348 349 return ctx, []
349 350 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
350 351
351 352 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
352 353 parent = ctx.parents()[0].node()
353 354 hg.update(repo, parent)
354 355 ### prepare new commit data
355 356 commitopts = opts.copy()
356 357 # username
357 358 if ctx.user() == oldctx.user():
358 359 username = ctx.user()
359 360 else:
360 361 username = ui.username()
361 362 commitopts['user'] = username
362 363 # commit message
363 364 newmessage = '\n***\n'.join(
364 365 [ctx.description()] +
365 366 [repo[r].description() for r in internalchanges] +
366 367 [oldctx.description()]) + '\n'
367 368 commitopts['message'] = newmessage
368 369 # date
369 370 commitopts['date'] = max(ctx.date(), oldctx.date())
370 371 extra = ctx.extra().copy()
371 372 # histedit_source
372 373 # note: ctx is likely a temporary commit but that the best we can do here
373 374 # This is sufficient to solve issue3681 anyway
374 375 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
375 376 commitopts['extra'] = extra
376 377 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
377 378 try:
378 379 phasemin = max(ctx.phase(), oldctx.phase())
379 380 repo.ui.setconfig('phases', 'new-commit', phasemin, 'histedit')
380 381 n = collapse(repo, ctx, repo[newnode], commitopts)
381 382 finally:
382 383 repo.ui.restoreconfig(phasebackup)
383 384 if n is None:
384 385 return ctx, []
385 386 hg.update(repo, n)
386 387 replacements = [(oldctx.node(), (newnode,)),
387 388 (ctx.node(), (n,)),
388 389 (newnode, (n,)),
389 390 ]
390 391 for ich in internalchanges:
391 392 replacements.append((ich, (n,)))
392 393 return repo[n], replacements
393 394
394 395 def drop(ui, repo, ctx, ha, opts):
395 396 return ctx, [(repo[ha].node(), ())]
396 397
397 398
398 399 def message(ui, repo, ctx, ha, opts):
399 400 oldctx = repo[ha]
400 401 hg.update(repo, ctx.node())
401 402 stats = applychanges(ui, repo, oldctx, opts)
402 403 if stats and stats[3] > 0:
403 404 raise error.InterventionRequired(
404 405 _('Fix up the change and run hg histedit --continue'))
405 406 message = oldctx.description()
406 407 commit = commitfuncfor(repo, oldctx)
407 408 new = commit(text=message, user=oldctx.user(), date=oldctx.date(),
408 409 extra=oldctx.extra(),
409 410 editor=cmdutil.getcommiteditor(edit=True))
410 411 newctx = repo[new]
411 412 if oldctx.node() != newctx.node():
412 413 return newctx, [(oldctx.node(), (new,))]
413 414 # We didn't make an edit, so just indicate no replaced nodes
414 415 return newctx, []
415 416
416 417 def findoutgoing(ui, repo, remote=None, force=False, opts={}):
417 418 """utility function to find the first outgoing changeset
418 419
419 420 Used by initialisation code"""
420 421 dest = ui.expandpath(remote or 'default-push', remote or 'default')
421 422 dest, revs = hg.parseurl(dest, None)[:2]
422 423 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
423 424
424 425 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
425 426 other = hg.peer(repo, opts, dest)
426 427
427 428 if revs:
428 429 revs = [repo.lookup(rev) for rev in revs]
429 430
430 431 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
431 432 if not outgoing.missing:
432 433 raise util.Abort(_('no outgoing ancestors'))
433 434 roots = list(repo.revs("roots(%ln)", outgoing.missing))
434 435 if 1 < len(roots):
435 436 msg = _('there are ambiguous outgoing revisions')
436 437 hint = _('see "hg help histedit" for more detail')
437 438 raise util.Abort(msg, hint=hint)
438 439 return repo.lookup(roots[0])
439 440
440 441 actiontable = {'p': pick,
441 442 'pick': pick,
442 443 'e': edit,
443 444 'edit': edit,
444 445 'f': fold,
445 446 'fold': fold,
446 447 'd': drop,
447 448 'drop': drop,
448 449 'm': message,
449 450 'mess': message,
450 451 }
451 452
452 453 @command('histedit',
453 454 [('', 'commands', '',
454 455 _('Read history edits from the specified file.')),
455 456 ('c', 'continue', False, _('continue an edit already in progress')),
456 457 ('k', 'keep', False,
457 458 _("don't strip old nodes after edit is complete")),
458 459 ('', 'abort', False, _('abort an edit in progress')),
459 460 ('o', 'outgoing', False, _('changesets not found in destination')),
460 461 ('f', 'force', False,
461 462 _('force outgoing even for unrelated repositories')),
462 463 ('r', 'rev', [], _('first revision to be edited'))],
463 464 _("ANCESTOR | --outgoing [URL]"))
464 465 def histedit(ui, repo, *freeargs, **opts):
465 466 """interactively edit changeset history
466 467
467 468 This command edits changesets between ANCESTOR and the parent of
468 469 the working directory.
469 470
470 471 With --outgoing, this edits changesets not found in the
471 472 destination repository. If URL of the destination is omitted, the
472 473 'default-push' (or 'default') path will be used.
473 474
474 475 For safety, this command is aborted, also if there are ambiguous
475 476 outgoing revisions which may confuse users: for example, there are
476 477 multiple branches containing outgoing revisions.
477 478
478 479 Use "min(outgoing() and ::.)" or similar revset specification
479 480 instead of --outgoing to specify edit target revision exactly in
480 481 such ambiguous situation. See :hg:`help revsets` for detail about
481 482 selecting revisions.
482 483
483 484 Returns 0 on success, 1 if user intervention is required (not only
484 485 for intentional "edit" command, but also for resolving unexpected
485 486 conflicts).
486 487 """
487 488 lock = wlock = None
488 489 try:
489 490 wlock = repo.wlock()
490 491 lock = repo.lock()
491 492 _histedit(ui, repo, *freeargs, **opts)
492 493 finally:
493 494 release(lock, wlock)
494 495
495 496 def _histedit(ui, repo, *freeargs, **opts):
496 497 # TODO only abort if we try and histedit mq patches, not just
497 498 # blanket if mq patches are applied somewhere
498 499 mq = getattr(repo, 'mq', None)
499 500 if mq and mq.applied:
500 501 raise util.Abort(_('source has mq patches applied'))
501 502
502 503 # basic argument incompatibility processing
503 504 outg = opts.get('outgoing')
504 505 cont = opts.get('continue')
505 506 abort = opts.get('abort')
506 507 force = opts.get('force')
507 508 rules = opts.get('commands', '')
508 509 revs = opts.get('rev', [])
509 510 goal = 'new' # This invocation goal, in new, continue, abort
510 511 if force and not outg:
511 512 raise util.Abort(_('--force only allowed with --outgoing'))
512 513 if cont:
513 514 if util.any((outg, abort, revs, freeargs, rules)):
514 515 raise util.Abort(_('no arguments allowed with --continue'))
515 516 goal = 'continue'
516 517 elif abort:
517 518 if util.any((outg, revs, freeargs, rules)):
518 519 raise util.Abort(_('no arguments allowed with --abort'))
519 520 goal = 'abort'
520 521 else:
521 522 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
522 523 raise util.Abort(_('history edit already in progress, try '
523 524 '--continue or --abort'))
524 525 if outg:
525 526 if revs:
526 527 raise util.Abort(_('no revisions allowed with --outgoing'))
527 528 if len(freeargs) > 1:
528 529 raise util.Abort(
529 530 _('only one repo argument allowed with --outgoing'))
530 531 else:
531 532 revs.extend(freeargs)
532 533 if len(revs) != 1:
533 534 raise util.Abort(
534 535 _('histedit requires exactly one ancestor revision'))
535 536
536 537
537 538 if goal == 'continue':
538 539 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
539 540 parentctx = repo[parentctxnode]
540 541 parentctx, repl = bootstrapcontinue(ui, repo, parentctx, rules, opts)
541 542 replacements.extend(repl)
542 543 elif goal == 'abort':
543 544 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
544 545 mapping, tmpnodes, leafs, _ntm = processreplacement(repo, replacements)
545 546 ui.debug('restore wc to old parent %s\n' % node.short(topmost))
546 547 # check whether we should update away
547 548 parentnodes = [c.node() for c in repo[None].parents()]
548 549 for n in leafs | set([parentctxnode]):
549 550 if n in parentnodes:
550 551 hg.clean(repo, topmost)
551 552 break
552 553 else:
553 554 pass
554 555 cleanupnode(ui, repo, 'created', tmpnodes)
555 556 cleanupnode(ui, repo, 'temp', leafs)
556 557 os.unlink(os.path.join(repo.path, 'histedit-state'))
557 558 return
558 559 else:
559 560 cmdutil.checkunfinished(repo)
560 561 cmdutil.bailifchanged(repo)
561 562
562 563 topmost, empty = repo.dirstate.parents()
563 564 if outg:
564 565 if freeargs:
565 566 remote = freeargs[0]
566 567 else:
567 568 remote = None
568 569 root = findoutgoing(ui, repo, remote, force, opts)
569 570 else:
570 rootrevs = list(repo.set('roots(%lr)', revs))
571 if len(rootrevs) != 1:
571 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs)))
572 if len(rr) != 1:
572 573 raise util.Abort(_('The specified revisions must have '
573 574 'exactly one common root'))
574 root = rootrevs[0].node()
575 root = rr[0].node()
575 576
576 577 keep = opts.get('keep', False)
577 578 revs = between(repo, root, topmost, keep)
578 579 if not revs:
579 580 raise util.Abort(_('%s is not an ancestor of working directory') %
580 581 node.short(root))
581 582
582 583 ctxs = [repo[r] for r in revs]
583 584 if not rules:
584 585 rules = '\n'.join([makedesc(c) for c in ctxs])
585 586 rules += '\n\n'
586 587 rules += editcomment % (node.short(root), node.short(topmost))
587 588 rules = ui.edit(rules, ui.username())
588 589 # Save edit rules in .hg/histedit-last-edit.txt in case
589 590 # the user needs to ask for help after something
590 591 # surprising happens.
591 592 f = open(repo.join('histedit-last-edit.txt'), 'w')
592 593 f.write(rules)
593 594 f.close()
594 595 else:
595 596 if rules == '-':
596 597 f = sys.stdin
597 598 else:
598 599 f = open(rules)
599 600 rules = f.read()
600 601 f.close()
601 602 rules = [l for l in (r.strip() for r in rules.splitlines())
602 603 if l and not l[0] == '#']
603 604 rules = verifyrules(rules, repo, ctxs)
604 605
605 606 parentctx = repo[root].parents()[0]
606 607 keep = opts.get('keep', False)
607 608 replacements = []
608 609
609 610
610 611 while rules:
611 612 writestate(repo, parentctx.node(), rules, keep, topmost, replacements)
612 613 action, ha = rules.pop(0)
613 614 ui.debug('histedit: processing %s %s\n' % (action, ha))
614 615 actfunc = actiontable[action]
615 616 parentctx, replacement_ = actfunc(ui, repo, parentctx, ha, opts)
616 617 replacements.extend(replacement_)
617 618
618 619 hg.update(repo, parentctx.node())
619 620
620 621 mapping, tmpnodes, created, ntm = processreplacement(repo, replacements)
621 622 if mapping:
622 623 for prec, succs in mapping.iteritems():
623 624 if not succs:
624 625 ui.debug('histedit: %s is dropped\n' % node.short(prec))
625 626 else:
626 627 ui.debug('histedit: %s is replaced by %s\n' % (
627 628 node.short(prec), node.short(succs[0])))
628 629 if len(succs) > 1:
629 630 m = 'histedit: %s'
630 631 for n in succs[1:]:
631 632 ui.debug(m % node.short(n))
632 633
633 634 if not keep:
634 635 if mapping:
635 636 movebookmarks(ui, repo, mapping, topmost, ntm)
636 637 # TODO update mq state
637 638 if obsolete._enabled:
638 639 markers = []
639 640 # sort by revision number because it sound "right"
640 641 for prec in sorted(mapping, key=repo.changelog.rev):
641 642 succs = mapping[prec]
642 643 markers.append((repo[prec],
643 644 tuple(repo[s] for s in succs)))
644 645 if markers:
645 646 obsolete.createmarkers(repo, markers)
646 647 else:
647 648 cleanupnode(ui, repo, 'replaced', mapping)
648 649
649 650 cleanupnode(ui, repo, 'temp', tmpnodes)
650 651 os.unlink(os.path.join(repo.path, 'histedit-state'))
651 652 if os.path.exists(repo.sjoin('undo')):
652 653 os.unlink(repo.sjoin('undo'))
653 654
654 655 def gatherchildren(repo, ctx):
655 656 # is there any new commit between the expected parent and "."
656 657 #
657 658 # note: does not take non linear new change in account (but previous
658 659 # implementation didn't used them anyway (issue3655)
659 660 newchildren = [c.node() for c in repo.set('(%d::.)', ctx)]
660 661 if ctx.node() != node.nullid:
661 662 if not newchildren:
662 663 # `ctx` should match but no result. This means that
663 664 # currentnode is not a descendant from ctx.
664 665 msg = _('%s is not an ancestor of working directory')
665 666 hint = _('use "histedit --abort" to clear broken state')
666 667 raise util.Abort(msg % ctx, hint=hint)
667 668 newchildren.pop(0) # remove ctx
668 669 return newchildren
669 670
670 671 def bootstrapcontinue(ui, repo, parentctx, rules, opts):
671 672 action, currentnode = rules.pop(0)
672 673 ctx = repo[currentnode]
673 674
674 675 newchildren = gatherchildren(repo, parentctx)
675 676
676 677 # Commit dirty working directory if necessary
677 678 new = None
678 679 m, a, r, d = repo.status()[:4]
679 680 if m or a or r or d:
680 681 # prepare the message for the commit to comes
681 682 if action in ('f', 'fold'):
682 683 message = 'fold-temp-revision %s' % currentnode
683 684 else:
684 685 message = ctx.description()
685 686 editopt = action in ('e', 'edit', 'm', 'mess')
686 687 editor = cmdutil.getcommiteditor(edit=editopt)
687 688 commit = commitfuncfor(repo, ctx)
688 689 new = commit(text=message, user=ctx.user(),
689 690 date=ctx.date(), extra=ctx.extra(),
690 691 editor=editor)
691 692 if new is not None:
692 693 newchildren.append(new)
693 694
694 695 replacements = []
695 696 # track replacements
696 697 if ctx.node() not in newchildren:
697 698 # note: new children may be empty when the changeset is dropped.
698 699 # this happen e.g during conflicting pick where we revert content
699 700 # to parent.
700 701 replacements.append((ctx.node(), tuple(newchildren)))
701 702
702 703 if action in ('f', 'fold'):
703 704 if newchildren:
704 705 # finalize fold operation if applicable
705 706 if new is None:
706 707 new = newchildren[-1]
707 708 else:
708 709 newchildren.pop() # remove new from internal changes
709 710 parentctx, repl = finishfold(ui, repo, parentctx, ctx, new, opts,
710 711 newchildren)
711 712 replacements.extend(repl)
712 713 else:
713 714 # newchildren is empty if the fold did not result in any commit
714 715 # this happen when all folded change are discarded during the
715 716 # merge.
716 717 replacements.append((ctx.node(), (parentctx.node(),)))
717 718 elif newchildren:
718 719 # otherwise update "parentctx" before proceeding to further operation
719 720 parentctx = repo[newchildren[-1]]
720 721 return parentctx, replacements
721 722
722 723
723 724 def between(repo, old, new, keep):
724 725 """select and validate the set of revision to edit
725 726
726 727 When keep is false, the specified set can't have children."""
727 728 ctxs = list(repo.set('%n::%n', old, new))
728 729 if ctxs and not keep:
729 730 if (not obsolete._enabled and
730 731 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
731 732 raise util.Abort(_('cannot edit history that would orphan nodes'))
732 733 if repo.revs('(%ld) and merge()', ctxs):
733 734 raise util.Abort(_('cannot edit history that contains merges'))
734 735 root = ctxs[0] # list is already sorted by repo.set
735 736 if not root.phase():
736 737 raise util.Abort(_('cannot edit immutable changeset: %s') % root)
737 738 return [c.node() for c in ctxs]
738 739
739 740
740 741 def writestate(repo, parentnode, rules, keep, topmost, replacements):
741 742 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
742 743 pickle.dump((parentnode, rules, keep, topmost, replacements), fp)
743 744 fp.close()
744 745
745 746 def readstate(repo):
746 747 """Returns a tuple of (parentnode, rules, keep, topmost, replacements).
747 748 """
748 749 fp = open(os.path.join(repo.path, 'histedit-state'))
749 750 return pickle.load(fp)
750 751
751 752
752 753 def makedesc(c):
753 754 """build a initial action line for a ctx `c`
754 755
755 756 line are in the form:
756 757
757 758 pick <hash> <rev> <summary>
758 759 """
759 760 summary = ''
760 761 if c.description():
761 762 summary = c.description().splitlines()[0]
762 763 line = 'pick %s %d %s' % (c, c.rev(), summary)
763 764 # trim to 80 columns so it's not stupidly wide in my editor
764 765 return util.ellipsis(line, 80)
765 766
766 767 def verifyrules(rules, repo, ctxs):
767 768 """Verify that there exists exactly one edit rule per given changeset.
768 769
769 770 Will abort if there are to many or too few rules, a malformed rule,
770 771 or a rule on a changeset outside of the user-given range.
771 772 """
772 773 parsed = []
773 774 expected = set(str(c) for c in ctxs)
774 775 seen = set()
775 776 for r in rules:
776 777 if ' ' not in r:
777 778 raise util.Abort(_('malformed line "%s"') % r)
778 779 action, rest = r.split(' ', 1)
779 780 ha = rest.strip().split(' ', 1)[0]
780 781 try:
781 782 ha = str(repo[ha]) # ensure its a short hash
782 783 except error.RepoError:
783 784 raise util.Abort(_('unknown changeset %s listed') % ha)
784 785 if ha not in expected:
785 786 raise util.Abort(
786 787 _('may not use changesets other than the ones listed'))
787 788 if ha in seen:
788 789 raise util.Abort(_('duplicated command for changeset %s') % ha)
789 790 seen.add(ha)
790 791 if action not in actiontable:
791 792 raise util.Abort(_('unknown action "%s"') % action)
792 793 parsed.append([action, ha])
793 794 missing = sorted(expected - seen) # sort to stabilize output
794 795 if missing:
795 796 raise util.Abort(_('missing rules for changeset %s') % missing[0],
796 797 hint=_('do you want to use the drop action?'))
797 798 return parsed
798 799
799 800 def processreplacement(repo, replacements):
800 801 """process the list of replacements to return
801 802
802 803 1) the final mapping between original and created nodes
803 804 2) the list of temporary node created by histedit
804 805 3) the list of new commit created by histedit"""
805 806 allsuccs = set()
806 807 replaced = set()
807 808 fullmapping = {}
808 809 # initialise basic set
809 810 # fullmapping record all operation recorded in replacement
810 811 for rep in replacements:
811 812 allsuccs.update(rep[1])
812 813 replaced.add(rep[0])
813 814 fullmapping.setdefault(rep[0], set()).update(rep[1])
814 815 new = allsuccs - replaced
815 816 tmpnodes = allsuccs & replaced
816 817 # Reduce content fullmapping into direct relation between original nodes
817 818 # and final node created during history edition
818 819 # Dropped changeset are replaced by an empty list
819 820 toproceed = set(fullmapping)
820 821 final = {}
821 822 while toproceed:
822 823 for x in list(toproceed):
823 824 succs = fullmapping[x]
824 825 for s in list(succs):
825 826 if s in toproceed:
826 827 # non final node with unknown closure
827 828 # We can't process this now
828 829 break
829 830 elif s in final:
830 831 # non final node, replace with closure
831 832 succs.remove(s)
832 833 succs.update(final[s])
833 834 else:
834 835 final[x] = succs
835 836 toproceed.remove(x)
836 837 # remove tmpnodes from final mapping
837 838 for n in tmpnodes:
838 839 del final[n]
839 840 # we expect all changes involved in final to exist in the repo
840 841 # turn `final` into list (topologically sorted)
841 842 nm = repo.changelog.nodemap
842 843 for prec, succs in final.items():
843 844 final[prec] = sorted(succs, key=nm.get)
844 845
845 846 # computed topmost element (necessary for bookmark)
846 847 if new:
847 848 newtopmost = sorted(new, key=repo.changelog.rev)[-1]
848 849 elif not final:
849 850 # Nothing rewritten at all. we won't need `newtopmost`
850 851 # It is the same as `oldtopmost` and `processreplacement` know it
851 852 newtopmost = None
852 853 else:
853 854 # every body died. The newtopmost is the parent of the root.
854 855 newtopmost = repo[sorted(final, key=repo.changelog.rev)[0]].p1().node()
855 856
856 857 return final, tmpnodes, new, newtopmost
857 858
858 859 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
859 860 """Move bookmark from old to newly created node"""
860 861 if not mapping:
861 862 # if nothing got rewritten there is not purpose for this function
862 863 return
863 864 moves = []
864 865 for bk, old in sorted(repo._bookmarks.iteritems()):
865 866 if old == oldtopmost:
866 867 # special case ensure bookmark stay on tip.
867 868 #
868 869 # This is arguably a feature and we may only want that for the
869 870 # active bookmark. But the behavior is kept compatible with the old
870 871 # version for now.
871 872 moves.append((bk, newtopmost))
872 873 continue
873 874 base = old
874 875 new = mapping.get(base, None)
875 876 if new is None:
876 877 continue
877 878 while not new:
878 879 # base is killed, trying with parent
879 880 base = repo[base].p1().node()
880 881 new = mapping.get(base, (base,))
881 882 # nothing to move
882 883 moves.append((bk, new[-1]))
883 884 if moves:
884 885 marks = repo._bookmarks
885 886 for mark, new in moves:
886 887 old = marks[mark]
887 888 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
888 889 % (mark, node.short(old), node.short(new)))
889 890 marks[mark] = new
890 891 marks.write()
891 892
892 893 def cleanupnode(ui, repo, name, nodes):
893 894 """strip a group of nodes from the repository
894 895
895 896 The set of node to strip may contains unknown nodes."""
896 897 ui.debug('should strip %s nodes %s\n' %
897 898 (name, ', '.join([node.short(n) for n in nodes])))
898 899 lock = None
899 900 try:
900 901 lock = repo.lock()
901 902 # Find all node that need to be stripped
902 903 # (we hg %lr instead of %ln to silently ignore unknown item
903 904 nm = repo.changelog.nodemap
904 905 nodes = [n for n in nodes if n in nm]
905 906 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
906 907 for c in roots:
907 908 # We should process node in reverse order to strip tip most first.
908 909 # but this trigger a bug in changegroup hook.
909 910 # This would reduce bundle overhead
910 911 repair.strip(ui, repo, c)
911 912 finally:
912 913 release(lock)
913 914
914 915 def summaryhook(ui, repo):
915 916 if not os.path.exists(repo.join('histedit-state')):
916 917 return
917 918 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
918 919 if rules:
919 920 # i18n: column positioning for "hg summary"
920 921 ui.write(_('hist: %s (histedit --continue)\n') %
921 922 (ui.label(_('%d remaining'), 'histedit.remaining') %
922 923 len(rules)))
923 924
924 925 def extsetup(ui):
925 926 cmdutil.summaryhooks.add('histedit', summaryhook)
926 927 cmdutil.unfinishedstates.append(
927 928 ['histedit-state', False, True, _('histedit in progress'),
928 929 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
@@ -1,3462 +1,3462 b''
1 1 # mq.py - patch queues for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''manage a stack of patches
9 9
10 10 This extension lets you work with a stack of patches in a Mercurial
11 11 repository. It manages two stacks of patches - all known patches, and
12 12 applied patches (subset of known patches).
13 13
14 14 Known patches are represented as patch files in the .hg/patches
15 15 directory. Applied patches are both patch files and changesets.
16 16
17 17 Common tasks (use :hg:`help command` for more details)::
18 18
19 19 create new patch qnew
20 20 import existing patch qimport
21 21
22 22 print patch series qseries
23 23 print applied patches qapplied
24 24
25 25 add known patch to applied stack qpush
26 26 remove patch from applied stack qpop
27 27 refresh contents of top applied patch qrefresh
28 28
29 29 By default, mq will automatically use git patches when required to
30 30 avoid losing file mode changes, copy records, binary files or empty
31 31 files creations or deletions. This behaviour can be configured with::
32 32
33 33 [mq]
34 34 git = auto/keep/yes/no
35 35
36 36 If set to 'keep', mq will obey the [diff] section configuration while
37 37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 38 'no', mq will override the [diff] section and always generate git or
39 39 regular patches, possibly losing data in the second case.
40 40
41 41 It may be desirable for mq changesets to be kept in the secret phase (see
42 42 :hg:`help phases`), which can be enabled with the following setting::
43 43
44 44 [mq]
45 45 secret = True
46 46
47 47 You will by default be managing a patch queue named "patches". You can
48 48 create other, independent patch queues with the :hg:`qqueue` command.
49 49
50 50 If the working directory contains uncommitted files, qpush, qpop and
51 51 qgoto abort immediately. If -f/--force is used, the changes are
52 52 discarded. Setting::
53 53
54 54 [mq]
55 55 keepchanges = True
56 56
57 57 make them behave as if --keep-changes were passed, and non-conflicting
58 58 local changes will be tolerated and preserved. If incompatible options
59 59 such as -f/--force or --exact are passed, this setting is ignored.
60 60
61 61 This extension used to provide a strip command. This command now lives
62 62 in the strip extension.
63 63 '''
64 64
65 65 from mercurial.i18n import _
66 66 from mercurial.node import bin, hex, short, nullid, nullrev
67 67 from mercurial.lock import release
68 68 from mercurial import commands, cmdutil, hg, scmutil, util, revset
69 69 from mercurial import extensions, error, phases
70 70 from mercurial import patch as patchmod
71 71 from mercurial import localrepo
72 72 from mercurial import subrepo
73 73 import os, re, errno, shutil
74 74
75 75 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
76 76
77 77 cmdtable = {}
78 78 command = cmdutil.command(cmdtable)
79 79 testedwith = 'internal'
80 80
81 81 # force load strip extension formerly included in mq and import some utility
82 82 try:
83 83 stripext = extensions.find('strip')
84 84 except KeyError:
85 85 # note: load is lazy so we could avoid the try-except,
86 86 # but I (marmoute) prefer this explicit code.
87 87 class dummyui(object):
88 88 def debug(self, msg):
89 89 pass
90 90 stripext = extensions.load(dummyui(), 'strip', '')
91 91
92 92 strip = stripext.strip
93 93 checksubstate = stripext.checksubstate
94 94 checklocalchanges = stripext.checklocalchanges
95 95
96 96
97 97 # Patch names looks like unix-file names.
98 98 # They must be joinable with queue directory and result in the patch path.
99 99 normname = util.normpath
100 100
101 101 class statusentry(object):
102 102 def __init__(self, node, name):
103 103 self.node, self.name = node, name
104 104 def __repr__(self):
105 105 return hex(self.node) + ':' + self.name
106 106
107 107 class patchheader(object):
108 108 def __init__(self, pf, plainmode=False):
109 109 def eatdiff(lines):
110 110 while lines:
111 111 l = lines[-1]
112 112 if (l.startswith("diff -") or
113 113 l.startswith("Index:") or
114 114 l.startswith("===========")):
115 115 del lines[-1]
116 116 else:
117 117 break
118 118 def eatempty(lines):
119 119 while lines:
120 120 if not lines[-1].strip():
121 121 del lines[-1]
122 122 else:
123 123 break
124 124
125 125 message = []
126 126 comments = []
127 127 user = None
128 128 date = None
129 129 parent = None
130 130 format = None
131 131 subject = None
132 132 branch = None
133 133 nodeid = None
134 134 diffstart = 0
135 135
136 136 for line in file(pf):
137 137 line = line.rstrip()
138 138 if (line.startswith('diff --git')
139 139 or (diffstart and line.startswith('+++ '))):
140 140 diffstart = 2
141 141 break
142 142 diffstart = 0 # reset
143 143 if line.startswith("--- "):
144 144 diffstart = 1
145 145 continue
146 146 elif format == "hgpatch":
147 147 # parse values when importing the result of an hg export
148 148 if line.startswith("# User "):
149 149 user = line[7:]
150 150 elif line.startswith("# Date "):
151 151 date = line[7:]
152 152 elif line.startswith("# Parent "):
153 153 parent = line[9:].lstrip()
154 154 elif line.startswith("# Branch "):
155 155 branch = line[9:]
156 156 elif line.startswith("# Node ID "):
157 157 nodeid = line[10:]
158 158 elif not line.startswith("# ") and line:
159 159 message.append(line)
160 160 format = None
161 161 elif line == '# HG changeset patch':
162 162 message = []
163 163 format = "hgpatch"
164 164 elif (format != "tagdone" and (line.startswith("Subject: ") or
165 165 line.startswith("subject: "))):
166 166 subject = line[9:]
167 167 format = "tag"
168 168 elif (format != "tagdone" and (line.startswith("From: ") or
169 169 line.startswith("from: "))):
170 170 user = line[6:]
171 171 format = "tag"
172 172 elif (format != "tagdone" and (line.startswith("Date: ") or
173 173 line.startswith("date: "))):
174 174 date = line[6:]
175 175 format = "tag"
176 176 elif format == "tag" and line == "":
177 177 # when looking for tags (subject: from: etc) they
178 178 # end once you find a blank line in the source
179 179 format = "tagdone"
180 180 elif message or line:
181 181 message.append(line)
182 182 comments.append(line)
183 183
184 184 eatdiff(message)
185 185 eatdiff(comments)
186 186 # Remember the exact starting line of the patch diffs before consuming
187 187 # empty lines, for external use by TortoiseHg and others
188 188 self.diffstartline = len(comments)
189 189 eatempty(message)
190 190 eatempty(comments)
191 191
192 192 # make sure message isn't empty
193 193 if format and format.startswith("tag") and subject:
194 194 message.insert(0, "")
195 195 message.insert(0, subject)
196 196
197 197 self.message = message
198 198 self.comments = comments
199 199 self.user = user
200 200 self.date = date
201 201 self.parent = parent
202 202 # nodeid and branch are for external use by TortoiseHg and others
203 203 self.nodeid = nodeid
204 204 self.branch = branch
205 205 self.haspatch = diffstart > 1
206 206 self.plainmode = plainmode
207 207
208 208 def setuser(self, user):
209 209 if not self.updateheader(['From: ', '# User '], user):
210 210 try:
211 211 patchheaderat = self.comments.index('# HG changeset patch')
212 212 self.comments.insert(patchheaderat + 1, '# User ' + user)
213 213 except ValueError:
214 214 if self.plainmode or self._hasheader(['Date: ']):
215 215 self.comments = ['From: ' + user] + self.comments
216 216 else:
217 217 tmp = ['# HG changeset patch', '# User ' + user, '']
218 218 self.comments = tmp + self.comments
219 219 self.user = user
220 220
221 221 def setdate(self, date):
222 222 if not self.updateheader(['Date: ', '# Date '], date):
223 223 try:
224 224 patchheaderat = self.comments.index('# HG changeset patch')
225 225 self.comments.insert(patchheaderat + 1, '# Date ' + date)
226 226 except ValueError:
227 227 if self.plainmode or self._hasheader(['From: ']):
228 228 self.comments = ['Date: ' + date] + self.comments
229 229 else:
230 230 tmp = ['# HG changeset patch', '# Date ' + date, '']
231 231 self.comments = tmp + self.comments
232 232 self.date = date
233 233
234 234 def setparent(self, parent):
235 235 if not self.updateheader(['# Parent '], parent):
236 236 try:
237 237 patchheaderat = self.comments.index('# HG changeset patch')
238 238 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
239 239 except ValueError:
240 240 pass
241 241 self.parent = parent
242 242
243 243 def setmessage(self, message):
244 244 if self.comments:
245 245 self._delmsg()
246 246 self.message = [message]
247 247 self.comments += self.message
248 248
249 249 def updateheader(self, prefixes, new):
250 250 '''Update all references to a field in the patch header.
251 251 Return whether the field is present.'''
252 252 res = False
253 253 for prefix in prefixes:
254 254 for i in xrange(len(self.comments)):
255 255 if self.comments[i].startswith(prefix):
256 256 self.comments[i] = prefix + new
257 257 res = True
258 258 break
259 259 return res
260 260
261 261 def _hasheader(self, prefixes):
262 262 '''Check if a header starts with any of the given prefixes.'''
263 263 for prefix in prefixes:
264 264 for comment in self.comments:
265 265 if comment.startswith(prefix):
266 266 return True
267 267 return False
268 268
269 269 def __str__(self):
270 270 if not self.comments:
271 271 return ''
272 272 return '\n'.join(self.comments) + '\n\n'
273 273
274 274 def _delmsg(self):
275 275 '''Remove existing message, keeping the rest of the comments fields.
276 276 If comments contains 'subject: ', message will prepend
277 277 the field and a blank line.'''
278 278 if self.message:
279 279 subj = 'subject: ' + self.message[0].lower()
280 280 for i in xrange(len(self.comments)):
281 281 if subj == self.comments[i].lower():
282 282 del self.comments[i]
283 283 self.message = self.message[2:]
284 284 break
285 285 ci = 0
286 286 for mi in self.message:
287 287 while mi != self.comments[ci]:
288 288 ci += 1
289 289 del self.comments[ci]
290 290
291 291 def newcommit(repo, phase, *args, **kwargs):
292 292 """helper dedicated to ensure a commit respect mq.secret setting
293 293
294 294 It should be used instead of repo.commit inside the mq source for operation
295 295 creating new changeset.
296 296 """
297 297 repo = repo.unfiltered()
298 298 if phase is None:
299 299 if repo.ui.configbool('mq', 'secret', False):
300 300 phase = phases.secret
301 301 if phase is not None:
302 302 backup = repo.ui.backupconfig('phases', 'new-commit')
303 303 try:
304 304 if phase is not None:
305 305 repo.ui.setconfig('phases', 'new-commit', phase, 'mq')
306 306 return repo.commit(*args, **kwargs)
307 307 finally:
308 308 if phase is not None:
309 309 repo.ui.restoreconfig(backup)
310 310
311 311 class AbortNoCleanup(error.Abort):
312 312 pass
313 313
314 314 class queue(object):
315 315 def __init__(self, ui, baseui, path, patchdir=None):
316 316 self.basepath = path
317 317 try:
318 318 fh = open(os.path.join(path, 'patches.queue'))
319 319 cur = fh.read().rstrip()
320 320 fh.close()
321 321 if not cur:
322 322 curpath = os.path.join(path, 'patches')
323 323 else:
324 324 curpath = os.path.join(path, 'patches-' + cur)
325 325 except IOError:
326 326 curpath = os.path.join(path, 'patches')
327 327 self.path = patchdir or curpath
328 328 self.opener = scmutil.opener(self.path)
329 329 self.ui = ui
330 330 self.baseui = baseui
331 331 self.applieddirty = False
332 332 self.seriesdirty = False
333 333 self.added = []
334 334 self.seriespath = "series"
335 335 self.statuspath = "status"
336 336 self.guardspath = "guards"
337 337 self.activeguards = None
338 338 self.guardsdirty = False
339 339 # Handle mq.git as a bool with extended values
340 340 try:
341 341 gitmode = ui.configbool('mq', 'git', None)
342 342 if gitmode is None:
343 343 raise error.ConfigError
344 344 self.gitmode = gitmode and 'yes' or 'no'
345 345 except error.ConfigError:
346 346 self.gitmode = ui.config('mq', 'git', 'auto').lower()
347 347 self.plainmode = ui.configbool('mq', 'plain', False)
348 348 self.checkapplied = True
349 349
350 350 @util.propertycache
351 351 def applied(self):
352 352 def parselines(lines):
353 353 for l in lines:
354 354 entry = l.split(':', 1)
355 355 if len(entry) > 1:
356 356 n, name = entry
357 357 yield statusentry(bin(n), name)
358 358 elif l.strip():
359 359 self.ui.warn(_('malformated mq status line: %s\n') % entry)
360 360 # else we ignore empty lines
361 361 try:
362 362 lines = self.opener.read(self.statuspath).splitlines()
363 363 return list(parselines(lines))
364 364 except IOError, e:
365 365 if e.errno == errno.ENOENT:
366 366 return []
367 367 raise
368 368
369 369 @util.propertycache
370 370 def fullseries(self):
371 371 try:
372 372 return self.opener.read(self.seriespath).splitlines()
373 373 except IOError, e:
374 374 if e.errno == errno.ENOENT:
375 375 return []
376 376 raise
377 377
378 378 @util.propertycache
379 379 def series(self):
380 380 self.parseseries()
381 381 return self.series
382 382
383 383 @util.propertycache
384 384 def seriesguards(self):
385 385 self.parseseries()
386 386 return self.seriesguards
387 387
388 388 def invalidate(self):
389 389 for a in 'applied fullseries series seriesguards'.split():
390 390 if a in self.__dict__:
391 391 delattr(self, a)
392 392 self.applieddirty = False
393 393 self.seriesdirty = False
394 394 self.guardsdirty = False
395 395 self.activeguards = None
396 396
397 397 def diffopts(self, opts={}, patchfn=None):
398 398 diffopts = patchmod.diffopts(self.ui, opts)
399 399 if self.gitmode == 'auto':
400 400 diffopts.upgrade = True
401 401 elif self.gitmode == 'keep':
402 402 pass
403 403 elif self.gitmode in ('yes', 'no'):
404 404 diffopts.git = self.gitmode == 'yes'
405 405 else:
406 406 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
407 407 ' got %s') % self.gitmode)
408 408 if patchfn:
409 409 diffopts = self.patchopts(diffopts, patchfn)
410 410 return diffopts
411 411
412 412 def patchopts(self, diffopts, *patches):
413 413 """Return a copy of input diff options with git set to true if
414 414 referenced patch is a git patch and should be preserved as such.
415 415 """
416 416 diffopts = diffopts.copy()
417 417 if not diffopts.git and self.gitmode == 'keep':
418 418 for patchfn in patches:
419 419 patchf = self.opener(patchfn, 'r')
420 420 # if the patch was a git patch, refresh it as a git patch
421 421 for line in patchf:
422 422 if line.startswith('diff --git'):
423 423 diffopts.git = True
424 424 break
425 425 patchf.close()
426 426 return diffopts
427 427
428 428 def join(self, *p):
429 429 return os.path.join(self.path, *p)
430 430
431 431 def findseries(self, patch):
432 432 def matchpatch(l):
433 433 l = l.split('#', 1)[0]
434 434 return l.strip() == patch
435 435 for index, l in enumerate(self.fullseries):
436 436 if matchpatch(l):
437 437 return index
438 438 return None
439 439
440 440 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
441 441
442 442 def parseseries(self):
443 443 self.series = []
444 444 self.seriesguards = []
445 445 for l in self.fullseries:
446 446 h = l.find('#')
447 447 if h == -1:
448 448 patch = l
449 449 comment = ''
450 450 elif h == 0:
451 451 continue
452 452 else:
453 453 patch = l[:h]
454 454 comment = l[h:]
455 455 patch = patch.strip()
456 456 if patch:
457 457 if patch in self.series:
458 458 raise util.Abort(_('%s appears more than once in %s') %
459 459 (patch, self.join(self.seriespath)))
460 460 self.series.append(patch)
461 461 self.seriesguards.append(self.guard_re.findall(comment))
462 462
463 463 def checkguard(self, guard):
464 464 if not guard:
465 465 return _('guard cannot be an empty string')
466 466 bad_chars = '# \t\r\n\f'
467 467 first = guard[0]
468 468 if first in '-+':
469 469 return (_('guard %r starts with invalid character: %r') %
470 470 (guard, first))
471 471 for c in bad_chars:
472 472 if c in guard:
473 473 return _('invalid character in guard %r: %r') % (guard, c)
474 474
475 475 def setactive(self, guards):
476 476 for guard in guards:
477 477 bad = self.checkguard(guard)
478 478 if bad:
479 479 raise util.Abort(bad)
480 480 guards = sorted(set(guards))
481 481 self.ui.debug('active guards: %s\n' % ' '.join(guards))
482 482 self.activeguards = guards
483 483 self.guardsdirty = True
484 484
485 485 def active(self):
486 486 if self.activeguards is None:
487 487 self.activeguards = []
488 488 try:
489 489 guards = self.opener.read(self.guardspath).split()
490 490 except IOError, err:
491 491 if err.errno != errno.ENOENT:
492 492 raise
493 493 guards = []
494 494 for i, guard in enumerate(guards):
495 495 bad = self.checkguard(guard)
496 496 if bad:
497 497 self.ui.warn('%s:%d: %s\n' %
498 498 (self.join(self.guardspath), i + 1, bad))
499 499 else:
500 500 self.activeguards.append(guard)
501 501 return self.activeguards
502 502
503 503 def setguards(self, idx, guards):
504 504 for g in guards:
505 505 if len(g) < 2:
506 506 raise util.Abort(_('guard %r too short') % g)
507 507 if g[0] not in '-+':
508 508 raise util.Abort(_('guard %r starts with invalid char') % g)
509 509 bad = self.checkguard(g[1:])
510 510 if bad:
511 511 raise util.Abort(bad)
512 512 drop = self.guard_re.sub('', self.fullseries[idx])
513 513 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
514 514 self.parseseries()
515 515 self.seriesdirty = True
516 516
517 517 def pushable(self, idx):
518 518 if isinstance(idx, str):
519 519 idx = self.series.index(idx)
520 520 patchguards = self.seriesguards[idx]
521 521 if not patchguards:
522 522 return True, None
523 523 guards = self.active()
524 524 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
525 525 if exactneg:
526 526 return False, repr(exactneg[0])
527 527 pos = [g for g in patchguards if g[0] == '+']
528 528 exactpos = [g for g in pos if g[1:] in guards]
529 529 if pos:
530 530 if exactpos:
531 531 return True, repr(exactpos[0])
532 532 return False, ' '.join(map(repr, pos))
533 533 return True, ''
534 534
535 535 def explainpushable(self, idx, all_patches=False):
536 536 write = all_patches and self.ui.write or self.ui.warn
537 537 if all_patches or self.ui.verbose:
538 538 if isinstance(idx, str):
539 539 idx = self.series.index(idx)
540 540 pushable, why = self.pushable(idx)
541 541 if all_patches and pushable:
542 542 if why is None:
543 543 write(_('allowing %s - no guards in effect\n') %
544 544 self.series[idx])
545 545 else:
546 546 if not why:
547 547 write(_('allowing %s - no matching negative guards\n') %
548 548 self.series[idx])
549 549 else:
550 550 write(_('allowing %s - guarded by %s\n') %
551 551 (self.series[idx], why))
552 552 if not pushable:
553 553 if why:
554 554 write(_('skipping %s - guarded by %s\n') %
555 555 (self.series[idx], why))
556 556 else:
557 557 write(_('skipping %s - no matching guards\n') %
558 558 self.series[idx])
559 559
560 560 def savedirty(self):
561 561 def writelist(items, path):
562 562 fp = self.opener(path, 'w')
563 563 for i in items:
564 564 fp.write("%s\n" % i)
565 565 fp.close()
566 566 if self.applieddirty:
567 567 writelist(map(str, self.applied), self.statuspath)
568 568 self.applieddirty = False
569 569 if self.seriesdirty:
570 570 writelist(self.fullseries, self.seriespath)
571 571 self.seriesdirty = False
572 572 if self.guardsdirty:
573 573 writelist(self.activeguards, self.guardspath)
574 574 self.guardsdirty = False
575 575 if self.added:
576 576 qrepo = self.qrepo()
577 577 if qrepo:
578 578 qrepo[None].add(f for f in self.added if f not in qrepo[None])
579 579 self.added = []
580 580
581 581 def removeundo(self, repo):
582 582 undo = repo.sjoin('undo')
583 583 if not os.path.exists(undo):
584 584 return
585 585 try:
586 586 os.unlink(undo)
587 587 except OSError, inst:
588 588 self.ui.warn(_('error removing undo: %s\n') % str(inst))
589 589
590 590 def backup(self, repo, files, copy=False):
591 591 # backup local changes in --force case
592 592 for f in sorted(files):
593 593 absf = repo.wjoin(f)
594 594 if os.path.lexists(absf):
595 595 self.ui.note(_('saving current version of %s as %s\n') %
596 596 (f, f + '.orig'))
597 597 if copy:
598 598 util.copyfile(absf, absf + '.orig')
599 599 else:
600 600 util.rename(absf, absf + '.orig')
601 601
602 602 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
603 603 fp=None, changes=None, opts={}):
604 604 stat = opts.get('stat')
605 605 m = scmutil.match(repo[node1], files, opts)
606 606 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
607 607 changes, stat, fp)
608 608
609 609 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
610 610 # first try just applying the patch
611 611 (err, n) = self.apply(repo, [patch], update_status=False,
612 612 strict=True, merge=rev)
613 613
614 614 if err == 0:
615 615 return (err, n)
616 616
617 617 if n is None:
618 618 raise util.Abort(_("apply failed for patch %s") % patch)
619 619
620 620 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
621 621
622 622 # apply failed, strip away that rev and merge.
623 623 hg.clean(repo, head)
624 624 strip(self.ui, repo, [n], update=False, backup='strip')
625 625
626 626 ctx = repo[rev]
627 627 ret = hg.merge(repo, rev)
628 628 if ret:
629 629 raise util.Abort(_("update returned %d") % ret)
630 630 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
631 631 if n is None:
632 632 raise util.Abort(_("repo commit failed"))
633 633 try:
634 634 ph = patchheader(mergeq.join(patch), self.plainmode)
635 635 except Exception:
636 636 raise util.Abort(_("unable to read %s") % patch)
637 637
638 638 diffopts = self.patchopts(diffopts, patch)
639 639 patchf = self.opener(patch, "w")
640 640 comments = str(ph)
641 641 if comments:
642 642 patchf.write(comments)
643 643 self.printdiff(repo, diffopts, head, n, fp=patchf)
644 644 patchf.close()
645 645 self.removeundo(repo)
646 646 return (0, n)
647 647
648 648 def qparents(self, repo, rev=None):
649 649 """return the mq handled parent or p1
650 650
651 651 In some case where mq get himself in being the parent of a merge the
652 652 appropriate parent may be p2.
653 653 (eg: an in progress merge started with mq disabled)
654 654
655 655 If no parent are managed by mq, p1 is returned.
656 656 """
657 657 if rev is None:
658 658 (p1, p2) = repo.dirstate.parents()
659 659 if p2 == nullid:
660 660 return p1
661 661 if not self.applied:
662 662 return None
663 663 return self.applied[-1].node
664 664 p1, p2 = repo.changelog.parents(rev)
665 665 if p2 != nullid and p2 in [x.node for x in self.applied]:
666 666 return p2
667 667 return p1
668 668
669 669 def mergepatch(self, repo, mergeq, series, diffopts):
670 670 if not self.applied:
671 671 # each of the patches merged in will have two parents. This
672 672 # can confuse the qrefresh, qdiff, and strip code because it
673 673 # needs to know which parent is actually in the patch queue.
674 674 # so, we insert a merge marker with only one parent. This way
675 675 # the first patch in the queue is never a merge patch
676 676 #
677 677 pname = ".hg.patches.merge.marker"
678 678 n = newcommit(repo, None, '[mq]: merge marker', force=True)
679 679 self.removeundo(repo)
680 680 self.applied.append(statusentry(n, pname))
681 681 self.applieddirty = True
682 682
683 683 head = self.qparents(repo)
684 684
685 685 for patch in series:
686 686 patch = mergeq.lookup(patch, strict=True)
687 687 if not patch:
688 688 self.ui.warn(_("patch %s does not exist\n") % patch)
689 689 return (1, None)
690 690 pushable, reason = self.pushable(patch)
691 691 if not pushable:
692 692 self.explainpushable(patch, all_patches=True)
693 693 continue
694 694 info = mergeq.isapplied(patch)
695 695 if not info:
696 696 self.ui.warn(_("patch %s is not applied\n") % patch)
697 697 return (1, None)
698 698 rev = info[1]
699 699 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
700 700 if head:
701 701 self.applied.append(statusentry(head, patch))
702 702 self.applieddirty = True
703 703 if err:
704 704 return (err, head)
705 705 self.savedirty()
706 706 return (0, head)
707 707
708 708 def patch(self, repo, patchfile):
709 709 '''Apply patchfile to the working directory.
710 710 patchfile: name of patch file'''
711 711 files = set()
712 712 try:
713 713 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
714 714 files=files, eolmode=None)
715 715 return (True, list(files), fuzz)
716 716 except Exception, inst:
717 717 self.ui.note(str(inst) + '\n')
718 718 if not self.ui.verbose:
719 719 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
720 720 self.ui.traceback()
721 721 return (False, list(files), False)
722 722
723 723 def apply(self, repo, series, list=False, update_status=True,
724 724 strict=False, patchdir=None, merge=None, all_files=None,
725 725 tobackup=None, keepchanges=False):
726 726 wlock = lock = tr = None
727 727 try:
728 728 wlock = repo.wlock()
729 729 lock = repo.lock()
730 730 tr = repo.transaction("qpush")
731 731 try:
732 732 ret = self._apply(repo, series, list, update_status,
733 733 strict, patchdir, merge, all_files=all_files,
734 734 tobackup=tobackup, keepchanges=keepchanges)
735 735 tr.close()
736 736 self.savedirty()
737 737 return ret
738 738 except AbortNoCleanup:
739 739 tr.close()
740 740 self.savedirty()
741 741 return 2, repo.dirstate.p1()
742 742 except: # re-raises
743 743 try:
744 744 tr.abort()
745 745 finally:
746 746 repo.invalidate()
747 747 repo.dirstate.invalidate()
748 748 self.invalidate()
749 749 raise
750 750 finally:
751 751 release(tr, lock, wlock)
752 752 self.removeundo(repo)
753 753
754 754 def _apply(self, repo, series, list=False, update_status=True,
755 755 strict=False, patchdir=None, merge=None, all_files=None,
756 756 tobackup=None, keepchanges=False):
757 757 """returns (error, hash)
758 758
759 759 error = 1 for unable to read, 2 for patch failed, 3 for patch
760 760 fuzz. tobackup is None or a set of files to backup before they
761 761 are modified by a patch.
762 762 """
763 763 # TODO unify with commands.py
764 764 if not patchdir:
765 765 patchdir = self.path
766 766 err = 0
767 767 n = None
768 768 for patchname in series:
769 769 pushable, reason = self.pushable(patchname)
770 770 if not pushable:
771 771 self.explainpushable(patchname, all_patches=True)
772 772 continue
773 773 self.ui.status(_("applying %s\n") % patchname)
774 774 pf = os.path.join(patchdir, patchname)
775 775
776 776 try:
777 777 ph = patchheader(self.join(patchname), self.plainmode)
778 778 except IOError:
779 779 self.ui.warn(_("unable to read %s\n") % patchname)
780 780 err = 1
781 781 break
782 782
783 783 message = ph.message
784 784 if not message:
785 785 # The commit message should not be translated
786 786 message = "imported patch %s\n" % patchname
787 787 else:
788 788 if list:
789 789 # The commit message should not be translated
790 790 message.append("\nimported patch %s" % patchname)
791 791 message = '\n'.join(message)
792 792
793 793 if ph.haspatch:
794 794 if tobackup:
795 795 touched = patchmod.changedfiles(self.ui, repo, pf)
796 796 touched = set(touched) & tobackup
797 797 if touched and keepchanges:
798 798 raise AbortNoCleanup(
799 799 _("local changes found, refresh first"))
800 800 self.backup(repo, touched, copy=True)
801 801 tobackup = tobackup - touched
802 802 (patcherr, files, fuzz) = self.patch(repo, pf)
803 803 if all_files is not None:
804 804 all_files.update(files)
805 805 patcherr = not patcherr
806 806 else:
807 807 self.ui.warn(_("patch %s is empty\n") % patchname)
808 808 patcherr, files, fuzz = 0, [], 0
809 809
810 810 if merge and files:
811 811 # Mark as removed/merged and update dirstate parent info
812 812 removed = []
813 813 merged = []
814 814 for f in files:
815 815 if os.path.lexists(repo.wjoin(f)):
816 816 merged.append(f)
817 817 else:
818 818 removed.append(f)
819 819 for f in removed:
820 820 repo.dirstate.remove(f)
821 821 for f in merged:
822 822 repo.dirstate.merge(f)
823 823 p1, p2 = repo.dirstate.parents()
824 824 repo.setparents(p1, merge)
825 825
826 826 if all_files and '.hgsubstate' in all_files:
827 827 wctx = repo[None]
828 828 pctx = repo['.']
829 829 overwrite = False
830 830 mergedsubstate = subrepo.submerge(repo, pctx, wctx, wctx,
831 831 overwrite)
832 832 files += mergedsubstate.keys()
833 833
834 834 match = scmutil.matchfiles(repo, files or [])
835 835 oldtip = repo['tip']
836 836 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
837 837 force=True)
838 838 if repo['tip'] == oldtip:
839 839 raise util.Abort(_("qpush exactly duplicates child changeset"))
840 840 if n is None:
841 841 raise util.Abort(_("repository commit failed"))
842 842
843 843 if update_status:
844 844 self.applied.append(statusentry(n, patchname))
845 845
846 846 if patcherr:
847 847 self.ui.warn(_("patch failed, rejects left in working dir\n"))
848 848 err = 2
849 849 break
850 850
851 851 if fuzz and strict:
852 852 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
853 853 err = 3
854 854 break
855 855 return (err, n)
856 856
857 857 def _cleanup(self, patches, numrevs, keep=False):
858 858 if not keep:
859 859 r = self.qrepo()
860 860 if r:
861 861 r[None].forget(patches)
862 862 for p in patches:
863 863 try:
864 864 os.unlink(self.join(p))
865 865 except OSError, inst:
866 866 if inst.errno != errno.ENOENT:
867 867 raise
868 868
869 869 qfinished = []
870 870 if numrevs:
871 871 qfinished = self.applied[:numrevs]
872 872 del self.applied[:numrevs]
873 873 self.applieddirty = True
874 874
875 875 unknown = []
876 876
877 877 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
878 878 reverse=True):
879 879 if i is not None:
880 880 del self.fullseries[i]
881 881 else:
882 882 unknown.append(p)
883 883
884 884 if unknown:
885 885 if numrevs:
886 886 rev = dict((entry.name, entry.node) for entry in qfinished)
887 887 for p in unknown:
888 888 msg = _('revision %s refers to unknown patches: %s\n')
889 889 self.ui.warn(msg % (short(rev[p]), p))
890 890 else:
891 891 msg = _('unknown patches: %s\n')
892 892 raise util.Abort(''.join(msg % p for p in unknown))
893 893
894 894 self.parseseries()
895 895 self.seriesdirty = True
896 896 return [entry.node for entry in qfinished]
897 897
898 898 def _revpatches(self, repo, revs):
899 899 firstrev = repo[self.applied[0].node].rev()
900 900 patches = []
901 901 for i, rev in enumerate(revs):
902 902
903 903 if rev < firstrev:
904 904 raise util.Abort(_('revision %d is not managed') % rev)
905 905
906 906 ctx = repo[rev]
907 907 base = self.applied[i].node
908 908 if ctx.node() != base:
909 909 msg = _('cannot delete revision %d above applied patches')
910 910 raise util.Abort(msg % rev)
911 911
912 912 patch = self.applied[i].name
913 913 for fmt in ('[mq]: %s', 'imported patch %s'):
914 914 if ctx.description() == fmt % patch:
915 915 msg = _('patch %s finalized without changeset message\n')
916 916 repo.ui.status(msg % patch)
917 917 break
918 918
919 919 patches.append(patch)
920 920 return patches
921 921
922 922 def finish(self, repo, revs):
923 923 # Manually trigger phase computation to ensure phasedefaults is
924 924 # executed before we remove the patches.
925 925 repo._phasecache
926 926 patches = self._revpatches(repo, sorted(revs))
927 927 qfinished = self._cleanup(patches, len(patches))
928 928 if qfinished and repo.ui.configbool('mq', 'secret', False):
929 929 # only use this logic when the secret option is added
930 930 oldqbase = repo[qfinished[0]]
931 931 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
932 932 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
933 933 phases.advanceboundary(repo, tphase, qfinished)
934 934
935 935 def delete(self, repo, patches, opts):
936 936 if not patches and not opts.get('rev'):
937 937 raise util.Abort(_('qdelete requires at least one revision or '
938 938 'patch name'))
939 939
940 940 realpatches = []
941 941 for patch in patches:
942 942 patch = self.lookup(patch, strict=True)
943 943 info = self.isapplied(patch)
944 944 if info:
945 945 raise util.Abort(_("cannot delete applied patch %s") % patch)
946 946 if patch not in self.series:
947 947 raise util.Abort(_("patch %s not in series file") % patch)
948 948 if patch not in realpatches:
949 949 realpatches.append(patch)
950 950
951 951 numrevs = 0
952 952 if opts.get('rev'):
953 953 if not self.applied:
954 954 raise util.Abort(_('no patches applied'))
955 955 revs = scmutil.revrange(repo, opts.get('rev'))
956 956 if len(revs) > 1 and revs[0] > revs[1]:
957 957 revs.reverse()
958 958 revpatches = self._revpatches(repo, revs)
959 959 realpatches += revpatches
960 960 numrevs = len(revpatches)
961 961
962 962 self._cleanup(realpatches, numrevs, opts.get('keep'))
963 963
964 964 def checktoppatch(self, repo):
965 965 '''check that working directory is at qtip'''
966 966 if self.applied:
967 967 top = self.applied[-1].node
968 968 patch = self.applied[-1].name
969 969 if repo.dirstate.p1() != top:
970 970 raise util.Abort(_("working directory revision is not qtip"))
971 971 return top, patch
972 972 return None, None
973 973
974 974 def putsubstate2changes(self, substatestate, changes):
975 975 for files in changes[:3]:
976 976 if '.hgsubstate' in files:
977 977 return # already listed up
978 978 # not yet listed up
979 979 if substatestate in 'a?':
980 980 changes[1].append('.hgsubstate')
981 981 elif substatestate in 'r':
982 982 changes[2].append('.hgsubstate')
983 983 else: # modified
984 984 changes[0].append('.hgsubstate')
985 985
986 986 def checklocalchanges(self, repo, force=False, refresh=True):
987 987 excsuffix = ''
988 988 if refresh:
989 989 excsuffix = ', refresh first'
990 990 # plain versions for i18n tool to detect them
991 991 _("local changes found, refresh first")
992 992 _("local changed subrepos found, refresh first")
993 993 return checklocalchanges(repo, force, excsuffix)
994 994
995 995 _reserved = ('series', 'status', 'guards', '.', '..')
996 996 def checkreservedname(self, name):
997 997 if name in self._reserved:
998 998 raise util.Abort(_('"%s" cannot be used as the name of a patch')
999 999 % name)
1000 1000 for prefix in ('.hg', '.mq'):
1001 1001 if name.startswith(prefix):
1002 1002 raise util.Abort(_('patch name cannot begin with "%s"')
1003 1003 % prefix)
1004 1004 for c in ('#', ':'):
1005 1005 if c in name:
1006 1006 raise util.Abort(_('"%s" cannot be used in the name of a patch')
1007 1007 % c)
1008 1008
1009 1009 def checkpatchname(self, name, force=False):
1010 1010 self.checkreservedname(name)
1011 1011 if not force and os.path.exists(self.join(name)):
1012 1012 if os.path.isdir(self.join(name)):
1013 1013 raise util.Abort(_('"%s" already exists as a directory')
1014 1014 % name)
1015 1015 else:
1016 1016 raise util.Abort(_('patch "%s" already exists') % name)
1017 1017
1018 1018 def checkkeepchanges(self, keepchanges, force):
1019 1019 if force and keepchanges:
1020 1020 raise util.Abort(_('cannot use both --force and --keep-changes'))
1021 1021
1022 1022 def new(self, repo, patchfn, *pats, **opts):
1023 1023 """options:
1024 1024 msg: a string or a no-argument function returning a string
1025 1025 """
1026 1026 msg = opts.get('msg')
1027 1027 edit = opts.get('edit')
1028 1028 user = opts.get('user')
1029 1029 date = opts.get('date')
1030 1030 if date:
1031 1031 date = util.parsedate(date)
1032 1032 diffopts = self.diffopts({'git': opts.get('git')})
1033 1033 if opts.get('checkname', True):
1034 1034 self.checkpatchname(patchfn)
1035 1035 inclsubs = checksubstate(repo)
1036 1036 if inclsubs:
1037 1037 substatestate = repo.dirstate['.hgsubstate']
1038 1038 if opts.get('include') or opts.get('exclude') or pats:
1039 1039 match = scmutil.match(repo[None], pats, opts)
1040 1040 # detect missing files in pats
1041 1041 def badfn(f, msg):
1042 1042 if f != '.hgsubstate': # .hgsubstate is auto-created
1043 1043 raise util.Abort('%s: %s' % (f, msg))
1044 1044 match.bad = badfn
1045 1045 changes = repo.status(match=match)
1046 1046 else:
1047 1047 changes = self.checklocalchanges(repo, force=True)
1048 1048 commitfiles = list(inclsubs)
1049 1049 for files in changes[:3]:
1050 1050 commitfiles.extend(files)
1051 1051 match = scmutil.matchfiles(repo, commitfiles)
1052 1052 if len(repo[None].parents()) > 1:
1053 1053 raise util.Abort(_('cannot manage merge changesets'))
1054 1054 self.checktoppatch(repo)
1055 1055 insert = self.fullseriesend()
1056 1056 wlock = repo.wlock()
1057 1057 try:
1058 1058 try:
1059 1059 # if patch file write fails, abort early
1060 1060 p = self.opener(patchfn, "w")
1061 1061 except IOError, e:
1062 1062 raise util.Abort(_('cannot write patch "%s": %s')
1063 1063 % (patchfn, e.strerror))
1064 1064 try:
1065 1065 if self.plainmode:
1066 1066 if user:
1067 1067 p.write("From: " + user + "\n")
1068 1068 if not date:
1069 1069 p.write("\n")
1070 1070 if date:
1071 1071 p.write("Date: %d %d\n\n" % date)
1072 1072 else:
1073 1073 p.write("# HG changeset patch\n")
1074 1074 p.write("# Parent "
1075 1075 + hex(repo[None].p1().node()) + "\n")
1076 1076 if user:
1077 1077 p.write("# User " + user + "\n")
1078 1078 if date:
1079 1079 p.write("# Date %s %s\n\n" % date)
1080 1080
1081 1081 defaultmsg = "[mq]: %s" % patchfn
1082 1082 editor = cmdutil.getcommiteditor()
1083 1083 if edit:
1084 1084 def finishdesc(desc):
1085 1085 if desc.rstrip():
1086 1086 return desc
1087 1087 else:
1088 1088 return defaultmsg
1089 1089 # i18n: this message is shown in editor with "HG: " prefix
1090 1090 extramsg = _('Leave message empty to use default message.')
1091 1091 editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
1092 1092 extramsg=extramsg)
1093 1093 commitmsg = msg
1094 1094 else:
1095 1095 commitmsg = msg or defaultmsg
1096 1096
1097 1097 n = newcommit(repo, None, commitmsg, user, date, match=match,
1098 1098 force=True, editor=editor)
1099 1099 if n is None:
1100 1100 raise util.Abort(_("repo commit failed"))
1101 1101 try:
1102 1102 self.fullseries[insert:insert] = [patchfn]
1103 1103 self.applied.append(statusentry(n, patchfn))
1104 1104 self.parseseries()
1105 1105 self.seriesdirty = True
1106 1106 self.applieddirty = True
1107 1107 nctx = repo[n]
1108 1108 if nctx.description() != defaultmsg.rstrip():
1109 1109 msg = nctx.description() + "\n\n"
1110 1110 p.write(msg)
1111 1111 if commitfiles:
1112 1112 parent = self.qparents(repo, n)
1113 1113 if inclsubs:
1114 1114 self.putsubstate2changes(substatestate, changes)
1115 1115 chunks = patchmod.diff(repo, node1=parent, node2=n,
1116 1116 changes=changes, opts=diffopts)
1117 1117 for chunk in chunks:
1118 1118 p.write(chunk)
1119 1119 p.close()
1120 1120 r = self.qrepo()
1121 1121 if r:
1122 1122 r[None].add([patchfn])
1123 1123 except: # re-raises
1124 1124 repo.rollback()
1125 1125 raise
1126 1126 except Exception:
1127 1127 patchpath = self.join(patchfn)
1128 1128 try:
1129 1129 os.unlink(patchpath)
1130 1130 except OSError:
1131 1131 self.ui.warn(_('error unlinking %s\n') % patchpath)
1132 1132 raise
1133 1133 self.removeundo(repo)
1134 1134 finally:
1135 1135 release(wlock)
1136 1136
1137 1137 def isapplied(self, patch):
1138 1138 """returns (index, rev, patch)"""
1139 1139 for i, a in enumerate(self.applied):
1140 1140 if a.name == patch:
1141 1141 return (i, a.node, a.name)
1142 1142 return None
1143 1143
1144 1144 # if the exact patch name does not exist, we try a few
1145 1145 # variations. If strict is passed, we try only #1
1146 1146 #
1147 1147 # 1) a number (as string) to indicate an offset in the series file
1148 1148 # 2) a unique substring of the patch name was given
1149 1149 # 3) patchname[-+]num to indicate an offset in the series file
1150 1150 def lookup(self, patch, strict=False):
1151 1151 def partialname(s):
1152 1152 if s in self.series:
1153 1153 return s
1154 1154 matches = [x for x in self.series if s in x]
1155 1155 if len(matches) > 1:
1156 1156 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1157 1157 for m in matches:
1158 1158 self.ui.warn(' %s\n' % m)
1159 1159 return None
1160 1160 if matches:
1161 1161 return matches[0]
1162 1162 if self.series and self.applied:
1163 1163 if s == 'qtip':
1164 1164 return self.series[self.seriesend(True) - 1]
1165 1165 if s == 'qbase':
1166 1166 return self.series[0]
1167 1167 return None
1168 1168
1169 1169 if patch in self.series:
1170 1170 return patch
1171 1171
1172 1172 if not os.path.isfile(self.join(patch)):
1173 1173 try:
1174 1174 sno = int(patch)
1175 1175 except (ValueError, OverflowError):
1176 1176 pass
1177 1177 else:
1178 1178 if -len(self.series) <= sno < len(self.series):
1179 1179 return self.series[sno]
1180 1180
1181 1181 if not strict:
1182 1182 res = partialname(patch)
1183 1183 if res:
1184 1184 return res
1185 1185 minus = patch.rfind('-')
1186 1186 if minus >= 0:
1187 1187 res = partialname(patch[:minus])
1188 1188 if res:
1189 1189 i = self.series.index(res)
1190 1190 try:
1191 1191 off = int(patch[minus + 1:] or 1)
1192 1192 except (ValueError, OverflowError):
1193 1193 pass
1194 1194 else:
1195 1195 if i - off >= 0:
1196 1196 return self.series[i - off]
1197 1197 plus = patch.rfind('+')
1198 1198 if plus >= 0:
1199 1199 res = partialname(patch[:plus])
1200 1200 if res:
1201 1201 i = self.series.index(res)
1202 1202 try:
1203 1203 off = int(patch[plus + 1:] or 1)
1204 1204 except (ValueError, OverflowError):
1205 1205 pass
1206 1206 else:
1207 1207 if i + off < len(self.series):
1208 1208 return self.series[i + off]
1209 1209 raise util.Abort(_("patch %s not in series") % patch)
1210 1210
1211 1211 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1212 1212 all=False, move=False, exact=False, nobackup=False,
1213 1213 keepchanges=False):
1214 1214 self.checkkeepchanges(keepchanges, force)
1215 1215 diffopts = self.diffopts()
1216 1216 wlock = repo.wlock()
1217 1217 try:
1218 1218 heads = []
1219 1219 for hs in repo.branchmap().itervalues():
1220 1220 heads.extend(hs)
1221 1221 if not heads:
1222 1222 heads = [nullid]
1223 1223 if repo.dirstate.p1() not in heads and not exact:
1224 1224 self.ui.status(_("(working directory not at a head)\n"))
1225 1225
1226 1226 if not self.series:
1227 1227 self.ui.warn(_('no patches in series\n'))
1228 1228 return 0
1229 1229
1230 1230 # Suppose our series file is: A B C and the current 'top'
1231 1231 # patch is B. qpush C should be performed (moving forward)
1232 1232 # qpush B is a NOP (no change) qpush A is an error (can't
1233 1233 # go backwards with qpush)
1234 1234 if patch:
1235 1235 patch = self.lookup(patch)
1236 1236 info = self.isapplied(patch)
1237 1237 if info and info[0] >= len(self.applied) - 1:
1238 1238 self.ui.warn(
1239 1239 _('qpush: %s is already at the top\n') % patch)
1240 1240 return 0
1241 1241
1242 1242 pushable, reason = self.pushable(patch)
1243 1243 if pushable:
1244 1244 if self.series.index(patch) < self.seriesend():
1245 1245 raise util.Abort(
1246 1246 _("cannot push to a previous patch: %s") % patch)
1247 1247 else:
1248 1248 if reason:
1249 1249 reason = _('guarded by %s') % reason
1250 1250 else:
1251 1251 reason = _('no matching guards')
1252 1252 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1253 1253 return 1
1254 1254 elif all:
1255 1255 patch = self.series[-1]
1256 1256 if self.isapplied(patch):
1257 1257 self.ui.warn(_('all patches are currently applied\n'))
1258 1258 return 0
1259 1259
1260 1260 # Following the above example, starting at 'top' of B:
1261 1261 # qpush should be performed (pushes C), but a subsequent
1262 1262 # qpush without an argument is an error (nothing to
1263 1263 # apply). This allows a loop of "...while hg qpush..." to
1264 1264 # work as it detects an error when done
1265 1265 start = self.seriesend()
1266 1266 if start == len(self.series):
1267 1267 self.ui.warn(_('patch series already fully applied\n'))
1268 1268 return 1
1269 1269 if not force and not keepchanges:
1270 1270 self.checklocalchanges(repo, refresh=self.applied)
1271 1271
1272 1272 if exact:
1273 1273 if keepchanges:
1274 1274 raise util.Abort(
1275 1275 _("cannot use --exact and --keep-changes together"))
1276 1276 if move:
1277 1277 raise util.Abort(_('cannot use --exact and --move '
1278 1278 'together'))
1279 1279 if self.applied:
1280 1280 raise util.Abort(_('cannot push --exact with applied '
1281 1281 'patches'))
1282 1282 root = self.series[start]
1283 1283 target = patchheader(self.join(root), self.plainmode).parent
1284 1284 if not target:
1285 1285 raise util.Abort(
1286 1286 _("%s does not have a parent recorded") % root)
1287 1287 if not repo[target] == repo['.']:
1288 1288 hg.update(repo, target)
1289 1289
1290 1290 if move:
1291 1291 if not patch:
1292 1292 raise util.Abort(_("please specify the patch to move"))
1293 1293 for fullstart, rpn in enumerate(self.fullseries):
1294 1294 # strip markers for patch guards
1295 1295 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1296 1296 break
1297 1297 for i, rpn in enumerate(self.fullseries[fullstart:]):
1298 1298 # strip markers for patch guards
1299 1299 if self.guard_re.split(rpn, 1)[0] == patch:
1300 1300 break
1301 1301 index = fullstart + i
1302 1302 assert index < len(self.fullseries)
1303 1303 fullpatch = self.fullseries[index]
1304 1304 del self.fullseries[index]
1305 1305 self.fullseries.insert(fullstart, fullpatch)
1306 1306 self.parseseries()
1307 1307 self.seriesdirty = True
1308 1308
1309 1309 self.applieddirty = True
1310 1310 if start > 0:
1311 1311 self.checktoppatch(repo)
1312 1312 if not patch:
1313 1313 patch = self.series[start]
1314 1314 end = start + 1
1315 1315 else:
1316 1316 end = self.series.index(patch, start) + 1
1317 1317
1318 1318 tobackup = set()
1319 1319 if (not nobackup and force) or keepchanges:
1320 1320 m, a, r, d = self.checklocalchanges(repo, force=True)
1321 1321 if keepchanges:
1322 1322 tobackup.update(m + a + r + d)
1323 1323 else:
1324 1324 tobackup.update(m + a)
1325 1325
1326 1326 s = self.series[start:end]
1327 1327 all_files = set()
1328 1328 try:
1329 1329 if mergeq:
1330 1330 ret = self.mergepatch(repo, mergeq, s, diffopts)
1331 1331 else:
1332 1332 ret = self.apply(repo, s, list, all_files=all_files,
1333 1333 tobackup=tobackup, keepchanges=keepchanges)
1334 1334 except: # re-raises
1335 1335 self.ui.warn(_('cleaning up working directory...'))
1336 1336 node = repo.dirstate.p1()
1337 1337 hg.revert(repo, node, None)
1338 1338 # only remove unknown files that we know we touched or
1339 1339 # created while patching
1340 1340 for f in all_files:
1341 1341 if f not in repo.dirstate:
1342 1342 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1343 1343 self.ui.warn(_('done\n'))
1344 1344 raise
1345 1345
1346 1346 if not self.applied:
1347 1347 return ret[0]
1348 1348 top = self.applied[-1].name
1349 1349 if ret[0] and ret[0] > 1:
1350 1350 msg = _("errors during apply, please fix and refresh %s\n")
1351 1351 self.ui.write(msg % top)
1352 1352 else:
1353 1353 self.ui.write(_("now at: %s\n") % top)
1354 1354 return ret[0]
1355 1355
1356 1356 finally:
1357 1357 wlock.release()
1358 1358
1359 1359 def pop(self, repo, patch=None, force=False, update=True, all=False,
1360 1360 nobackup=False, keepchanges=False):
1361 1361 self.checkkeepchanges(keepchanges, force)
1362 1362 wlock = repo.wlock()
1363 1363 try:
1364 1364 if patch:
1365 1365 # index, rev, patch
1366 1366 info = self.isapplied(patch)
1367 1367 if not info:
1368 1368 patch = self.lookup(patch)
1369 1369 info = self.isapplied(patch)
1370 1370 if not info:
1371 1371 raise util.Abort(_("patch %s is not applied") % patch)
1372 1372
1373 1373 if not self.applied:
1374 1374 # Allow qpop -a to work repeatedly,
1375 1375 # but not qpop without an argument
1376 1376 self.ui.warn(_("no patches applied\n"))
1377 1377 return not all
1378 1378
1379 1379 if all:
1380 1380 start = 0
1381 1381 elif patch:
1382 1382 start = info[0] + 1
1383 1383 else:
1384 1384 start = len(self.applied) - 1
1385 1385
1386 1386 if start >= len(self.applied):
1387 1387 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1388 1388 return
1389 1389
1390 1390 if not update:
1391 1391 parents = repo.dirstate.parents()
1392 1392 rr = [x.node for x in self.applied]
1393 1393 for p in parents:
1394 1394 if p in rr:
1395 1395 self.ui.warn(_("qpop: forcing dirstate update\n"))
1396 1396 update = True
1397 1397 else:
1398 1398 parents = [p.node() for p in repo[None].parents()]
1399 1399 needupdate = False
1400 1400 for entry in self.applied[start:]:
1401 1401 if entry.node in parents:
1402 1402 needupdate = True
1403 1403 break
1404 1404 update = needupdate
1405 1405
1406 1406 tobackup = set()
1407 1407 if update:
1408 1408 m, a, r, d = self.checklocalchanges(
1409 1409 repo, force=force or keepchanges)
1410 1410 if force:
1411 1411 if not nobackup:
1412 1412 tobackup.update(m + a)
1413 1413 elif keepchanges:
1414 1414 tobackup.update(m + a + r + d)
1415 1415
1416 1416 self.applieddirty = True
1417 1417 end = len(self.applied)
1418 1418 rev = self.applied[start].node
1419 1419
1420 1420 try:
1421 1421 heads = repo.changelog.heads(rev)
1422 1422 except error.LookupError:
1423 1423 node = short(rev)
1424 1424 raise util.Abort(_('trying to pop unknown node %s') % node)
1425 1425
1426 1426 if heads != [self.applied[-1].node]:
1427 1427 raise util.Abort(_("popping would remove a revision not "
1428 1428 "managed by this patch queue"))
1429 1429 if not repo[self.applied[-1].node].mutable():
1430 1430 raise util.Abort(
1431 1431 _("popping would remove an immutable revision"),
1432 1432 hint=_('see "hg help phases" for details'))
1433 1433
1434 1434 # we know there are no local changes, so we can make a simplified
1435 1435 # form of hg.update.
1436 1436 if update:
1437 1437 qp = self.qparents(repo, rev)
1438 1438 ctx = repo[qp]
1439 1439 m, a, r, d = repo.status(qp, '.')[:4]
1440 1440 if d:
1441 1441 raise util.Abort(_("deletions found between repo revs"))
1442 1442
1443 1443 tobackup = set(a + m + r) & tobackup
1444 1444 if keepchanges and tobackup:
1445 1445 raise util.Abort(_("local changes found, refresh first"))
1446 1446 self.backup(repo, tobackup)
1447 1447
1448 1448 for f in a:
1449 1449 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1450 1450 repo.dirstate.drop(f)
1451 1451 for f in m + r:
1452 1452 fctx = ctx[f]
1453 1453 repo.wwrite(f, fctx.data(), fctx.flags())
1454 1454 repo.dirstate.normal(f)
1455 1455 repo.setparents(qp, nullid)
1456 1456 for patch in reversed(self.applied[start:end]):
1457 1457 self.ui.status(_("popping %s\n") % patch.name)
1458 1458 del self.applied[start:end]
1459 1459 strip(self.ui, repo, [rev], update=False, backup='strip')
1460 1460 for s, state in repo['.'].substate.items():
1461 1461 repo['.'].sub(s).get(state)
1462 1462 if self.applied:
1463 1463 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1464 1464 else:
1465 1465 self.ui.write(_("patch queue now empty\n"))
1466 1466 finally:
1467 1467 wlock.release()
1468 1468
1469 1469 def diff(self, repo, pats, opts):
1470 1470 top, patch = self.checktoppatch(repo)
1471 1471 if not top:
1472 1472 self.ui.write(_("no patches applied\n"))
1473 1473 return
1474 1474 qp = self.qparents(repo, top)
1475 1475 if opts.get('reverse'):
1476 1476 node1, node2 = None, qp
1477 1477 else:
1478 1478 node1, node2 = qp, None
1479 1479 diffopts = self.diffopts(opts, patch)
1480 1480 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1481 1481
1482 1482 def refresh(self, repo, pats=None, **opts):
1483 1483 if not self.applied:
1484 1484 self.ui.write(_("no patches applied\n"))
1485 1485 return 1
1486 1486 msg = opts.get('msg', '').rstrip()
1487 1487 edit = opts.get('edit')
1488 1488 newuser = opts.get('user')
1489 1489 newdate = opts.get('date')
1490 1490 if newdate:
1491 1491 newdate = '%d %d' % util.parsedate(newdate)
1492 1492 wlock = repo.wlock()
1493 1493
1494 1494 try:
1495 1495 self.checktoppatch(repo)
1496 1496 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1497 1497 if repo.changelog.heads(top) != [top]:
1498 1498 raise util.Abort(_("cannot refresh a revision with children"))
1499 1499 if not repo[top].mutable():
1500 1500 raise util.Abort(_("cannot refresh immutable revision"),
1501 1501 hint=_('see "hg help phases" for details'))
1502 1502
1503 1503 cparents = repo.changelog.parents(top)
1504 1504 patchparent = self.qparents(repo, top)
1505 1505
1506 1506 inclsubs = checksubstate(repo, hex(patchparent))
1507 1507 if inclsubs:
1508 1508 substatestate = repo.dirstate['.hgsubstate']
1509 1509
1510 1510 ph = patchheader(self.join(patchfn), self.plainmode)
1511 1511 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1512 1512 if newuser:
1513 1513 ph.setuser(newuser)
1514 1514 if newdate:
1515 1515 ph.setdate(newdate)
1516 1516 ph.setparent(hex(patchparent))
1517 1517
1518 1518 # only commit new patch when write is complete
1519 1519 patchf = self.opener(patchfn, 'w', atomictemp=True)
1520 1520
1521 1521 # update the dirstate in place, strip off the qtip commit
1522 1522 # and then commit.
1523 1523 #
1524 1524 # this should really read:
1525 1525 # mm, dd, aa = repo.status(top, patchparent)[:3]
1526 1526 # but we do it backwards to take advantage of manifest/changelog
1527 1527 # caching against the next repo.status call
1528 1528 mm, aa, dd = repo.status(patchparent, top)[:3]
1529 1529 changes = repo.changelog.read(top)
1530 1530 man = repo.manifest.read(changes[0])
1531 1531 aaa = aa[:]
1532 1532 matchfn = scmutil.match(repo[None], pats, opts)
1533 1533 # in short mode, we only diff the files included in the
1534 1534 # patch already plus specified files
1535 1535 if opts.get('short'):
1536 1536 # if amending a patch, we start with existing
1537 1537 # files plus specified files - unfiltered
1538 1538 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1539 1539 # filter with include/exclude options
1540 1540 matchfn = scmutil.match(repo[None], opts=opts)
1541 1541 else:
1542 1542 match = scmutil.matchall(repo)
1543 1543 m, a, r, d = repo.status(match=match)[:4]
1544 1544 mm = set(mm)
1545 1545 aa = set(aa)
1546 1546 dd = set(dd)
1547 1547
1548 1548 # we might end up with files that were added between
1549 1549 # qtip and the dirstate parent, but then changed in the
1550 1550 # local dirstate. in this case, we want them to only
1551 1551 # show up in the added section
1552 1552 for x in m:
1553 1553 if x not in aa:
1554 1554 mm.add(x)
1555 1555 # we might end up with files added by the local dirstate that
1556 1556 # were deleted by the patch. In this case, they should only
1557 1557 # show up in the changed section.
1558 1558 for x in a:
1559 1559 if x in dd:
1560 1560 dd.remove(x)
1561 1561 mm.add(x)
1562 1562 else:
1563 1563 aa.add(x)
1564 1564 # make sure any files deleted in the local dirstate
1565 1565 # are not in the add or change column of the patch
1566 1566 forget = []
1567 1567 for x in d + r:
1568 1568 if x in aa:
1569 1569 aa.remove(x)
1570 1570 forget.append(x)
1571 1571 continue
1572 1572 else:
1573 1573 mm.discard(x)
1574 1574 dd.add(x)
1575 1575
1576 1576 m = list(mm)
1577 1577 r = list(dd)
1578 1578 a = list(aa)
1579 1579
1580 1580 # create 'match' that includes the files to be recommitted.
1581 1581 # apply matchfn via repo.status to ensure correct case handling.
1582 1582 cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
1583 1583 allmatches = set(cm + ca + cr + cd)
1584 1584 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1585 1585
1586 1586 files = set(inclsubs)
1587 1587 for x in refreshchanges:
1588 1588 files.update(x)
1589 1589 match = scmutil.matchfiles(repo, files)
1590 1590
1591 1591 bmlist = repo[top].bookmarks()
1592 1592
1593 1593 try:
1594 1594 if diffopts.git or diffopts.upgrade:
1595 1595 copies = {}
1596 1596 for dst in a:
1597 1597 src = repo.dirstate.copied(dst)
1598 1598 # during qfold, the source file for copies may
1599 1599 # be removed. Treat this as a simple add.
1600 1600 if src is not None and src in repo.dirstate:
1601 1601 copies.setdefault(src, []).append(dst)
1602 1602 repo.dirstate.add(dst)
1603 1603 # remember the copies between patchparent and qtip
1604 1604 for dst in aaa:
1605 1605 f = repo.file(dst)
1606 1606 src = f.renamed(man[dst])
1607 1607 if src:
1608 1608 copies.setdefault(src[0], []).extend(
1609 1609 copies.get(dst, []))
1610 1610 if dst in a:
1611 1611 copies[src[0]].append(dst)
1612 1612 # we can't copy a file created by the patch itself
1613 1613 if dst in copies:
1614 1614 del copies[dst]
1615 1615 for src, dsts in copies.iteritems():
1616 1616 for dst in dsts:
1617 1617 repo.dirstate.copy(src, dst)
1618 1618 else:
1619 1619 for dst in a:
1620 1620 repo.dirstate.add(dst)
1621 1621 # Drop useless copy information
1622 1622 for f in list(repo.dirstate.copies()):
1623 1623 repo.dirstate.copy(None, f)
1624 1624 for f in r:
1625 1625 repo.dirstate.remove(f)
1626 1626 # if the patch excludes a modified file, mark that
1627 1627 # file with mtime=0 so status can see it.
1628 1628 mm = []
1629 1629 for i in xrange(len(m) - 1, -1, -1):
1630 1630 if not matchfn(m[i]):
1631 1631 mm.append(m[i])
1632 1632 del m[i]
1633 1633 for f in m:
1634 1634 repo.dirstate.normal(f)
1635 1635 for f in mm:
1636 1636 repo.dirstate.normallookup(f)
1637 1637 for f in forget:
1638 1638 repo.dirstate.drop(f)
1639 1639
1640 1640 user = ph.user or changes[1]
1641 1641
1642 1642 oldphase = repo[top].phase()
1643 1643
1644 1644 # assumes strip can roll itself back if interrupted
1645 1645 repo.setparents(*cparents)
1646 1646 self.applied.pop()
1647 1647 self.applieddirty = True
1648 1648 strip(self.ui, repo, [top], update=False, backup='strip')
1649 1649 except: # re-raises
1650 1650 repo.dirstate.invalidate()
1651 1651 raise
1652 1652
1653 1653 try:
1654 1654 # might be nice to attempt to roll back strip after this
1655 1655
1656 1656 defaultmsg = "[mq]: %s" % patchfn
1657 1657 editor = cmdutil.getcommiteditor()
1658 1658 if edit:
1659 1659 def finishdesc(desc):
1660 1660 if desc.rstrip():
1661 1661 ph.setmessage(desc)
1662 1662 return desc
1663 1663 return defaultmsg
1664 1664 # i18n: this message is shown in editor with "HG: " prefix
1665 1665 extramsg = _('Leave message empty to use default message.')
1666 1666 editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
1667 1667 extramsg=extramsg)
1668 1668 message = msg or "\n".join(ph.message)
1669 1669 elif not msg:
1670 1670 if not ph.message:
1671 1671 message = defaultmsg
1672 1672 else:
1673 1673 message = "\n".join(ph.message)
1674 1674 else:
1675 1675 message = msg
1676 1676 ph.setmessage(msg)
1677 1677
1678 1678 # Ensure we create a new changeset in the same phase than
1679 1679 # the old one.
1680 1680 n = newcommit(repo, oldphase, message, user, ph.date,
1681 1681 match=match, force=True, editor=editor)
1682 1682 # only write patch after a successful commit
1683 1683 c = [list(x) for x in refreshchanges]
1684 1684 if inclsubs:
1685 1685 self.putsubstate2changes(substatestate, c)
1686 1686 chunks = patchmod.diff(repo, patchparent,
1687 1687 changes=c, opts=diffopts)
1688 1688 comments = str(ph)
1689 1689 if comments:
1690 1690 patchf.write(comments)
1691 1691 for chunk in chunks:
1692 1692 patchf.write(chunk)
1693 1693 patchf.close()
1694 1694
1695 1695 marks = repo._bookmarks
1696 1696 for bm in bmlist:
1697 1697 marks[bm] = n
1698 1698 marks.write()
1699 1699
1700 1700 self.applied.append(statusentry(n, patchfn))
1701 1701 except: # re-raises
1702 1702 ctx = repo[cparents[0]]
1703 1703 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1704 1704 self.savedirty()
1705 1705 self.ui.warn(_('refresh interrupted while patch was popped! '
1706 1706 '(revert --all, qpush to recover)\n'))
1707 1707 raise
1708 1708 finally:
1709 1709 wlock.release()
1710 1710 self.removeundo(repo)
1711 1711
1712 1712 def init(self, repo, create=False):
1713 1713 if not create and os.path.isdir(self.path):
1714 1714 raise util.Abort(_("patch queue directory already exists"))
1715 1715 try:
1716 1716 os.mkdir(self.path)
1717 1717 except OSError, inst:
1718 1718 if inst.errno != errno.EEXIST or not create:
1719 1719 raise
1720 1720 if create:
1721 1721 return self.qrepo(create=True)
1722 1722
1723 1723 def unapplied(self, repo, patch=None):
1724 1724 if patch and patch not in self.series:
1725 1725 raise util.Abort(_("patch %s is not in series file") % patch)
1726 1726 if not patch:
1727 1727 start = self.seriesend()
1728 1728 else:
1729 1729 start = self.series.index(patch) + 1
1730 1730 unapplied = []
1731 1731 for i in xrange(start, len(self.series)):
1732 1732 pushable, reason = self.pushable(i)
1733 1733 if pushable:
1734 1734 unapplied.append((i, self.series[i]))
1735 1735 self.explainpushable(i)
1736 1736 return unapplied
1737 1737
1738 1738 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1739 1739 summary=False):
1740 1740 def displayname(pfx, patchname, state):
1741 1741 if pfx:
1742 1742 self.ui.write(pfx)
1743 1743 if summary:
1744 1744 ph = patchheader(self.join(patchname), self.plainmode)
1745 1745 msg = ph.message and ph.message[0] or ''
1746 1746 if self.ui.formatted():
1747 1747 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1748 1748 if width > 0:
1749 1749 msg = util.ellipsis(msg, width)
1750 1750 else:
1751 1751 msg = ''
1752 1752 self.ui.write(patchname, label='qseries.' + state)
1753 1753 self.ui.write(': ')
1754 1754 self.ui.write(msg, label='qseries.message.' + state)
1755 1755 else:
1756 1756 self.ui.write(patchname, label='qseries.' + state)
1757 1757 self.ui.write('\n')
1758 1758
1759 1759 applied = set([p.name for p in self.applied])
1760 1760 if length is None:
1761 1761 length = len(self.series) - start
1762 1762 if not missing:
1763 1763 if self.ui.verbose:
1764 1764 idxwidth = len(str(start + length - 1))
1765 1765 for i in xrange(start, start + length):
1766 1766 patch = self.series[i]
1767 1767 if patch in applied:
1768 1768 char, state = 'A', 'applied'
1769 1769 elif self.pushable(i)[0]:
1770 1770 char, state = 'U', 'unapplied'
1771 1771 else:
1772 1772 char, state = 'G', 'guarded'
1773 1773 pfx = ''
1774 1774 if self.ui.verbose:
1775 1775 pfx = '%*d %s ' % (idxwidth, i, char)
1776 1776 elif status and status != char:
1777 1777 continue
1778 1778 displayname(pfx, patch, state)
1779 1779 else:
1780 1780 msng_list = []
1781 1781 for root, dirs, files in os.walk(self.path):
1782 1782 d = root[len(self.path) + 1:]
1783 1783 for f in files:
1784 1784 fl = os.path.join(d, f)
1785 1785 if (fl not in self.series and
1786 1786 fl not in (self.statuspath, self.seriespath,
1787 1787 self.guardspath)
1788 1788 and not fl.startswith('.')):
1789 1789 msng_list.append(fl)
1790 1790 for x in sorted(msng_list):
1791 1791 pfx = self.ui.verbose and ('D ') or ''
1792 1792 displayname(pfx, x, 'missing')
1793 1793
1794 1794 def issaveline(self, l):
1795 1795 if l.name == '.hg.patches.save.line':
1796 1796 return True
1797 1797
1798 1798 def qrepo(self, create=False):
1799 1799 ui = self.baseui.copy()
1800 1800 if create or os.path.isdir(self.join(".hg")):
1801 1801 return hg.repository(ui, path=self.path, create=create)
1802 1802
1803 1803 def restore(self, repo, rev, delete=None, qupdate=None):
1804 1804 desc = repo[rev].description().strip()
1805 1805 lines = desc.splitlines()
1806 1806 i = 0
1807 1807 datastart = None
1808 1808 series = []
1809 1809 applied = []
1810 1810 qpp = None
1811 1811 for i, line in enumerate(lines):
1812 1812 if line == 'Patch Data:':
1813 1813 datastart = i + 1
1814 1814 elif line.startswith('Dirstate:'):
1815 1815 l = line.rstrip()
1816 1816 l = l[10:].split(' ')
1817 1817 qpp = [bin(x) for x in l]
1818 1818 elif datastart is not None:
1819 1819 l = line.rstrip()
1820 1820 n, name = l.split(':', 1)
1821 1821 if n:
1822 1822 applied.append(statusentry(bin(n), name))
1823 1823 else:
1824 1824 series.append(l)
1825 1825 if datastart is None:
1826 1826 self.ui.warn(_("no saved patch data found\n"))
1827 1827 return 1
1828 1828 self.ui.warn(_("restoring status: %s\n") % lines[0])
1829 1829 self.fullseries = series
1830 1830 self.applied = applied
1831 1831 self.parseseries()
1832 1832 self.seriesdirty = True
1833 1833 self.applieddirty = True
1834 1834 heads = repo.changelog.heads()
1835 1835 if delete:
1836 1836 if rev not in heads:
1837 1837 self.ui.warn(_("save entry has children, leaving it alone\n"))
1838 1838 else:
1839 1839 self.ui.warn(_("removing save entry %s\n") % short(rev))
1840 1840 pp = repo.dirstate.parents()
1841 1841 if rev in pp:
1842 1842 update = True
1843 1843 else:
1844 1844 update = False
1845 1845 strip(self.ui, repo, [rev], update=update, backup='strip')
1846 1846 if qpp:
1847 1847 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1848 1848 (short(qpp[0]), short(qpp[1])))
1849 1849 if qupdate:
1850 1850 self.ui.status(_("updating queue directory\n"))
1851 1851 r = self.qrepo()
1852 1852 if not r:
1853 1853 self.ui.warn(_("unable to load queue repository\n"))
1854 1854 return 1
1855 1855 hg.clean(r, qpp[0])
1856 1856
1857 1857 def save(self, repo, msg=None):
1858 1858 if not self.applied:
1859 1859 self.ui.warn(_("save: no patches applied, exiting\n"))
1860 1860 return 1
1861 1861 if self.issaveline(self.applied[-1]):
1862 1862 self.ui.warn(_("status is already saved\n"))
1863 1863 return 1
1864 1864
1865 1865 if not msg:
1866 1866 msg = _("hg patches saved state")
1867 1867 else:
1868 1868 msg = "hg patches: " + msg.rstrip('\r\n')
1869 1869 r = self.qrepo()
1870 1870 if r:
1871 1871 pp = r.dirstate.parents()
1872 1872 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1873 1873 msg += "\n\nPatch Data:\n"
1874 1874 msg += ''.join('%s\n' % x for x in self.applied)
1875 1875 msg += ''.join(':%s\n' % x for x in self.fullseries)
1876 1876 n = repo.commit(msg, force=True)
1877 1877 if not n:
1878 1878 self.ui.warn(_("repo commit failed\n"))
1879 1879 return 1
1880 1880 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1881 1881 self.applieddirty = True
1882 1882 self.removeundo(repo)
1883 1883
1884 1884 def fullseriesend(self):
1885 1885 if self.applied:
1886 1886 p = self.applied[-1].name
1887 1887 end = self.findseries(p)
1888 1888 if end is None:
1889 1889 return len(self.fullseries)
1890 1890 return end + 1
1891 1891 return 0
1892 1892
1893 1893 def seriesend(self, all_patches=False):
1894 1894 """If all_patches is False, return the index of the next pushable patch
1895 1895 in the series, or the series length. If all_patches is True, return the
1896 1896 index of the first patch past the last applied one.
1897 1897 """
1898 1898 end = 0
1899 1899 def nextpatch(start):
1900 1900 if all_patches or start >= len(self.series):
1901 1901 return start
1902 1902 for i in xrange(start, len(self.series)):
1903 1903 p, reason = self.pushable(i)
1904 1904 if p:
1905 1905 return i
1906 1906 self.explainpushable(i)
1907 1907 return len(self.series)
1908 1908 if self.applied:
1909 1909 p = self.applied[-1].name
1910 1910 try:
1911 1911 end = self.series.index(p)
1912 1912 except ValueError:
1913 1913 return 0
1914 1914 return nextpatch(end + 1)
1915 1915 return nextpatch(end)
1916 1916
1917 1917 def appliedname(self, index):
1918 1918 pname = self.applied[index].name
1919 1919 if not self.ui.verbose:
1920 1920 p = pname
1921 1921 else:
1922 1922 p = str(self.series.index(pname)) + " " + pname
1923 1923 return p
1924 1924
1925 1925 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1926 1926 force=None, git=False):
1927 1927 def checkseries(patchname):
1928 1928 if patchname in self.series:
1929 1929 raise util.Abort(_('patch %s is already in the series file')
1930 1930 % patchname)
1931 1931
1932 1932 if rev:
1933 1933 if files:
1934 1934 raise util.Abort(_('option "-r" not valid when importing '
1935 1935 'files'))
1936 1936 rev = scmutil.revrange(repo, rev)
1937 1937 rev.sort(reverse=True)
1938 1938 elif not files:
1939 1939 raise util.Abort(_('no files or revisions specified'))
1940 1940 if (len(files) > 1 or len(rev) > 1) and patchname:
1941 1941 raise util.Abort(_('option "-n" not valid when importing multiple '
1942 1942 'patches'))
1943 1943 imported = []
1944 1944 if rev:
1945 1945 # If mq patches are applied, we can only import revisions
1946 1946 # that form a linear path to qbase.
1947 1947 # Otherwise, they should form a linear path to a head.
1948 1948 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1949 1949 if len(heads) > 1:
1950 1950 raise util.Abort(_('revision %d is the root of more than one '
1951 1951 'branch') % rev[-1])
1952 1952 if self.applied:
1953 1953 base = repo.changelog.node(rev[0])
1954 1954 if base in [n.node for n in self.applied]:
1955 1955 raise util.Abort(_('revision %d is already managed')
1956 1956 % rev[0])
1957 1957 if heads != [self.applied[-1].node]:
1958 1958 raise util.Abort(_('revision %d is not the parent of '
1959 1959 'the queue') % rev[0])
1960 1960 base = repo.changelog.rev(self.applied[0].node)
1961 1961 lastparent = repo.changelog.parentrevs(base)[0]
1962 1962 else:
1963 1963 if heads != [repo.changelog.node(rev[0])]:
1964 1964 raise util.Abort(_('revision %d has unmanaged children')
1965 1965 % rev[0])
1966 1966 lastparent = None
1967 1967
1968 1968 diffopts = self.diffopts({'git': git})
1969 1969 for r in rev:
1970 1970 if not repo[r].mutable():
1971 1971 raise util.Abort(_('revision %d is not mutable') % r,
1972 1972 hint=_('see "hg help phases" for details'))
1973 1973 p1, p2 = repo.changelog.parentrevs(r)
1974 1974 n = repo.changelog.node(r)
1975 1975 if p2 != nullrev:
1976 1976 raise util.Abort(_('cannot import merge revision %d') % r)
1977 1977 if lastparent and lastparent != r:
1978 1978 raise util.Abort(_('revision %d is not the parent of %d')
1979 1979 % (r, lastparent))
1980 1980 lastparent = p1
1981 1981
1982 1982 if not patchname:
1983 1983 patchname = normname('%d.diff' % r)
1984 1984 checkseries(patchname)
1985 1985 self.checkpatchname(patchname, force)
1986 1986 self.fullseries.insert(0, patchname)
1987 1987
1988 1988 patchf = self.opener(patchname, "w")
1989 1989 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1990 1990 patchf.close()
1991 1991
1992 1992 se = statusentry(n, patchname)
1993 1993 self.applied.insert(0, se)
1994 1994
1995 1995 self.added.append(patchname)
1996 1996 imported.append(patchname)
1997 1997 patchname = None
1998 if rev and repo.ui.configbool('mq', 'secret', False):
1999 # if we added anything with --rev, we must move the secret root
2000 phases.retractboundary(repo, phases.secret, [n])
2001 self.parseseries()
2002 self.applieddirty = True
2003 self.seriesdirty = True
1998 if rev and repo.ui.configbool('mq', 'secret', False):
1999 # if we added anything with --rev, move the secret root
2000 phases.retractboundary(repo, phases.secret, [n])
2001 self.parseseries()
2002 self.applieddirty = True
2003 self.seriesdirty = True
2004 2004
2005 2005 for i, filename in enumerate(files):
2006 2006 if existing:
2007 2007 if filename == '-':
2008 2008 raise util.Abort(_('-e is incompatible with import from -'))
2009 2009 filename = normname(filename)
2010 2010 self.checkreservedname(filename)
2011 2011 if util.url(filename).islocal():
2012 2012 originpath = self.join(filename)
2013 2013 if not os.path.isfile(originpath):
2014 2014 raise util.Abort(
2015 2015 _("patch %s does not exist") % filename)
2016 2016
2017 2017 if patchname:
2018 2018 self.checkpatchname(patchname, force)
2019 2019
2020 2020 self.ui.write(_('renaming %s to %s\n')
2021 2021 % (filename, patchname))
2022 2022 util.rename(originpath, self.join(patchname))
2023 2023 else:
2024 2024 patchname = filename
2025 2025
2026 2026 else:
2027 2027 if filename == '-' and not patchname:
2028 2028 raise util.Abort(_('need --name to import a patch from -'))
2029 2029 elif not patchname:
2030 2030 patchname = normname(os.path.basename(filename.rstrip('/')))
2031 2031 self.checkpatchname(patchname, force)
2032 2032 try:
2033 2033 if filename == '-':
2034 2034 text = self.ui.fin.read()
2035 2035 else:
2036 2036 fp = hg.openpath(self.ui, filename)
2037 2037 text = fp.read()
2038 2038 fp.close()
2039 2039 except (OSError, IOError):
2040 2040 raise util.Abort(_("unable to read file %s") % filename)
2041 2041 patchf = self.opener(patchname, "w")
2042 2042 patchf.write(text)
2043 2043 patchf.close()
2044 2044 if not force:
2045 2045 checkseries(patchname)
2046 2046 if patchname not in self.series:
2047 2047 index = self.fullseriesend() + i
2048 2048 self.fullseries[index:index] = [patchname]
2049 2049 self.parseseries()
2050 2050 self.seriesdirty = True
2051 2051 self.ui.warn(_("adding %s to series file\n") % patchname)
2052 2052 self.added.append(patchname)
2053 2053 imported.append(patchname)
2054 2054 patchname = None
2055 2055
2056 2056 self.removeundo(repo)
2057 2057 return imported
2058 2058
2059 2059 def fixkeepchangesopts(ui, opts):
2060 2060 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2061 2061 or opts.get('exact')):
2062 2062 return opts
2063 2063 opts = dict(opts)
2064 2064 opts['keep_changes'] = True
2065 2065 return opts
2066 2066
2067 2067 @command("qdelete|qremove|qrm",
2068 2068 [('k', 'keep', None, _('keep patch file')),
2069 2069 ('r', 'rev', [],
2070 2070 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2071 2071 _('hg qdelete [-k] [PATCH]...'))
2072 2072 def delete(ui, repo, *patches, **opts):
2073 2073 """remove patches from queue
2074 2074
2075 2075 The patches must not be applied, and at least one patch is required. Exact
2076 2076 patch identifiers must be given. With -k/--keep, the patch files are
2077 2077 preserved in the patch directory.
2078 2078
2079 2079 To stop managing a patch and move it into permanent history,
2080 2080 use the :hg:`qfinish` command."""
2081 2081 q = repo.mq
2082 2082 q.delete(repo, patches, opts)
2083 2083 q.savedirty()
2084 2084 return 0
2085 2085
2086 2086 @command("qapplied",
2087 2087 [('1', 'last', None, _('show only the preceding applied patch'))
2088 2088 ] + seriesopts,
2089 2089 _('hg qapplied [-1] [-s] [PATCH]'))
2090 2090 def applied(ui, repo, patch=None, **opts):
2091 2091 """print the patches already applied
2092 2092
2093 2093 Returns 0 on success."""
2094 2094
2095 2095 q = repo.mq
2096 2096
2097 2097 if patch:
2098 2098 if patch not in q.series:
2099 2099 raise util.Abort(_("patch %s is not in series file") % patch)
2100 2100 end = q.series.index(patch) + 1
2101 2101 else:
2102 2102 end = q.seriesend(True)
2103 2103
2104 2104 if opts.get('last') and not end:
2105 2105 ui.write(_("no patches applied\n"))
2106 2106 return 1
2107 2107 elif opts.get('last') and end == 1:
2108 2108 ui.write(_("only one patch applied\n"))
2109 2109 return 1
2110 2110 elif opts.get('last'):
2111 2111 start = end - 2
2112 2112 end = 1
2113 2113 else:
2114 2114 start = 0
2115 2115
2116 2116 q.qseries(repo, length=end, start=start, status='A',
2117 2117 summary=opts.get('summary'))
2118 2118
2119 2119
2120 2120 @command("qunapplied",
2121 2121 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2122 2122 _('hg qunapplied [-1] [-s] [PATCH]'))
2123 2123 def unapplied(ui, repo, patch=None, **opts):
2124 2124 """print the patches not yet applied
2125 2125
2126 2126 Returns 0 on success."""
2127 2127
2128 2128 q = repo.mq
2129 2129 if patch:
2130 2130 if patch not in q.series:
2131 2131 raise util.Abort(_("patch %s is not in series file") % patch)
2132 2132 start = q.series.index(patch) + 1
2133 2133 else:
2134 2134 start = q.seriesend(True)
2135 2135
2136 2136 if start == len(q.series) and opts.get('first'):
2137 2137 ui.write(_("all patches applied\n"))
2138 2138 return 1
2139 2139
2140 2140 length = opts.get('first') and 1 or None
2141 2141 q.qseries(repo, start=start, length=length, status='U',
2142 2142 summary=opts.get('summary'))
2143 2143
2144 2144 @command("qimport",
2145 2145 [('e', 'existing', None, _('import file in patch directory')),
2146 2146 ('n', 'name', '',
2147 2147 _('name of patch file'), _('NAME')),
2148 2148 ('f', 'force', None, _('overwrite existing files')),
2149 2149 ('r', 'rev', [],
2150 2150 _('place existing revisions under mq control'), _('REV')),
2151 2151 ('g', 'git', None, _('use git extended diff format')),
2152 2152 ('P', 'push', None, _('qpush after importing'))],
2153 2153 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2154 2154 def qimport(ui, repo, *filename, **opts):
2155 2155 """import a patch or existing changeset
2156 2156
2157 2157 The patch is inserted into the series after the last applied
2158 2158 patch. If no patches have been applied, qimport prepends the patch
2159 2159 to the series.
2160 2160
2161 2161 The patch will have the same name as its source file unless you
2162 2162 give it a new one with -n/--name.
2163 2163
2164 2164 You can register an existing patch inside the patch directory with
2165 2165 the -e/--existing flag.
2166 2166
2167 2167 With -f/--force, an existing patch of the same name will be
2168 2168 overwritten.
2169 2169
2170 2170 An existing changeset may be placed under mq control with -r/--rev
2171 2171 (e.g. qimport --rev . -n patch will place the current revision
2172 2172 under mq control). With -g/--git, patches imported with --rev will
2173 2173 use the git diff format. See the diffs help topic for information
2174 2174 on why this is important for preserving rename/copy information
2175 2175 and permission changes. Use :hg:`qfinish` to remove changesets
2176 2176 from mq control.
2177 2177
2178 2178 To import a patch from standard input, pass - as the patch file.
2179 2179 When importing from standard input, a patch name must be specified
2180 2180 using the --name flag.
2181 2181
2182 2182 To import an existing patch while renaming it::
2183 2183
2184 2184 hg qimport -e existing-patch -n new-name
2185 2185
2186 2186 Returns 0 if import succeeded.
2187 2187 """
2188 2188 lock = repo.lock() # cause this may move phase
2189 2189 try:
2190 2190 q = repo.mq
2191 2191 try:
2192 2192 imported = q.qimport(
2193 2193 repo, filename, patchname=opts.get('name'),
2194 2194 existing=opts.get('existing'), force=opts.get('force'),
2195 2195 rev=opts.get('rev'), git=opts.get('git'))
2196 2196 finally:
2197 2197 q.savedirty()
2198 2198 finally:
2199 2199 lock.release()
2200 2200
2201 2201 if imported and opts.get('push') and not opts.get('rev'):
2202 2202 return q.push(repo, imported[-1])
2203 2203 return 0
2204 2204
2205 2205 def qinit(ui, repo, create):
2206 2206 """initialize a new queue repository
2207 2207
2208 2208 This command also creates a series file for ordering patches, and
2209 2209 an mq-specific .hgignore file in the queue repository, to exclude
2210 2210 the status and guards files (these contain mostly transient state).
2211 2211
2212 2212 Returns 0 if initialization succeeded."""
2213 2213 q = repo.mq
2214 2214 r = q.init(repo, create)
2215 2215 q.savedirty()
2216 2216 if r:
2217 2217 if not os.path.exists(r.wjoin('.hgignore')):
2218 2218 fp = r.wopener('.hgignore', 'w')
2219 2219 fp.write('^\\.hg\n')
2220 2220 fp.write('^\\.mq\n')
2221 2221 fp.write('syntax: glob\n')
2222 2222 fp.write('status\n')
2223 2223 fp.write('guards\n')
2224 2224 fp.close()
2225 2225 if not os.path.exists(r.wjoin('series')):
2226 2226 r.wopener('series', 'w').close()
2227 2227 r[None].add(['.hgignore', 'series'])
2228 2228 commands.add(ui, r)
2229 2229 return 0
2230 2230
2231 2231 @command("^qinit",
2232 2232 [('c', 'create-repo', None, _('create queue repository'))],
2233 2233 _('hg qinit [-c]'))
2234 2234 def init(ui, repo, **opts):
2235 2235 """init a new queue repository (DEPRECATED)
2236 2236
2237 2237 The queue repository is unversioned by default. If
2238 2238 -c/--create-repo is specified, qinit will create a separate nested
2239 2239 repository for patches (qinit -c may also be run later to convert
2240 2240 an unversioned patch repository into a versioned one). You can use
2241 2241 qcommit to commit changes to this queue repository.
2242 2242
2243 2243 This command is deprecated. Without -c, it's implied by other relevant
2244 2244 commands. With -c, use :hg:`init --mq` instead."""
2245 2245 return qinit(ui, repo, create=opts.get('create_repo'))
2246 2246
2247 2247 @command("qclone",
2248 2248 [('', 'pull', None, _('use pull protocol to copy metadata')),
2249 2249 ('U', 'noupdate', None,
2250 2250 _('do not update the new working directories')),
2251 2251 ('', 'uncompressed', None,
2252 2252 _('use uncompressed transfer (fast over LAN)')),
2253 2253 ('p', 'patches', '',
2254 2254 _('location of source patch repository'), _('REPO')),
2255 2255 ] + commands.remoteopts,
2256 2256 _('hg qclone [OPTION]... SOURCE [DEST]'),
2257 2257 norepo=True)
2258 2258 def clone(ui, source, dest=None, **opts):
2259 2259 '''clone main and patch repository at same time
2260 2260
2261 2261 If source is local, destination will have no patches applied. If
2262 2262 source is remote, this command can not check if patches are
2263 2263 applied in source, so cannot guarantee that patches are not
2264 2264 applied in destination. If you clone remote repository, be sure
2265 2265 before that it has no patches applied.
2266 2266
2267 2267 Source patch repository is looked for in <src>/.hg/patches by
2268 2268 default. Use -p <url> to change.
2269 2269
2270 2270 The patch directory must be a nested Mercurial repository, as
2271 2271 would be created by :hg:`init --mq`.
2272 2272
2273 2273 Return 0 on success.
2274 2274 '''
2275 2275 def patchdir(repo):
2276 2276 """compute a patch repo url from a repo object"""
2277 2277 url = repo.url()
2278 2278 if url.endswith('/'):
2279 2279 url = url[:-1]
2280 2280 return url + '/.hg/patches'
2281 2281
2282 2282 # main repo (destination and sources)
2283 2283 if dest is None:
2284 2284 dest = hg.defaultdest(source)
2285 2285 sr = hg.peer(ui, opts, ui.expandpath(source))
2286 2286
2287 2287 # patches repo (source only)
2288 2288 if opts.get('patches'):
2289 2289 patchespath = ui.expandpath(opts.get('patches'))
2290 2290 else:
2291 2291 patchespath = patchdir(sr)
2292 2292 try:
2293 2293 hg.peer(ui, opts, patchespath)
2294 2294 except error.RepoError:
2295 2295 raise util.Abort(_('versioned patch repository not found'
2296 2296 ' (see init --mq)'))
2297 2297 qbase, destrev = None, None
2298 2298 if sr.local():
2299 2299 repo = sr.local()
2300 2300 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2301 2301 qbase = repo.mq.applied[0].node
2302 2302 if not hg.islocal(dest):
2303 2303 heads = set(repo.heads())
2304 2304 destrev = list(heads.difference(repo.heads(qbase)))
2305 2305 destrev.append(repo.changelog.parents(qbase)[0])
2306 2306 elif sr.capable('lookup'):
2307 2307 try:
2308 2308 qbase = sr.lookup('qbase')
2309 2309 except error.RepoError:
2310 2310 pass
2311 2311
2312 2312 ui.note(_('cloning main repository\n'))
2313 2313 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2314 2314 pull=opts.get('pull'),
2315 2315 rev=destrev,
2316 2316 update=False,
2317 2317 stream=opts.get('uncompressed'))
2318 2318
2319 2319 ui.note(_('cloning patch repository\n'))
2320 2320 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2321 2321 pull=opts.get('pull'), update=not opts.get('noupdate'),
2322 2322 stream=opts.get('uncompressed'))
2323 2323
2324 2324 if dr.local():
2325 2325 repo = dr.local()
2326 2326 if qbase:
2327 2327 ui.note(_('stripping applied patches from destination '
2328 2328 'repository\n'))
2329 2329 strip(ui, repo, [qbase], update=False, backup=None)
2330 2330 if not opts.get('noupdate'):
2331 2331 ui.note(_('updating destination repository\n'))
2332 2332 hg.update(repo, repo.changelog.tip())
2333 2333
2334 2334 @command("qcommit|qci",
2335 2335 commands.table["^commit|ci"][1],
2336 2336 _('hg qcommit [OPTION]... [FILE]...'),
2337 2337 inferrepo=True)
2338 2338 def commit(ui, repo, *pats, **opts):
2339 2339 """commit changes in the queue repository (DEPRECATED)
2340 2340
2341 2341 This command is deprecated; use :hg:`commit --mq` instead."""
2342 2342 q = repo.mq
2343 2343 r = q.qrepo()
2344 2344 if not r:
2345 2345 raise util.Abort('no queue repository')
2346 2346 commands.commit(r.ui, r, *pats, **opts)
2347 2347
2348 2348 @command("qseries",
2349 2349 [('m', 'missing', None, _('print patches not in series')),
2350 2350 ] + seriesopts,
2351 2351 _('hg qseries [-ms]'))
2352 2352 def series(ui, repo, **opts):
2353 2353 """print the entire series file
2354 2354
2355 2355 Returns 0 on success."""
2356 2356 repo.mq.qseries(repo, missing=opts.get('missing'),
2357 2357 summary=opts.get('summary'))
2358 2358 return 0
2359 2359
2360 2360 @command("qtop", seriesopts, _('hg qtop [-s]'))
2361 2361 def top(ui, repo, **opts):
2362 2362 """print the name of the current patch
2363 2363
2364 2364 Returns 0 on success."""
2365 2365 q = repo.mq
2366 2366 t = q.applied and q.seriesend(True) or 0
2367 2367 if t:
2368 2368 q.qseries(repo, start=t - 1, length=1, status='A',
2369 2369 summary=opts.get('summary'))
2370 2370 else:
2371 2371 ui.write(_("no patches applied\n"))
2372 2372 return 1
2373 2373
2374 2374 @command("qnext", seriesopts, _('hg qnext [-s]'))
2375 2375 def next(ui, repo, **opts):
2376 2376 """print the name of the next pushable patch
2377 2377
2378 2378 Returns 0 on success."""
2379 2379 q = repo.mq
2380 2380 end = q.seriesend()
2381 2381 if end == len(q.series):
2382 2382 ui.write(_("all patches applied\n"))
2383 2383 return 1
2384 2384 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2385 2385
2386 2386 @command("qprev", seriesopts, _('hg qprev [-s]'))
2387 2387 def prev(ui, repo, **opts):
2388 2388 """print the name of the preceding applied patch
2389 2389
2390 2390 Returns 0 on success."""
2391 2391 q = repo.mq
2392 2392 l = len(q.applied)
2393 2393 if l == 1:
2394 2394 ui.write(_("only one patch applied\n"))
2395 2395 return 1
2396 2396 if not l:
2397 2397 ui.write(_("no patches applied\n"))
2398 2398 return 1
2399 2399 idx = q.series.index(q.applied[-2].name)
2400 2400 q.qseries(repo, start=idx, length=1, status='A',
2401 2401 summary=opts.get('summary'))
2402 2402
2403 2403 def setupheaderopts(ui, opts):
2404 2404 if not opts.get('user') and opts.get('currentuser'):
2405 2405 opts['user'] = ui.username()
2406 2406 if not opts.get('date') and opts.get('currentdate'):
2407 2407 opts['date'] = "%d %d" % util.makedate()
2408 2408
2409 2409 @command("^qnew",
2410 [('e', 'edit', None, _('edit commit message')),
2410 [('e', 'edit', None, _('invoke editor on commit messages')),
2411 2411 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2412 2412 ('g', 'git', None, _('use git extended diff format')),
2413 2413 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2414 2414 ('u', 'user', '',
2415 2415 _('add "From: <USER>" to patch'), _('USER')),
2416 2416 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2417 2417 ('d', 'date', '',
2418 2418 _('add "Date: <DATE>" to patch'), _('DATE'))
2419 2419 ] + commands.walkopts + commands.commitopts,
2420 2420 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'),
2421 2421 inferrepo=True)
2422 2422 def new(ui, repo, patch, *args, **opts):
2423 2423 """create a new patch
2424 2424
2425 2425 qnew creates a new patch on top of the currently-applied patch (if
2426 2426 any). The patch will be initialized with any outstanding changes
2427 2427 in the working directory. You may also use -I/--include,
2428 2428 -X/--exclude, and/or a list of files after the patch name to add
2429 2429 only changes to matching files to the new patch, leaving the rest
2430 2430 as uncommitted modifications.
2431 2431
2432 2432 -u/--user and -d/--date can be used to set the (given) user and
2433 2433 date, respectively. -U/--currentuser and -D/--currentdate set user
2434 2434 to current user and date to current date.
2435 2435
2436 2436 -e/--edit, -m/--message or -l/--logfile set the patch header as
2437 2437 well as the commit message. If none is specified, the header is
2438 2438 empty and the commit message is '[mq]: PATCH'.
2439 2439
2440 2440 Use the -g/--git option to keep the patch in the git extended diff
2441 2441 format. Read the diffs help topic for more information on why this
2442 2442 is important for preserving permission changes and copy/rename
2443 2443 information.
2444 2444
2445 2445 Returns 0 on successful creation of a new patch.
2446 2446 """
2447 2447 msg = cmdutil.logmessage(ui, opts)
2448 2448 q = repo.mq
2449 2449 opts['msg'] = msg
2450 2450 setupheaderopts(ui, opts)
2451 2451 q.new(repo, patch, *args, **opts)
2452 2452 q.savedirty()
2453 2453 return 0
2454 2454
2455 2455 @command("^qrefresh",
2456 [('e', 'edit', None, _('edit commit message')),
2456 [('e', 'edit', None, _('invoke editor on commit messages')),
2457 2457 ('g', 'git', None, _('use git extended diff format')),
2458 2458 ('s', 'short', None,
2459 2459 _('refresh only files already in the patch and specified files')),
2460 2460 ('U', 'currentuser', None,
2461 2461 _('add/update author field in patch with current user')),
2462 2462 ('u', 'user', '',
2463 2463 _('add/update author field in patch with given user'), _('USER')),
2464 2464 ('D', 'currentdate', None,
2465 2465 _('add/update date field in patch with current date')),
2466 2466 ('d', 'date', '',
2467 2467 _('add/update date field in patch with given date'), _('DATE'))
2468 2468 ] + commands.walkopts + commands.commitopts,
2469 2469 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'),
2470 2470 inferrepo=True)
2471 2471 def refresh(ui, repo, *pats, **opts):
2472 2472 """update the current patch
2473 2473
2474 2474 If any file patterns are provided, the refreshed patch will
2475 2475 contain only the modifications that match those patterns; the
2476 2476 remaining modifications will remain in the working directory.
2477 2477
2478 2478 If -s/--short is specified, files currently included in the patch
2479 2479 will be refreshed just like matched files and remain in the patch.
2480 2480
2481 2481 If -e/--edit is specified, Mercurial will start your configured editor for
2482 2482 you to enter a message. In case qrefresh fails, you will find a backup of
2483 2483 your message in ``.hg/last-message.txt``.
2484 2484
2485 2485 hg add/remove/copy/rename work as usual, though you might want to
2486 2486 use git-style patches (-g/--git or [diff] git=1) to track copies
2487 2487 and renames. See the diffs help topic for more information on the
2488 2488 git diff format.
2489 2489
2490 2490 Returns 0 on success.
2491 2491 """
2492 2492 q = repo.mq
2493 2493 message = cmdutil.logmessage(ui, opts)
2494 2494 setupheaderopts(ui, opts)
2495 2495 wlock = repo.wlock()
2496 2496 try:
2497 2497 ret = q.refresh(repo, pats, msg=message, **opts)
2498 2498 q.savedirty()
2499 2499 return ret
2500 2500 finally:
2501 2501 wlock.release()
2502 2502
2503 2503 @command("^qdiff",
2504 2504 commands.diffopts + commands.diffopts2 + commands.walkopts,
2505 2505 _('hg qdiff [OPTION]... [FILE]...'),
2506 2506 inferrepo=True)
2507 2507 def diff(ui, repo, *pats, **opts):
2508 2508 """diff of the current patch and subsequent modifications
2509 2509
2510 2510 Shows a diff which includes the current patch as well as any
2511 2511 changes which have been made in the working directory since the
2512 2512 last refresh (thus showing what the current patch would become
2513 2513 after a qrefresh).
2514 2514
2515 2515 Use :hg:`diff` if you only want to see the changes made since the
2516 2516 last qrefresh, or :hg:`export qtip` if you want to see changes
2517 2517 made by the current patch without including changes made since the
2518 2518 qrefresh.
2519 2519
2520 2520 Returns 0 on success.
2521 2521 """
2522 2522 repo.mq.diff(repo, pats, opts)
2523 2523 return 0
2524 2524
2525 2525 @command('qfold',
2526 [('e', 'edit', None, _('edit patch header')),
2526 [('e', 'edit', None, _('invoke editor on commit messages')),
2527 2527 ('k', 'keep', None, _('keep folded patch files')),
2528 2528 ] + commands.commitopts,
2529 2529 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2530 2530 def fold(ui, repo, *files, **opts):
2531 2531 """fold the named patches into the current patch
2532 2532
2533 2533 Patches must not yet be applied. Each patch will be successively
2534 2534 applied to the current patch in the order given. If all the
2535 2535 patches apply successfully, the current patch will be refreshed
2536 2536 with the new cumulative patch, and the folded patches will be
2537 2537 deleted. With -k/--keep, the folded patch files will not be
2538 2538 removed afterwards.
2539 2539
2540 2540 The header for each folded patch will be concatenated with the
2541 2541 current patch header, separated by a line of ``* * *``.
2542 2542
2543 2543 Returns 0 on success."""
2544 2544 q = repo.mq
2545 2545 if not files:
2546 2546 raise util.Abort(_('qfold requires at least one patch name'))
2547 2547 if not q.checktoppatch(repo)[0]:
2548 2548 raise util.Abort(_('no patches applied'))
2549 2549 q.checklocalchanges(repo)
2550 2550
2551 2551 message = cmdutil.logmessage(ui, opts)
2552 2552
2553 2553 parent = q.lookup('qtip')
2554 2554 patches = []
2555 2555 messages = []
2556 2556 for f in files:
2557 2557 p = q.lookup(f)
2558 2558 if p in patches or p == parent:
2559 2559 ui.warn(_('skipping already folded patch %s\n') % p)
2560 2560 if q.isapplied(p):
2561 2561 raise util.Abort(_('qfold cannot fold already applied patch %s')
2562 2562 % p)
2563 2563 patches.append(p)
2564 2564
2565 2565 for p in patches:
2566 2566 if not message:
2567 2567 ph = patchheader(q.join(p), q.plainmode)
2568 2568 if ph.message:
2569 2569 messages.append(ph.message)
2570 2570 pf = q.join(p)
2571 2571 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2572 2572 if not patchsuccess:
2573 2573 raise util.Abort(_('error folding patch %s') % p)
2574 2574
2575 2575 if not message:
2576 2576 ph = patchheader(q.join(parent), q.plainmode)
2577 2577 message = ph.message
2578 2578 for msg in messages:
2579 2579 if msg:
2580 2580 if message:
2581 2581 message.append('* * *')
2582 2582 message.extend(msg)
2583 2583 message = '\n'.join(message)
2584 2584
2585 2585 diffopts = q.patchopts(q.diffopts(), *patches)
2586 2586 wlock = repo.wlock()
2587 2587 try:
2588 2588 q.refresh(repo, msg=message, git=diffopts.git, edit=opts.get('edit'))
2589 2589 q.delete(repo, patches, opts)
2590 2590 q.savedirty()
2591 2591 finally:
2592 2592 wlock.release()
2593 2593
2594 2594 @command("qgoto",
2595 2595 [('', 'keep-changes', None,
2596 2596 _('tolerate non-conflicting local changes')),
2597 2597 ('f', 'force', None, _('overwrite any local changes')),
2598 2598 ('', 'no-backup', None, _('do not save backup copies of files'))],
2599 2599 _('hg qgoto [OPTION]... PATCH'))
2600 2600 def goto(ui, repo, patch, **opts):
2601 2601 '''push or pop patches until named patch is at top of stack
2602 2602
2603 2603 Returns 0 on success.'''
2604 2604 opts = fixkeepchangesopts(ui, opts)
2605 2605 q = repo.mq
2606 2606 patch = q.lookup(patch)
2607 2607 nobackup = opts.get('no_backup')
2608 2608 keepchanges = opts.get('keep_changes')
2609 2609 if q.isapplied(patch):
2610 2610 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2611 2611 keepchanges=keepchanges)
2612 2612 else:
2613 2613 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2614 2614 keepchanges=keepchanges)
2615 2615 q.savedirty()
2616 2616 return ret
2617 2617
2618 2618 @command("qguard",
2619 2619 [('l', 'list', None, _('list all patches and guards')),
2620 2620 ('n', 'none', None, _('drop all guards'))],
2621 2621 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2622 2622 def guard(ui, repo, *args, **opts):
2623 2623 '''set or print guards for a patch
2624 2624
2625 2625 Guards control whether a patch can be pushed. A patch with no
2626 2626 guards is always pushed. A patch with a positive guard ("+foo") is
2627 2627 pushed only if the :hg:`qselect` command has activated it. A patch with
2628 2628 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2629 2629 has activated it.
2630 2630
2631 2631 With no arguments, print the currently active guards.
2632 2632 With arguments, set guards for the named patch.
2633 2633
2634 2634 .. note::
2635 2635
2636 2636 Specifying negative guards now requires '--'.
2637 2637
2638 2638 To set guards on another patch::
2639 2639
2640 2640 hg qguard other.patch -- +2.6.17 -stable
2641 2641
2642 2642 Returns 0 on success.
2643 2643 '''
2644 2644 def status(idx):
2645 2645 guards = q.seriesguards[idx] or ['unguarded']
2646 2646 if q.series[idx] in applied:
2647 2647 state = 'applied'
2648 2648 elif q.pushable(idx)[0]:
2649 2649 state = 'unapplied'
2650 2650 else:
2651 2651 state = 'guarded'
2652 2652 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2653 2653 ui.write('%s: ' % ui.label(q.series[idx], label))
2654 2654
2655 2655 for i, guard in enumerate(guards):
2656 2656 if guard.startswith('+'):
2657 2657 ui.write(guard, label='qguard.positive')
2658 2658 elif guard.startswith('-'):
2659 2659 ui.write(guard, label='qguard.negative')
2660 2660 else:
2661 2661 ui.write(guard, label='qguard.unguarded')
2662 2662 if i != len(guards) - 1:
2663 2663 ui.write(' ')
2664 2664 ui.write('\n')
2665 2665 q = repo.mq
2666 2666 applied = set(p.name for p in q.applied)
2667 2667 patch = None
2668 2668 args = list(args)
2669 2669 if opts.get('list'):
2670 2670 if args or opts.get('none'):
2671 2671 raise util.Abort(_('cannot mix -l/--list with options or '
2672 2672 'arguments'))
2673 2673 for i in xrange(len(q.series)):
2674 2674 status(i)
2675 2675 return
2676 2676 if not args or args[0][0:1] in '-+':
2677 2677 if not q.applied:
2678 2678 raise util.Abort(_('no patches applied'))
2679 2679 patch = q.applied[-1].name
2680 2680 if patch is None and args[0][0:1] not in '-+':
2681 2681 patch = args.pop(0)
2682 2682 if patch is None:
2683 2683 raise util.Abort(_('no patch to work with'))
2684 2684 if args or opts.get('none'):
2685 2685 idx = q.findseries(patch)
2686 2686 if idx is None:
2687 2687 raise util.Abort(_('no patch named %s') % patch)
2688 2688 q.setguards(idx, args)
2689 2689 q.savedirty()
2690 2690 else:
2691 2691 status(q.series.index(q.lookup(patch)))
2692 2692
2693 2693 @command("qheader", [], _('hg qheader [PATCH]'))
2694 2694 def header(ui, repo, patch=None):
2695 2695 """print the header of the topmost or specified patch
2696 2696
2697 2697 Returns 0 on success."""
2698 2698 q = repo.mq
2699 2699
2700 2700 if patch:
2701 2701 patch = q.lookup(patch)
2702 2702 else:
2703 2703 if not q.applied:
2704 2704 ui.write(_('no patches applied\n'))
2705 2705 return 1
2706 2706 patch = q.lookup('qtip')
2707 2707 ph = patchheader(q.join(patch), q.plainmode)
2708 2708
2709 2709 ui.write('\n'.join(ph.message) + '\n')
2710 2710
2711 2711 def lastsavename(path):
2712 2712 (directory, base) = os.path.split(path)
2713 2713 names = os.listdir(directory)
2714 2714 namere = re.compile("%s.([0-9]+)" % base)
2715 2715 maxindex = None
2716 2716 maxname = None
2717 2717 for f in names:
2718 2718 m = namere.match(f)
2719 2719 if m:
2720 2720 index = int(m.group(1))
2721 2721 if maxindex is None or index > maxindex:
2722 2722 maxindex = index
2723 2723 maxname = f
2724 2724 if maxname:
2725 2725 return (os.path.join(directory, maxname), maxindex)
2726 2726 return (None, None)
2727 2727
2728 2728 def savename(path):
2729 2729 (last, index) = lastsavename(path)
2730 2730 if last is None:
2731 2731 index = 0
2732 2732 newpath = path + ".%d" % (index + 1)
2733 2733 return newpath
2734 2734
2735 2735 @command("^qpush",
2736 2736 [('', 'keep-changes', None,
2737 2737 _('tolerate non-conflicting local changes')),
2738 2738 ('f', 'force', None, _('apply on top of local changes')),
2739 2739 ('e', 'exact', None,
2740 2740 _('apply the target patch to its recorded parent')),
2741 2741 ('l', 'list', None, _('list patch name in commit text')),
2742 2742 ('a', 'all', None, _('apply all patches')),
2743 2743 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2744 2744 ('n', 'name', '',
2745 2745 _('merge queue name (DEPRECATED)'), _('NAME')),
2746 2746 ('', 'move', None,
2747 2747 _('reorder patch series and apply only the patch')),
2748 2748 ('', 'no-backup', None, _('do not save backup copies of files'))],
2749 2749 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2750 2750 def push(ui, repo, patch=None, **opts):
2751 2751 """push the next patch onto the stack
2752 2752
2753 2753 By default, abort if the working directory contains uncommitted
2754 2754 changes. With --keep-changes, abort only if the uncommitted files
2755 2755 overlap with patched files. With -f/--force, backup and patch over
2756 2756 uncommitted changes.
2757 2757
2758 2758 Return 0 on success.
2759 2759 """
2760 2760 q = repo.mq
2761 2761 mergeq = None
2762 2762
2763 2763 opts = fixkeepchangesopts(ui, opts)
2764 2764 if opts.get('merge'):
2765 2765 if opts.get('name'):
2766 2766 newpath = repo.join(opts.get('name'))
2767 2767 else:
2768 2768 newpath, i = lastsavename(q.path)
2769 2769 if not newpath:
2770 2770 ui.warn(_("no saved queues found, please use -n\n"))
2771 2771 return 1
2772 2772 mergeq = queue(ui, repo.baseui, repo.path, newpath)
2773 2773 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2774 2774 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2775 2775 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2776 2776 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2777 2777 keepchanges=opts.get('keep_changes'))
2778 2778 return ret
2779 2779
2780 2780 @command("^qpop",
2781 2781 [('a', 'all', None, _('pop all patches')),
2782 2782 ('n', 'name', '',
2783 2783 _('queue name to pop (DEPRECATED)'), _('NAME')),
2784 2784 ('', 'keep-changes', None,
2785 2785 _('tolerate non-conflicting local changes')),
2786 2786 ('f', 'force', None, _('forget any local changes to patched files')),
2787 2787 ('', 'no-backup', None, _('do not save backup copies of files'))],
2788 2788 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2789 2789 def pop(ui, repo, patch=None, **opts):
2790 2790 """pop the current patch off the stack
2791 2791
2792 2792 Without argument, pops off the top of the patch stack. If given a
2793 2793 patch name, keeps popping off patches until the named patch is at
2794 2794 the top of the stack.
2795 2795
2796 2796 By default, abort if the working directory contains uncommitted
2797 2797 changes. With --keep-changes, abort only if the uncommitted files
2798 2798 overlap with patched files. With -f/--force, backup and discard
2799 2799 changes made to such files.
2800 2800
2801 2801 Return 0 on success.
2802 2802 """
2803 2803 opts = fixkeepchangesopts(ui, opts)
2804 2804 localupdate = True
2805 2805 if opts.get('name'):
2806 2806 q = queue(ui, repo.baseui, repo.path, repo.join(opts.get('name')))
2807 2807 ui.warn(_('using patch queue: %s\n') % q.path)
2808 2808 localupdate = False
2809 2809 else:
2810 2810 q = repo.mq
2811 2811 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2812 2812 all=opts.get('all'), nobackup=opts.get('no_backup'),
2813 2813 keepchanges=opts.get('keep_changes'))
2814 2814 q.savedirty()
2815 2815 return ret
2816 2816
2817 2817 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2818 2818 def rename(ui, repo, patch, name=None, **opts):
2819 2819 """rename a patch
2820 2820
2821 2821 With one argument, renames the current patch to PATCH1.
2822 2822 With two arguments, renames PATCH1 to PATCH2.
2823 2823
2824 2824 Returns 0 on success."""
2825 2825 q = repo.mq
2826 2826 if not name:
2827 2827 name = patch
2828 2828 patch = None
2829 2829
2830 2830 if patch:
2831 2831 patch = q.lookup(patch)
2832 2832 else:
2833 2833 if not q.applied:
2834 2834 ui.write(_('no patches applied\n'))
2835 2835 return
2836 2836 patch = q.lookup('qtip')
2837 2837 absdest = q.join(name)
2838 2838 if os.path.isdir(absdest):
2839 2839 name = normname(os.path.join(name, os.path.basename(patch)))
2840 2840 absdest = q.join(name)
2841 2841 q.checkpatchname(name)
2842 2842
2843 2843 ui.note(_('renaming %s to %s\n') % (patch, name))
2844 2844 i = q.findseries(patch)
2845 2845 guards = q.guard_re.findall(q.fullseries[i])
2846 2846 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2847 2847 q.parseseries()
2848 2848 q.seriesdirty = True
2849 2849
2850 2850 info = q.isapplied(patch)
2851 2851 if info:
2852 2852 q.applied[info[0]] = statusentry(info[1], name)
2853 2853 q.applieddirty = True
2854 2854
2855 2855 destdir = os.path.dirname(absdest)
2856 2856 if not os.path.isdir(destdir):
2857 2857 os.makedirs(destdir)
2858 2858 util.rename(q.join(patch), absdest)
2859 2859 r = q.qrepo()
2860 2860 if r and patch in r.dirstate:
2861 2861 wctx = r[None]
2862 2862 wlock = r.wlock()
2863 2863 try:
2864 2864 if r.dirstate[patch] == 'a':
2865 2865 r.dirstate.drop(patch)
2866 2866 r.dirstate.add(name)
2867 2867 else:
2868 2868 wctx.copy(patch, name)
2869 2869 wctx.forget([patch])
2870 2870 finally:
2871 2871 wlock.release()
2872 2872
2873 2873 q.savedirty()
2874 2874
2875 2875 @command("qrestore",
2876 2876 [('d', 'delete', None, _('delete save entry')),
2877 2877 ('u', 'update', None, _('update queue working directory'))],
2878 2878 _('hg qrestore [-d] [-u] REV'))
2879 2879 def restore(ui, repo, rev, **opts):
2880 2880 """restore the queue state saved by a revision (DEPRECATED)
2881 2881
2882 2882 This command is deprecated, use :hg:`rebase` instead."""
2883 2883 rev = repo.lookup(rev)
2884 2884 q = repo.mq
2885 2885 q.restore(repo, rev, delete=opts.get('delete'),
2886 2886 qupdate=opts.get('update'))
2887 2887 q.savedirty()
2888 2888 return 0
2889 2889
2890 2890 @command("qsave",
2891 2891 [('c', 'copy', None, _('copy patch directory')),
2892 2892 ('n', 'name', '',
2893 2893 _('copy directory name'), _('NAME')),
2894 2894 ('e', 'empty', None, _('clear queue status file')),
2895 2895 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2896 2896 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2897 2897 def save(ui, repo, **opts):
2898 2898 """save current queue state (DEPRECATED)
2899 2899
2900 2900 This command is deprecated, use :hg:`rebase` instead."""
2901 2901 q = repo.mq
2902 2902 message = cmdutil.logmessage(ui, opts)
2903 2903 ret = q.save(repo, msg=message)
2904 2904 if ret:
2905 2905 return ret
2906 2906 q.savedirty() # save to .hg/patches before copying
2907 2907 if opts.get('copy'):
2908 2908 path = q.path
2909 2909 if opts.get('name'):
2910 2910 newpath = os.path.join(q.basepath, opts.get('name'))
2911 2911 if os.path.exists(newpath):
2912 2912 if not os.path.isdir(newpath):
2913 2913 raise util.Abort(_('destination %s exists and is not '
2914 2914 'a directory') % newpath)
2915 2915 if not opts.get('force'):
2916 2916 raise util.Abort(_('destination %s exists, '
2917 2917 'use -f to force') % newpath)
2918 2918 else:
2919 2919 newpath = savename(path)
2920 2920 ui.warn(_("copy %s to %s\n") % (path, newpath))
2921 2921 util.copyfiles(path, newpath)
2922 2922 if opts.get('empty'):
2923 2923 del q.applied[:]
2924 2924 q.applieddirty = True
2925 2925 q.savedirty()
2926 2926 return 0
2927 2927
2928 2928
2929 2929 @command("qselect",
2930 2930 [('n', 'none', None, _('disable all guards')),
2931 2931 ('s', 'series', None, _('list all guards in series file')),
2932 2932 ('', 'pop', None, _('pop to before first guarded applied patch')),
2933 2933 ('', 'reapply', None, _('pop, then reapply patches'))],
2934 2934 _('hg qselect [OPTION]... [GUARD]...'))
2935 2935 def select(ui, repo, *args, **opts):
2936 2936 '''set or print guarded patches to push
2937 2937
2938 2938 Use the :hg:`qguard` command to set or print guards on patch, then use
2939 2939 qselect to tell mq which guards to use. A patch will be pushed if
2940 2940 it has no guards or any positive guards match the currently
2941 2941 selected guard, but will not be pushed if any negative guards
2942 2942 match the current guard. For example::
2943 2943
2944 2944 qguard foo.patch -- -stable (negative guard)
2945 2945 qguard bar.patch +stable (positive guard)
2946 2946 qselect stable
2947 2947
2948 2948 This activates the "stable" guard. mq will skip foo.patch (because
2949 2949 it has a negative match) but push bar.patch (because it has a
2950 2950 positive match).
2951 2951
2952 2952 With no arguments, prints the currently active guards.
2953 2953 With one argument, sets the active guard.
2954 2954
2955 2955 Use -n/--none to deactivate guards (no other arguments needed).
2956 2956 When no guards are active, patches with positive guards are
2957 2957 skipped and patches with negative guards are pushed.
2958 2958
2959 2959 qselect can change the guards on applied patches. It does not pop
2960 2960 guarded patches by default. Use --pop to pop back to the last
2961 2961 applied patch that is not guarded. Use --reapply (which implies
2962 2962 --pop) to push back to the current patch afterwards, but skip
2963 2963 guarded patches.
2964 2964
2965 2965 Use -s/--series to print a list of all guards in the series file
2966 2966 (no other arguments needed). Use -v for more information.
2967 2967
2968 2968 Returns 0 on success.'''
2969 2969
2970 2970 q = repo.mq
2971 2971 guards = q.active()
2972 2972 if args or opts.get('none'):
2973 2973 old_unapplied = q.unapplied(repo)
2974 2974 old_guarded = [i for i in xrange(len(q.applied)) if
2975 2975 not q.pushable(i)[0]]
2976 2976 q.setactive(args)
2977 2977 q.savedirty()
2978 2978 if not args:
2979 2979 ui.status(_('guards deactivated\n'))
2980 2980 if not opts.get('pop') and not opts.get('reapply'):
2981 2981 unapplied = q.unapplied(repo)
2982 2982 guarded = [i for i in xrange(len(q.applied))
2983 2983 if not q.pushable(i)[0]]
2984 2984 if len(unapplied) != len(old_unapplied):
2985 2985 ui.status(_('number of unguarded, unapplied patches has '
2986 2986 'changed from %d to %d\n') %
2987 2987 (len(old_unapplied), len(unapplied)))
2988 2988 if len(guarded) != len(old_guarded):
2989 2989 ui.status(_('number of guarded, applied patches has changed '
2990 2990 'from %d to %d\n') %
2991 2991 (len(old_guarded), len(guarded)))
2992 2992 elif opts.get('series'):
2993 2993 guards = {}
2994 2994 noguards = 0
2995 2995 for gs in q.seriesguards:
2996 2996 if not gs:
2997 2997 noguards += 1
2998 2998 for g in gs:
2999 2999 guards.setdefault(g, 0)
3000 3000 guards[g] += 1
3001 3001 if ui.verbose:
3002 3002 guards['NONE'] = noguards
3003 3003 guards = guards.items()
3004 3004 guards.sort(key=lambda x: x[0][1:])
3005 3005 if guards:
3006 3006 ui.note(_('guards in series file:\n'))
3007 3007 for guard, count in guards:
3008 3008 ui.note('%2d ' % count)
3009 3009 ui.write(guard, '\n')
3010 3010 else:
3011 3011 ui.note(_('no guards in series file\n'))
3012 3012 else:
3013 3013 if guards:
3014 3014 ui.note(_('active guards:\n'))
3015 3015 for g in guards:
3016 3016 ui.write(g, '\n')
3017 3017 else:
3018 3018 ui.write(_('no active guards\n'))
3019 3019 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
3020 3020 popped = False
3021 3021 if opts.get('pop') or opts.get('reapply'):
3022 3022 for i in xrange(len(q.applied)):
3023 3023 pushable, reason = q.pushable(i)
3024 3024 if not pushable:
3025 3025 ui.status(_('popping guarded patches\n'))
3026 3026 popped = True
3027 3027 if i == 0:
3028 3028 q.pop(repo, all=True)
3029 3029 else:
3030 3030 q.pop(repo, str(i - 1))
3031 3031 break
3032 3032 if popped:
3033 3033 try:
3034 3034 if reapply:
3035 3035 ui.status(_('reapplying unguarded patches\n'))
3036 3036 q.push(repo, reapply)
3037 3037 finally:
3038 3038 q.savedirty()
3039 3039
3040 3040 @command("qfinish",
3041 3041 [('a', 'applied', None, _('finish all applied changesets'))],
3042 3042 _('hg qfinish [-a] [REV]...'))
3043 3043 def finish(ui, repo, *revrange, **opts):
3044 3044 """move applied patches into repository history
3045 3045
3046 3046 Finishes the specified revisions (corresponding to applied
3047 3047 patches) by moving them out of mq control into regular repository
3048 3048 history.
3049 3049
3050 3050 Accepts a revision range or the -a/--applied option. If --applied
3051 3051 is specified, all applied mq revisions are removed from mq
3052 3052 control. Otherwise, the given revisions must be at the base of the
3053 3053 stack of applied patches.
3054 3054
3055 3055 This can be especially useful if your changes have been applied to
3056 3056 an upstream repository, or if you are about to push your changes
3057 3057 to upstream.
3058 3058
3059 3059 Returns 0 on success.
3060 3060 """
3061 3061 if not opts.get('applied') and not revrange:
3062 3062 raise util.Abort(_('no revisions specified'))
3063 3063 elif opts.get('applied'):
3064 3064 revrange = ('qbase::qtip',) + revrange
3065 3065
3066 3066 q = repo.mq
3067 3067 if not q.applied:
3068 3068 ui.status(_('no patches applied\n'))
3069 3069 return 0
3070 3070
3071 3071 revs = scmutil.revrange(repo, revrange)
3072 3072 if repo['.'].rev() in revs and repo[None].files():
3073 3073 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3074 3074 # queue.finish may changes phases but leave the responsibility to lock the
3075 3075 # repo to the caller to avoid deadlock with wlock. This command code is
3076 3076 # responsibility for this locking.
3077 3077 lock = repo.lock()
3078 3078 try:
3079 3079 q.finish(repo, revs)
3080 3080 q.savedirty()
3081 3081 finally:
3082 3082 lock.release()
3083 3083 return 0
3084 3084
3085 3085 @command("qqueue",
3086 3086 [('l', 'list', False, _('list all available queues')),
3087 3087 ('', 'active', False, _('print name of active queue')),
3088 3088 ('c', 'create', False, _('create new queue')),
3089 3089 ('', 'rename', False, _('rename active queue')),
3090 3090 ('', 'delete', False, _('delete reference to queue')),
3091 3091 ('', 'purge', False, _('delete queue, and remove patch dir')),
3092 3092 ],
3093 3093 _('[OPTION] [QUEUE]'))
3094 3094 def qqueue(ui, repo, name=None, **opts):
3095 3095 '''manage multiple patch queues
3096 3096
3097 3097 Supports switching between different patch queues, as well as creating
3098 3098 new patch queues and deleting existing ones.
3099 3099
3100 3100 Omitting a queue name or specifying -l/--list will show you the registered
3101 3101 queues - by default the "normal" patches queue is registered. The currently
3102 3102 active queue will be marked with "(active)". Specifying --active will print
3103 3103 only the name of the active queue.
3104 3104
3105 3105 To create a new queue, use -c/--create. The queue is automatically made
3106 3106 active, except in the case where there are applied patches from the
3107 3107 currently active queue in the repository. Then the queue will only be
3108 3108 created and switching will fail.
3109 3109
3110 3110 To delete an existing queue, use --delete. You cannot delete the currently
3111 3111 active queue.
3112 3112
3113 3113 Returns 0 on success.
3114 3114 '''
3115 3115 q = repo.mq
3116 3116 _defaultqueue = 'patches'
3117 3117 _allqueues = 'patches.queues'
3118 3118 _activequeue = 'patches.queue'
3119 3119
3120 3120 def _getcurrent():
3121 3121 cur = os.path.basename(q.path)
3122 3122 if cur.startswith('patches-'):
3123 3123 cur = cur[8:]
3124 3124 return cur
3125 3125
3126 3126 def _noqueues():
3127 3127 try:
3128 3128 fh = repo.opener(_allqueues, 'r')
3129 3129 fh.close()
3130 3130 except IOError:
3131 3131 return True
3132 3132
3133 3133 return False
3134 3134
3135 3135 def _getqueues():
3136 3136 current = _getcurrent()
3137 3137
3138 3138 try:
3139 3139 fh = repo.opener(_allqueues, 'r')
3140 3140 queues = [queue.strip() for queue in fh if queue.strip()]
3141 3141 fh.close()
3142 3142 if current not in queues:
3143 3143 queues.append(current)
3144 3144 except IOError:
3145 3145 queues = [_defaultqueue]
3146 3146
3147 3147 return sorted(queues)
3148 3148
3149 3149 def _setactive(name):
3150 3150 if q.applied:
3151 3151 raise util.Abort(_('new queue created, but cannot make active '
3152 3152 'as patches are applied'))
3153 3153 _setactivenocheck(name)
3154 3154
3155 3155 def _setactivenocheck(name):
3156 3156 fh = repo.opener(_activequeue, 'w')
3157 3157 if name != 'patches':
3158 3158 fh.write(name)
3159 3159 fh.close()
3160 3160
3161 3161 def _addqueue(name):
3162 3162 fh = repo.opener(_allqueues, 'a')
3163 3163 fh.write('%s\n' % (name,))
3164 3164 fh.close()
3165 3165
3166 3166 def _queuedir(name):
3167 3167 if name == 'patches':
3168 3168 return repo.join('patches')
3169 3169 else:
3170 3170 return repo.join('patches-' + name)
3171 3171
3172 3172 def _validname(name):
3173 3173 for n in name:
3174 3174 if n in ':\\/.':
3175 3175 return False
3176 3176 return True
3177 3177
3178 3178 def _delete(name):
3179 3179 if name not in existing:
3180 3180 raise util.Abort(_('cannot delete queue that does not exist'))
3181 3181
3182 3182 current = _getcurrent()
3183 3183
3184 3184 if name == current:
3185 3185 raise util.Abort(_('cannot delete currently active queue'))
3186 3186
3187 3187 fh = repo.opener('patches.queues.new', 'w')
3188 3188 for queue in existing:
3189 3189 if queue == name:
3190 3190 continue
3191 3191 fh.write('%s\n' % (queue,))
3192 3192 fh.close()
3193 3193 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3194 3194
3195 3195 if not name or opts.get('list') or opts.get('active'):
3196 3196 current = _getcurrent()
3197 3197 if opts.get('active'):
3198 3198 ui.write('%s\n' % (current,))
3199 3199 return
3200 3200 for queue in _getqueues():
3201 3201 ui.write('%s' % (queue,))
3202 3202 if queue == current and not ui.quiet:
3203 3203 ui.write(_(' (active)\n'))
3204 3204 else:
3205 3205 ui.write('\n')
3206 3206 return
3207 3207
3208 3208 if not _validname(name):
3209 3209 raise util.Abort(
3210 3210 _('invalid queue name, may not contain the characters ":\\/."'))
3211 3211
3212 3212 existing = _getqueues()
3213 3213
3214 3214 if opts.get('create'):
3215 3215 if name in existing:
3216 3216 raise util.Abort(_('queue "%s" already exists') % name)
3217 3217 if _noqueues():
3218 3218 _addqueue(_defaultqueue)
3219 3219 _addqueue(name)
3220 3220 _setactive(name)
3221 3221 elif opts.get('rename'):
3222 3222 current = _getcurrent()
3223 3223 if name == current:
3224 3224 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3225 3225 if name in existing:
3226 3226 raise util.Abort(_('queue "%s" already exists') % name)
3227 3227
3228 3228 olddir = _queuedir(current)
3229 3229 newdir = _queuedir(name)
3230 3230
3231 3231 if os.path.exists(newdir):
3232 3232 raise util.Abort(_('non-queue directory "%s" already exists') %
3233 3233 newdir)
3234 3234
3235 3235 fh = repo.opener('patches.queues.new', 'w')
3236 3236 for queue in existing:
3237 3237 if queue == current:
3238 3238 fh.write('%s\n' % (name,))
3239 3239 if os.path.exists(olddir):
3240 3240 util.rename(olddir, newdir)
3241 3241 else:
3242 3242 fh.write('%s\n' % (queue,))
3243 3243 fh.close()
3244 3244 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3245 3245 _setactivenocheck(name)
3246 3246 elif opts.get('delete'):
3247 3247 _delete(name)
3248 3248 elif opts.get('purge'):
3249 3249 if name in existing:
3250 3250 _delete(name)
3251 3251 qdir = _queuedir(name)
3252 3252 if os.path.exists(qdir):
3253 3253 shutil.rmtree(qdir)
3254 3254 else:
3255 3255 if name not in existing:
3256 3256 raise util.Abort(_('use --create to create a new queue'))
3257 3257 _setactive(name)
3258 3258
3259 3259 def mqphasedefaults(repo, roots):
3260 3260 """callback used to set mq changeset as secret when no phase data exists"""
3261 3261 if repo.mq.applied:
3262 3262 if repo.ui.configbool('mq', 'secret', False):
3263 3263 mqphase = phases.secret
3264 3264 else:
3265 3265 mqphase = phases.draft
3266 3266 qbase = repo[repo.mq.applied[0].node]
3267 3267 roots[mqphase].add(qbase.node())
3268 3268 return roots
3269 3269
3270 3270 def reposetup(ui, repo):
3271 3271 class mqrepo(repo.__class__):
3272 3272 @localrepo.unfilteredpropertycache
3273 3273 def mq(self):
3274 3274 return queue(self.ui, self.baseui, self.path)
3275 3275
3276 3276 def invalidateall(self):
3277 3277 super(mqrepo, self).invalidateall()
3278 3278 if localrepo.hasunfilteredcache(self, 'mq'):
3279 3279 # recreate mq in case queue path was changed
3280 3280 delattr(self.unfiltered(), 'mq')
3281 3281
3282 3282 def abortifwdirpatched(self, errmsg, force=False):
3283 3283 if self.mq.applied and self.mq.checkapplied and not force:
3284 3284 parents = self.dirstate.parents()
3285 3285 patches = [s.node for s in self.mq.applied]
3286 3286 if parents[0] in patches or parents[1] in patches:
3287 3287 raise util.Abort(errmsg)
3288 3288
3289 3289 def commit(self, text="", user=None, date=None, match=None,
3290 3290 force=False, editor=False, extra={}):
3291 3291 self.abortifwdirpatched(
3292 3292 _('cannot commit over an applied mq patch'),
3293 3293 force)
3294 3294
3295 3295 return super(mqrepo, self).commit(text, user, date, match, force,
3296 3296 editor, extra)
3297 3297
3298 3298 def checkpush(self, pushop):
3299 3299 if self.mq.applied and self.mq.checkapplied and not pushop.force:
3300 3300 outapplied = [e.node for e in self.mq.applied]
3301 3301 if pushop.revs:
3302 3302 # Assume applied patches have no non-patch descendants and
3303 3303 # are not on remote already. Filtering any changeset not
3304 3304 # pushed.
3305 3305 heads = set(pushop.revs)
3306 3306 for node in reversed(outapplied):
3307 3307 if node in heads:
3308 3308 break
3309 3309 else:
3310 3310 outapplied.pop()
3311 3311 # looking for pushed and shared changeset
3312 3312 for node in outapplied:
3313 3313 if self[node].phase() < phases.secret:
3314 3314 raise util.Abort(_('source has mq patches applied'))
3315 3315 # no non-secret patches pushed
3316 3316 super(mqrepo, self).checkpush(pushop)
3317 3317
3318 3318 def _findtags(self):
3319 3319 '''augment tags from base class with patch tags'''
3320 3320 result = super(mqrepo, self)._findtags()
3321 3321
3322 3322 q = self.mq
3323 3323 if not q.applied:
3324 3324 return result
3325 3325
3326 3326 mqtags = [(patch.node, patch.name) for patch in q.applied]
3327 3327
3328 3328 try:
3329 3329 # for now ignore filtering business
3330 3330 self.unfiltered().changelog.rev(mqtags[-1][0])
3331 3331 except error.LookupError:
3332 3332 self.ui.warn(_('mq status file refers to unknown node %s\n')
3333 3333 % short(mqtags[-1][0]))
3334 3334 return result
3335 3335
3336 3336 # do not add fake tags for filtered revisions
3337 3337 included = self.changelog.hasnode
3338 3338 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
3339 3339 if not mqtags:
3340 3340 return result
3341 3341
3342 3342 mqtags.append((mqtags[-1][0], 'qtip'))
3343 3343 mqtags.append((mqtags[0][0], 'qbase'))
3344 3344 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3345 3345 tags = result[0]
3346 3346 for patch in mqtags:
3347 3347 if patch[1] in tags:
3348 3348 self.ui.warn(_('tag %s overrides mq patch of the same '
3349 3349 'name\n') % patch[1])
3350 3350 else:
3351 3351 tags[patch[1]] = patch[0]
3352 3352
3353 3353 return result
3354 3354
3355 3355 if repo.local():
3356 3356 repo.__class__ = mqrepo
3357 3357
3358 3358 repo._phasedefaults.append(mqphasedefaults)
3359 3359
3360 3360 def mqimport(orig, ui, repo, *args, **kwargs):
3361 3361 if (util.safehasattr(repo, 'abortifwdirpatched')
3362 3362 and not kwargs.get('no_commit', False)):
3363 3363 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3364 3364 kwargs.get('force'))
3365 3365 return orig(ui, repo, *args, **kwargs)
3366 3366
3367 3367 def mqinit(orig, ui, *args, **kwargs):
3368 3368 mq = kwargs.pop('mq', None)
3369 3369
3370 3370 if not mq:
3371 3371 return orig(ui, *args, **kwargs)
3372 3372
3373 3373 if args:
3374 3374 repopath = args[0]
3375 3375 if not hg.islocal(repopath):
3376 3376 raise util.Abort(_('only a local queue repository '
3377 3377 'may be initialized'))
3378 3378 else:
3379 3379 repopath = cmdutil.findrepo(os.getcwd())
3380 3380 if not repopath:
3381 3381 raise util.Abort(_('there is no Mercurial repository here '
3382 3382 '(.hg not found)'))
3383 3383 repo = hg.repository(ui, repopath)
3384 3384 return qinit(ui, repo, True)
3385 3385
3386 3386 def mqcommand(orig, ui, repo, *args, **kwargs):
3387 3387 """Add --mq option to operate on patch repository instead of main"""
3388 3388
3389 3389 # some commands do not like getting unknown options
3390 3390 mq = kwargs.pop('mq', None)
3391 3391
3392 3392 if not mq:
3393 3393 return orig(ui, repo, *args, **kwargs)
3394 3394
3395 3395 q = repo.mq
3396 3396 r = q.qrepo()
3397 3397 if not r:
3398 3398 raise util.Abort(_('no queue repository'))
3399 3399 return orig(r.ui, r, *args, **kwargs)
3400 3400
3401 3401 def summaryhook(ui, repo):
3402 3402 q = repo.mq
3403 3403 m = []
3404 3404 a, u = len(q.applied), len(q.unapplied(repo))
3405 3405 if a:
3406 3406 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3407 3407 if u:
3408 3408 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3409 3409 if m:
3410 3410 # i18n: column positioning for "hg summary"
3411 3411 ui.write(_("mq: %s\n") % ', '.join(m))
3412 3412 else:
3413 3413 # i18n: column positioning for "hg summary"
3414 3414 ui.note(_("mq: (empty queue)\n"))
3415 3415
3416 3416 def revsetmq(repo, subset, x):
3417 3417 """``mq()``
3418 3418 Changesets managed by MQ.
3419 3419 """
3420 3420 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3421 3421 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3422 3422 return revset.baseset([r for r in subset if r in applied])
3423 3423
3424 3424 # tell hggettext to extract docstrings from these functions:
3425 3425 i18nfunctions = [revsetmq]
3426 3426
3427 3427 def extsetup(ui):
3428 3428 # Ensure mq wrappers are called first, regardless of extension load order by
3429 3429 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3430 3430 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3431 3431
3432 3432 extensions.wrapcommand(commands.table, 'import', mqimport)
3433 3433 cmdutil.summaryhooks.add('mq', summaryhook)
3434 3434
3435 3435 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3436 3436 entry[1].extend(mqopt)
3437 3437
3438 3438 nowrap = set(commands.norepo.split(" "))
3439 3439
3440 3440 def dotable(cmdtable):
3441 3441 for cmd in cmdtable.keys():
3442 3442 cmd = cmdutil.parsealiases(cmd)[0]
3443 3443 if cmd in nowrap:
3444 3444 continue
3445 3445 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3446 3446 entry[1].extend(mqopt)
3447 3447
3448 3448 dotable(commands.table)
3449 3449
3450 3450 for extname, extmodule in extensions.extensions():
3451 3451 if extmodule.__file__ != __file__:
3452 3452 dotable(getattr(extmodule, 'cmdtable', {}))
3453 3453
3454 3454 revset.symbols['mq'] = revsetmq
3455 3455
3456 3456 colortable = {'qguard.negative': 'red',
3457 3457 'qguard.positive': 'yellow',
3458 3458 'qguard.unguarded': 'green',
3459 3459 'qseries.applied': 'blue bold underline',
3460 3460 'qseries.guarded': 'black bold',
3461 3461 'qseries.missing': 'red bold',
3462 3462 'qseries.unapplied': 'black bold'}
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now