##// END OF EJS Templates
merge with stable
Matt Mackall -
r19852:57479e0d merge default
parent child Browse files
Show More

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

1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,79 +1,80
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 e7fa36d2ad3a7944a52dca126458d6f482db3524 0 iQIVAwUAUktg4yBXgaxoKi1yAQLO0g//du/2ypYYUfmM/yZ4zztNKIvgMSGTDVbCCGB2y2/wk2EcolpjpGTkcgnJT413ksYtw78ZU+mvv0RjgrFCm8DQ8kroJaQZ2qHmtSUb42hPBPvtg6kL9YaA4yvp87uUBpFRavGS5uX4hhEIyvZKzhXUBvqtL3TfwR7ld21bj8j00wudqELyyU9IrojIY9jkJ3XL/4shBGgP7u6OK5g8yJ6zTnWgysUetxHBPrYjG25lziiiZQFvZqK1B3PUqAOaFPltQs0PB8ipOCAHQgJsjaREj8VmC3+rskmSSy66NHm6gAB9+E8oAgOcU7FzWbdYgnz4kR3M7TQvHX9U61NinPXC6Q9d1VPhO3E6sIGvqJ4YeQOn65V9ezYuIpFSlgQzCHMmLVnOV96Uv1R/Z39I4w7D3S5qoZcQT/siQwGbsZoPMGFYmqOK1da5TZWrrJWkYzc9xvzT9m3q3Wds5pmCmo4b/dIqDifWwYEcNAZ0/YLHwCN5SEZWuunkEwtU5o7TZAv3bvDDA6WxUrrHI/y9/qvvhXxsJnY8IueNhshdmWZfXKz+lJi2Dvk7DUlEQ1zZWSsozi1E+3biMPJO47jsxjoT/jmE5+GHLCgcnXXDVBeaVal99IOaTRFukiz2EMsry1s8fnwEE5XKDKRlU/dOPfsje0gc7bgE0QD/u3E4NJ99g9A=
@@ -1,92 +1,93
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 e7fa36d2ad3a7944a52dca126458d6f482db3524 2.7.2
@@ -1,901 +1,900
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 # Commands:
34 34 # p, pick = use commit
35 35 # e, edit = use commit, but stop for amending
36 36 # f, fold = use commit, but fold into previous commit (combines N and N-1)
37 37 # d, drop = remove commit from history
38 38 # m, mess = edit message without changing commit content
39 39 #
40 40
41 41 In this file, lines beginning with ``#`` are ignored. You must specify a rule
42 42 for each revision in your history. For example, if you had meant to add gamma
43 43 before beta, and then wanted to add delta in the same revision as beta, you
44 44 would reorganize the file to look like this::
45 45
46 46 pick 030b686bedc4 Add gamma
47 47 pick c561b4e977df Add beta
48 48 fold 7c2fd3b9020c Add delta
49 49
50 50 # Edit history between c561b4e977df and 7c2fd3b9020c
51 51 #
52 52 # Commands:
53 53 # p, pick = use commit
54 54 # e, edit = use commit, but stop for amending
55 55 # f, fold = use commit, but fold into previous commit (combines N and N-1)
56 56 # d, drop = remove commit from history
57 57 # m, mess = edit message without changing commit content
58 58 #
59 59
60 60 At which point you close the editor and ``histedit`` starts working. When you
61 61 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
62 62 those revisions together, offering you a chance to clean up the commit message::
63 63
64 64 Add beta
65 65 ***
66 66 Add delta
67 67
68 68 Edit the commit message to your liking, then close the editor. For
69 69 this example, let's assume that the commit message was changed to
70 70 ``Add beta and delta.`` After histedit has run and had a chance to
71 71 remove any old or temporary revisions it needed, the history looks
72 72 like this::
73 73
74 74 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
75 75 | Add beta and delta.
76 76 |
77 77 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
78 78 | Add gamma
79 79 |
80 80 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
81 81 Add alpha
82 82
83 83 Note that ``histedit`` does *not* remove any revisions (even its own temporary
84 84 ones) until after it has completed all the editing operations, so it will
85 85 probably perform several strip operations when it's done. For the above example,
86 86 it had to run strip twice. Strip can be slow depending on a variety of factors,
87 87 so you might need to be a little patient. You can choose to keep the original
88 88 revisions by passing the ``--keep`` flag.
89 89
90 90 The ``edit`` operation will drop you back to a command prompt,
91 91 allowing you to edit files freely, or even use ``hg record`` to commit
92 92 some changes as a separate commit. When you're done, any remaining
93 93 uncommitted changes will be committed as well. When done, run ``hg
94 94 histedit --continue`` to finish this step. You'll be prompted for a
95 95 new commit message, but the default commit message will be the
96 96 original message for the ``edit`` ed revision.
97 97
98 98 The ``message`` operation will give you a chance to revise a commit
99 99 message without changing the contents. It's a shortcut for doing
100 100 ``edit`` immediately followed by `hg histedit --continue``.
101 101
102 102 If ``histedit`` encounters a conflict when moving a revision (while
103 103 handling ``pick`` or ``fold``), it'll stop in a similar manner to
104 104 ``edit`` with the difference that it won't prompt you for a commit
105 105 message when done. If you decide at this point that you don't like how
106 106 much work it will be to rearrange history, or that you made a mistake,
107 107 you can use ``hg histedit --abort`` to abandon the new changes you
108 108 have made and return to the state before you attempted to edit your
109 109 history.
110 110
111 111 If we clone the histedit-ed example repository above and add four more
112 112 changes, such that we have the following history::
113 113
114 114 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
115 115 | Add theta
116 116 |
117 117 o 5 140988835471 2009-04-27 18:04 -0500 stefan
118 118 | Add eta
119 119 |
120 120 o 4 122930637314 2009-04-27 18:04 -0500 stefan
121 121 | Add zeta
122 122 |
123 123 o 3 836302820282 2009-04-27 18:04 -0500 stefan
124 124 | Add epsilon
125 125 |
126 126 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
127 127 | Add beta and delta.
128 128 |
129 129 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
130 130 | Add gamma
131 131 |
132 132 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
133 133 Add alpha
134 134
135 135 If you run ``hg histedit --outgoing`` on the clone then it is the same
136 136 as running ``hg histedit 836302820282``. If you need plan to push to a
137 137 repository that Mercurial does not detect to be related to the source
138 138 repo, you can add a ``--force`` option.
139 139 """
140 140
141 141 try:
142 142 import cPickle as pickle
143 143 pickle.dump # import now
144 144 except ImportError:
145 145 import pickle
146 146 import os
147 147 import sys
148 148
149 149 from mercurial import cmdutil
150 150 from mercurial import discovery
151 151 from mercurial import error
152 152 from mercurial import copies
153 153 from mercurial import context
154 154 from mercurial import hg
155 155 from mercurial import lock as lockmod
156 156 from mercurial import node
157 157 from mercurial import repair
158 158 from mercurial import scmutil
159 159 from mercurial import util
160 160 from mercurial import obsolete
161 161 from mercurial import merge as mergemod
162 162 from mercurial.i18n import _
163 163
164 164 cmdtable = {}
165 165 command = cmdutil.command(cmdtable)
166 166
167 167 testedwith = 'internal'
168 168
169 169 # i18n: command names and abbreviations must remain untranslated
170 170 editcomment = _("""# Edit history between %s and %s
171 171 #
172 172 # Commands:
173 173 # p, pick = use commit
174 174 # e, edit = use commit, but stop for amending
175 175 # f, fold = use commit, but fold into previous commit (combines N and N-1)
176 176 # d, drop = remove commit from history
177 177 # m, mess = edit message without changing commit content
178 178 #
179 179 """)
180 180
181 181 def commitfuncfor(repo, src):
182 182 """Build a commit function for the replacement of <src>
183 183
184 184 This function ensure we apply the same treatment to all changesets.
185 185
186 186 - Add a 'histedit_source' entry in extra.
187 187
188 188 Note that fold have its own separated logic because its handling is a bit
189 189 different and not easily factored out of the fold method.
190 190 """
191 191 phasemin = src.phase()
192 192 def commitfunc(**kwargs):
193 193 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
194 194 try:
195 195 repo.ui.setconfig('phases', 'new-commit', phasemin)
196 196 extra = kwargs.get('extra', {}).copy()
197 197 extra['histedit_source'] = src.hex()
198 198 kwargs['extra'] = extra
199 199 return repo.commit(**kwargs)
200 200 finally:
201 201 repo.ui.restoreconfig(phasebackup)
202 202 return commitfunc
203 203
204 204
205 205
206 206 def applychanges(ui, repo, ctx, opts):
207 207 """Merge changeset from ctx (only) in the current working directory"""
208 208 wcpar = repo.dirstate.parents()[0]
209 209 if ctx.p1().node() == wcpar:
210 210 # edition ar "in place" we do not need to make any merge,
211 211 # just applies changes on parent for edition
212 212 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
213 213 stats = None
214 214 else:
215 215 try:
216 216 # ui.forcemerge is an internal variable, do not document
217 217 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
218 218 stats = mergemod.update(repo, ctx.node(), True, True, False,
219 219 ctx.p1().node())
220 220 finally:
221 221 repo.ui.setconfig('ui', 'forcemerge', '')
222 222 repo.setparents(wcpar, node.nullid)
223 223 repo.dirstate.write()
224 224 # fix up dirstate for copies and renames
225 225 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
226 226 return stats
227 227
228 228 def collapse(repo, first, last, commitopts):
229 229 """collapse the set of revisions from first to last as new one.
230 230
231 231 Expected commit options are:
232 232 - message
233 233 - date
234 234 - username
235 235 Commit message is edited in all cases.
236 236
237 237 This function works in memory."""
238 238 ctxs = list(repo.set('%d::%d', first, last))
239 239 if not ctxs:
240 240 return None
241 241 base = first.parents()[0]
242 242
243 243 # commit a new version of the old changeset, including the update
244 244 # collect all files which might be affected
245 245 files = set()
246 246 for ctx in ctxs:
247 247 files.update(ctx.files())
248 248
249 249 # Recompute copies (avoid recording a -> b -> a)
250 250 copied = copies.pathcopies(base, last)
251 251
252 252 # prune files which were reverted by the updates
253 253 def samefile(f):
254 254 if f in last.manifest():
255 255 a = last.filectx(f)
256 256 if f in base.manifest():
257 257 b = base.filectx(f)
258 258 return (a.data() == b.data()
259 259 and a.flags() == b.flags())
260 260 else:
261 261 return False
262 262 else:
263 263 return f not in base.manifest()
264 264 files = [f for f in files if not samefile(f)]
265 265 # commit version of these files as defined by head
266 266 headmf = last.manifest()
267 267 def filectxfn(repo, ctx, path):
268 268 if path in headmf:
269 269 fctx = last[path]
270 270 flags = fctx.flags()
271 271 mctx = context.memfilectx(fctx.path(), fctx.data(),
272 272 islink='l' in flags,
273 273 isexec='x' in flags,
274 274 copied=copied.get(path))
275 275 return mctx
276 276 raise IOError()
277 277
278 278 if commitopts.get('message'):
279 279 message = commitopts['message']
280 280 else:
281 281 message = first.description()
282 282 user = commitopts.get('user')
283 283 date = commitopts.get('date')
284 284 extra = commitopts.get('extra')
285 285
286 286 parents = (first.p1().node(), first.p2().node())
287 287 new = context.memctx(repo,
288 288 parents=parents,
289 289 text=message,
290 290 files=files,
291 291 filectxfn=filectxfn,
292 292 user=user,
293 293 date=date,
294 294 extra=extra)
295 295 new._text = cmdutil.commitforceeditor(repo, new, [])
296 296 return repo.commitctx(new)
297 297
298 298 def pick(ui, repo, ctx, ha, opts):
299 299 oldctx = repo[ha]
300 300 if oldctx.parents()[0] == ctx:
301 301 ui.debug('node %s unchanged\n' % ha)
302 302 return oldctx, []
303 303 hg.update(repo, ctx.node())
304 304 stats = applychanges(ui, repo, oldctx, opts)
305 305 if stats and stats[3] > 0:
306 306 raise error.InterventionRequired(_('Fix up the change and run '
307 307 'hg histedit --continue'))
308 308 # drop the second merge parent
309 309 commit = commitfuncfor(repo, oldctx)
310 310 n = commit(text=oldctx.description(), user=oldctx.user(),
311 311 date=oldctx.date(), extra=oldctx.extra())
312 312 if n is None:
313 313 ui.warn(_('%s: empty changeset\n')
314 314 % node.hex(ha))
315 315 return ctx, []
316 316 new = repo[n]
317 317 return new, [(oldctx.node(), (n,))]
318 318
319 319
320 320 def edit(ui, repo, ctx, ha, opts):
321 321 oldctx = repo[ha]
322 322 hg.update(repo, ctx.node())
323 323 applychanges(ui, repo, oldctx, opts)
324 324 raise error.InterventionRequired(
325 325 _('Make changes as needed, you may commit or record as needed now.\n'
326 326 'When you are finished, run hg histedit --continue to resume.'))
327 327
328 328 def fold(ui, repo, ctx, ha, opts):
329 329 oldctx = repo[ha]
330 330 hg.update(repo, ctx.node())
331 331 stats = applychanges(ui, repo, oldctx, opts)
332 332 if stats and stats[3] > 0:
333 333 raise error.InterventionRequired(
334 334 _('Fix up the change and run hg histedit --continue'))
335 335 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
336 336 date=oldctx.date(), extra=oldctx.extra())
337 337 if n is None:
338 338 ui.warn(_('%s: empty changeset')
339 339 % node.hex(ha))
340 340 return ctx, []
341 341 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
342 342
343 343 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
344 344 parent = ctx.parents()[0].node()
345 345 hg.update(repo, parent)
346 346 ### prepare new commit data
347 347 commitopts = opts.copy()
348 348 # username
349 349 if ctx.user() == oldctx.user():
350 350 username = ctx.user()
351 351 else:
352 352 username = ui.username()
353 353 commitopts['user'] = username
354 354 # commit message
355 355 newmessage = '\n***\n'.join(
356 356 [ctx.description()] +
357 357 [repo[r].description() for r in internalchanges] +
358 358 [oldctx.description()]) + '\n'
359 359 commitopts['message'] = newmessage
360 360 # date
361 361 commitopts['date'] = max(ctx.date(), oldctx.date())
362 362 extra = ctx.extra().copy()
363 363 # histedit_source
364 364 # note: ctx is likely a temporary commit but that the best we can do here
365 365 # This is sufficient to solve issue3681 anyway
366 366 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
367 367 commitopts['extra'] = extra
368 368 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
369 369 try:
370 370 phasemin = max(ctx.phase(), oldctx.phase())
371 371 repo.ui.setconfig('phases', 'new-commit', phasemin)
372 372 n = collapse(repo, ctx, repo[newnode], commitopts)
373 373 finally:
374 374 repo.ui.restoreconfig(phasebackup)
375 375 if n is None:
376 376 return ctx, []
377 377 hg.update(repo, n)
378 378 replacements = [(oldctx.node(), (newnode,)),
379 379 (ctx.node(), (n,)),
380 380 (newnode, (n,)),
381 381 ]
382 382 for ich in internalchanges:
383 383 replacements.append((ich, (n,)))
384 384 return repo[n], replacements
385 385
386 386 def drop(ui, repo, ctx, ha, opts):
387 387 return ctx, [(repo[ha].node(), ())]
388 388
389 389
390 390 def message(ui, repo, ctx, ha, opts):
391 391 oldctx = repo[ha]
392 392 hg.update(repo, ctx.node())
393 393 stats = applychanges(ui, repo, oldctx, opts)
394 394 if stats and stats[3] > 0:
395 395 raise error.InterventionRequired(
396 396 _('Fix up the change and run hg histedit --continue'))
397 397 message = oldctx.description() + '\n'
398 398 message = ui.edit(message, ui.username())
399 399 commit = commitfuncfor(repo, oldctx)
400 400 new = commit(text=message, user=oldctx.user(), date=oldctx.date(),
401 401 extra=oldctx.extra())
402 402 newctx = repo[new]
403 403 if oldctx.node() != newctx.node():
404 404 return newctx, [(oldctx.node(), (new,))]
405 405 # We didn't make an edit, so just indicate no replaced nodes
406 406 return newctx, []
407 407
408 408 def findoutgoing(ui, repo, remote=None, force=False, opts={}):
409 409 """utility function to find the first outgoing changeset
410 410
411 411 Used by initialisation code"""
412 412 dest = ui.expandpath(remote or 'default-push', remote or 'default')
413 413 dest, revs = hg.parseurl(dest, None)[:2]
414 414 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
415 415
416 416 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
417 417 other = hg.peer(repo, opts, dest)
418 418
419 419 if revs:
420 420 revs = [repo.lookup(rev) for rev in revs]
421 421
422 422 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
423 423 if not outgoing.missing:
424 424 raise util.Abort(_('no outgoing ancestors'))
425 425 roots = list(repo.revs("roots(%ln)", outgoing.missing))
426 426 if 1 < len(roots):
427 427 msg = _('there are ambiguous outgoing revisions')
428 428 hint = _('see "hg help histedit" for more detail')
429 429 raise util.Abort(msg, hint=hint)
430 430 return repo.lookup(roots[0])
431 431
432 432 actiontable = {'p': pick,
433 433 'pick': pick,
434 434 'e': edit,
435 435 'edit': edit,
436 436 'f': fold,
437 437 'fold': fold,
438 438 'd': drop,
439 439 'drop': drop,
440 440 'm': message,
441 441 'mess': message,
442 442 }
443 443
444 444 @command('histedit',
445 445 [('', 'commands', '',
446 446 _('Read history edits from the specified file.')),
447 447 ('c', 'continue', False, _('continue an edit already in progress')),
448 448 ('k', 'keep', False,
449 449 _("don't strip old nodes after edit is complete")),
450 450 ('', 'abort', False, _('abort an edit in progress')),
451 451 ('o', 'outgoing', False, _('changesets not found in destination')),
452 452 ('f', 'force', False,
453 453 _('force outgoing even for unrelated repositories')),
454 454 ('r', 'rev', [], _('first revision to be edited'))],
455 455 _("ANCESTOR | --outgoing [URL]"))
456 456 def histedit(ui, repo, *freeargs, **opts):
457 457 """interactively edit changeset history
458 458
459 459 This command edits changesets between ANCESTOR and the parent of
460 460 the working directory.
461 461
462 462 With --outgoing, this edits changesets not found in the
463 463 destination repository. If URL of the destination is omitted, the
464 464 'default-push' (or 'default') path will be used.
465 465
466 466 For safety, this command is aborted, also if there are ambiguous
467 467 outgoing revisions which may confuse users: for example, there are
468 468 multiple branches containing outgoing revisions.
469 469
470 470 Use "min(outgoing() and ::.)" or similar revset specification
471 471 instead of --outgoing to specify edit target revision exactly in
472 472 such ambiguous situation. See :hg:`help revsets` for detail about
473 473 selecting revisions.
474 474 """
475 475 # TODO only abort if we try and histedit mq patches, not just
476 476 # blanket if mq patches are applied somewhere
477 477 mq = getattr(repo, 'mq', None)
478 478 if mq and mq.applied:
479 479 raise util.Abort(_('source has mq patches applied'))
480 480
481 481 # basic argument incompatibility processing
482 482 outg = opts.get('outgoing')
483 483 cont = opts.get('continue')
484 484 abort = opts.get('abort')
485 485 force = opts.get('force')
486 486 rules = opts.get('commands', '')
487 487 revs = opts.get('rev', [])
488 488 goal = 'new' # This invocation goal, in new, continue, abort
489 489 if force and not outg:
490 490 raise util.Abort(_('--force only allowed with --outgoing'))
491 491 if cont:
492 492 if util.any((outg, abort, revs, freeargs, rules)):
493 493 raise util.Abort(_('no arguments allowed with --continue'))
494 494 goal = 'continue'
495 495 elif abort:
496 496 if util.any((outg, revs, freeargs, rules)):
497 497 raise util.Abort(_('no arguments allowed with --abort'))
498 498 goal = 'abort'
499 499 else:
500 500 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
501 501 raise util.Abort(_('history edit already in progress, try '
502 502 '--continue or --abort'))
503 503 if outg:
504 504 if revs:
505 505 raise util.Abort(_('no revisions allowed with --outgoing'))
506 506 if len(freeargs) > 1:
507 507 raise util.Abort(
508 508 _('only one repo argument allowed with --outgoing'))
509 509 else:
510 510 revs.extend(freeargs)
511 511 if len(revs) != 1:
512 512 raise util.Abort(
513 513 _('histedit requires exactly one ancestor revision'))
514 514
515 515
516 516 if goal == 'continue':
517 517 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
518 518 parentctx = repo[parentctxnode]
519 519 parentctx, repl = bootstrapcontinue(ui, repo, parentctx, rules, opts)
520 520 replacements.extend(repl)
521 521 elif goal == 'abort':
522 522 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
523 523 mapping, tmpnodes, leafs, _ntm = processreplacement(repo, replacements)
524 524 ui.debug('restore wc to old parent %s\n' % node.short(topmost))
525 525 # check whether we should update away
526 526 parentnodes = [c.node() for c in repo[None].parents()]
527 527 for n in leafs | set([parentctxnode]):
528 528 if n in parentnodes:
529 529 hg.clean(repo, topmost)
530 530 break
531 531 else:
532 532 pass
533 533 cleanupnode(ui, repo, 'created', tmpnodes)
534 534 cleanupnode(ui, repo, 'temp', leafs)
535 535 os.unlink(os.path.join(repo.path, 'histedit-state'))
536 536 return
537 537 else:
538 538 cmdutil.checkunfinished(repo)
539 539 cmdutil.bailifchanged(repo)
540 540
541 541 topmost, empty = repo.dirstate.parents()
542 542 if outg:
543 543 if freeargs:
544 544 remote = freeargs[0]
545 545 else:
546 546 remote = None
547 547 root = findoutgoing(ui, repo, remote, force, opts)
548 548 else:
549 549 root = revs[0]
550 550 root = scmutil.revsingle(repo, root).node()
551 551
552 552 keep = opts.get('keep', False)
553 553 revs = between(repo, root, topmost, keep)
554 554 if not revs:
555 555 raise util.Abort(_('%s is not an ancestor of working directory') %
556 556 node.short(root))
557 557
558 558 ctxs = [repo[r] for r in revs]
559 559 if not rules:
560 560 rules = '\n'.join([makedesc(c) for c in ctxs])
561 561 rules += '\n\n'
562 562 rules += editcomment % (node.short(root), node.short(topmost))
563 563 rules = ui.edit(rules, ui.username())
564 564 # Save edit rules in .hg/histedit-last-edit.txt in case
565 565 # the user needs to ask for help after something
566 566 # surprising happens.
567 567 f = open(repo.join('histedit-last-edit.txt'), 'w')
568 568 f.write(rules)
569 569 f.close()
570 570 else:
571 571 if rules == '-':
572 572 f = sys.stdin
573 573 else:
574 574 f = open(rules)
575 575 rules = f.read()
576 576 f.close()
577 577 rules = [l for l in (r.strip() for r in rules.splitlines())
578 578 if l and not l[0] == '#']
579 579 rules = verifyrules(rules, repo, ctxs)
580 580
581 581 parentctx = repo[root].parents()[0]
582 582 keep = opts.get('keep', False)
583 583 replacements = []
584 584
585 585
586 586 while rules:
587 587 writestate(repo, parentctx.node(), rules, keep, topmost, replacements)
588 588 action, ha = rules.pop(0)
589 589 ui.debug('histedit: processing %s %s\n' % (action, ha))
590 590 actfunc = actiontable[action]
591 591 parentctx, replacement_ = actfunc(ui, repo, parentctx, ha, opts)
592 592 replacements.extend(replacement_)
593 593
594 594 hg.update(repo, parentctx.node())
595 595
596 596 mapping, tmpnodes, created, ntm = processreplacement(repo, replacements)
597 597 if mapping:
598 598 for prec, succs in mapping.iteritems():
599 599 if not succs:
600 600 ui.debug('histedit: %s is dropped\n' % node.short(prec))
601 601 else:
602 602 ui.debug('histedit: %s is replaced by %s\n' % (
603 603 node.short(prec), node.short(succs[0])))
604 604 if len(succs) > 1:
605 605 m = 'histedit: %s'
606 606 for n in succs[1:]:
607 607 ui.debug(m % node.short(n))
608 608
609 609 if not keep:
610 610 if mapping:
611 611 movebookmarks(ui, repo, mapping, topmost, ntm)
612 612 # TODO update mq state
613 613 if obsolete._enabled:
614 614 markers = []
615 615 # sort by revision number because it sound "right"
616 616 for prec in sorted(mapping, key=repo.changelog.rev):
617 617 succs = mapping[prec]
618 618 markers.append((repo[prec],
619 619 tuple(repo[s] for s in succs)))
620 620 if markers:
621 621 obsolete.createmarkers(repo, markers)
622 622 else:
623 623 cleanupnode(ui, repo, 'replaced', mapping)
624 624
625 625 cleanupnode(ui, repo, 'temp', tmpnodes)
626 626 os.unlink(os.path.join(repo.path, 'histedit-state'))
627 627 if os.path.exists(repo.sjoin('undo')):
628 628 os.unlink(repo.sjoin('undo'))
629 629
630 630
631 631 def bootstrapcontinue(ui, repo, parentctx, rules, opts):
632 632 action, currentnode = rules.pop(0)
633 633 ctx = repo[currentnode]
634 634 # is there any new commit between the expected parent and "."
635 635 #
636 636 # note: does not take non linear new change in account (but previous
637 637 # implementation didn't used them anyway (issue3655)
638 638 newchildren = [c.node() for c in repo.set('(%d::.)', parentctx)]
639 639 if parentctx.node() != node.nullid:
640 640 if not newchildren:
641 641 # `parentctxnode` should match but no result. This means that
642 642 # currentnode is not a descendant from parentctxnode.
643 643 msg = _('%s is not an ancestor of working directory')
644 hint = _('update to %s or descendant and run "hg histedit '
645 '--continue" again') % parentctx
644 hint = _('use "histedit --abort" to clear broken state')
646 645 raise util.Abort(msg % parentctx, hint=hint)
647 646 newchildren.pop(0) # remove parentctxnode
648 647 # Commit dirty working directory if necessary
649 648 new = None
650 649 m, a, r, d = repo.status()[:4]
651 650 if m or a or r or d:
652 651 # prepare the message for the commit to comes
653 652 if action in ('f', 'fold'):
654 653 message = 'fold-temp-revision %s' % currentnode
655 654 else:
656 655 message = ctx.description() + '\n'
657 656 if action in ('e', 'edit', 'm', 'mess'):
658 657 editor = cmdutil.commitforceeditor
659 658 else:
660 659 editor = False
661 660 commit = commitfuncfor(repo, ctx)
662 661 new = commit(text=message, user=ctx.user(),
663 662 date=ctx.date(), extra=ctx.extra(),
664 663 editor=editor)
665 664 if new is not None:
666 665 newchildren.append(new)
667 666
668 667 replacements = []
669 668 # track replacements
670 669 if ctx.node() not in newchildren:
671 670 # note: new children may be empty when the changeset is dropped.
672 671 # this happen e.g during conflicting pick where we revert content
673 672 # to parent.
674 673 replacements.append((ctx.node(), tuple(newchildren)))
675 674
676 675 if action in ('f', 'fold'):
677 676 if newchildren:
678 677 # finalize fold operation if applicable
679 678 if new is None:
680 679 new = newchildren[-1]
681 680 else:
682 681 newchildren.pop() # remove new from internal changes
683 682 parentctx, repl = finishfold(ui, repo, parentctx, ctx, new, opts,
684 683 newchildren)
685 684 replacements.extend(repl)
686 685 else:
687 686 # newchildren is empty if the fold did not result in any commit
688 687 # this happen when all folded change are discarded during the
689 688 # merge.
690 689 replacements.append((ctx.node(), (parentctx.node(),)))
691 690 elif newchildren:
692 691 # otherwise update "parentctx" before proceeding to further operation
693 692 parentctx = repo[newchildren[-1]]
694 693 return parentctx, replacements
695 694
696 695
697 696 def between(repo, old, new, keep):
698 697 """select and validate the set of revision to edit
699 698
700 699 When keep is false, the specified set can't have children."""
701 700 ctxs = list(repo.set('%n::%n', old, new))
702 701 if ctxs and not keep:
703 702 if (not obsolete._enabled and
704 703 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
705 704 raise util.Abort(_('cannot edit history that would orphan nodes'))
706 705 if repo.revs('(%ld) and merge()', ctxs):
707 706 raise util.Abort(_('cannot edit history that contains merges'))
708 707 root = ctxs[0] # list is already sorted by repo.set
709 708 if not root.phase():
710 709 raise util.Abort(_('cannot edit immutable changeset: %s') % root)
711 710 return [c.node() for c in ctxs]
712 711
713 712
714 713 def writestate(repo, parentnode, rules, keep, topmost, replacements):
715 714 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
716 715 pickle.dump((parentnode, rules, keep, topmost, replacements), fp)
717 716 fp.close()
718 717
719 718 def readstate(repo):
720 719 """Returns a tuple of (parentnode, rules, keep, topmost, replacements).
721 720 """
722 721 fp = open(os.path.join(repo.path, 'histedit-state'))
723 722 return pickle.load(fp)
724 723
725 724
726 725 def makedesc(c):
727 726 """build a initial action line for a ctx `c`
728 727
729 728 line are in the form:
730 729
731 730 pick <hash> <rev> <summary>
732 731 """
733 732 summary = ''
734 733 if c.description():
735 734 summary = c.description().splitlines()[0]
736 735 line = 'pick %s %d %s' % (c, c.rev(), summary)
737 736 return line[:80] # trim to 80 chars so it's not stupidly wide in my editor
738 737
739 738 def verifyrules(rules, repo, ctxs):
740 739 """Verify that there exists exactly one edit rule per given changeset.
741 740
742 741 Will abort if there are to many or too few rules, a malformed rule,
743 742 or a rule on a changeset outside of the user-given range.
744 743 """
745 744 parsed = []
746 745 expected = set(str(c) for c in ctxs)
747 746 seen = set()
748 747 for r in rules:
749 748 if ' ' not in r:
750 749 raise util.Abort(_('malformed line "%s"') % r)
751 750 action, rest = r.split(' ', 1)
752 751 ha = rest.strip().split(' ', 1)[0]
753 752 try:
754 753 ha = str(repo[ha]) # ensure its a short hash
755 754 except error.RepoError:
756 755 raise util.Abort(_('unknown changeset %s listed') % ha)
757 756 if ha not in expected:
758 757 raise util.Abort(
759 758 _('may not use changesets other than the ones listed'))
760 759 if ha in seen:
761 760 raise util.Abort(_('duplicated command for changeset %s') % ha)
762 761 seen.add(ha)
763 762 if action not in actiontable:
764 763 raise util.Abort(_('unknown action "%s"') % action)
765 764 parsed.append([action, ha])
766 765 missing = sorted(expected - seen) # sort to stabilize output
767 766 if missing:
768 767 raise util.Abort(_('missing rules for changeset %s') % missing[0],
769 768 hint=_('do you want to use the drop action?'))
770 769 return parsed
771 770
772 771 def processreplacement(repo, replacements):
773 772 """process the list of replacements to return
774 773
775 774 1) the final mapping between original and created nodes
776 775 2) the list of temporary node created by histedit
777 776 3) the list of new commit created by histedit"""
778 777 allsuccs = set()
779 778 replaced = set()
780 779 fullmapping = {}
781 780 # initialise basic set
782 781 # fullmapping record all operation recorded in replacement
783 782 for rep in replacements:
784 783 allsuccs.update(rep[1])
785 784 replaced.add(rep[0])
786 785 fullmapping.setdefault(rep[0], set()).update(rep[1])
787 786 new = allsuccs - replaced
788 787 tmpnodes = allsuccs & replaced
789 788 # Reduce content fullmapping into direct relation between original nodes
790 789 # and final node created during history edition
791 790 # Dropped changeset are replaced by an empty list
792 791 toproceed = set(fullmapping)
793 792 final = {}
794 793 while toproceed:
795 794 for x in list(toproceed):
796 795 succs = fullmapping[x]
797 796 for s in list(succs):
798 797 if s in toproceed:
799 798 # non final node with unknown closure
800 799 # We can't process this now
801 800 break
802 801 elif s in final:
803 802 # non final node, replace with closure
804 803 succs.remove(s)
805 804 succs.update(final[s])
806 805 else:
807 806 final[x] = succs
808 807 toproceed.remove(x)
809 808 # remove tmpnodes from final mapping
810 809 for n in tmpnodes:
811 810 del final[n]
812 811 # we expect all changes involved in final to exist in the repo
813 812 # turn `final` into list (topologically sorted)
814 813 nm = repo.changelog.nodemap
815 814 for prec, succs in final.items():
816 815 final[prec] = sorted(succs, key=nm.get)
817 816
818 817 # computed topmost element (necessary for bookmark)
819 818 if new:
820 819 newtopmost = sorted(new, key=repo.changelog.rev)[-1]
821 820 elif not final:
822 821 # Nothing rewritten at all. we won't need `newtopmost`
823 822 # It is the same as `oldtopmost` and `processreplacement` know it
824 823 newtopmost = None
825 824 else:
826 825 # every body died. The newtopmost is the parent of the root.
827 826 newtopmost = repo[sorted(final, key=repo.changelog.rev)[0]].p1().node()
828 827
829 828 return final, tmpnodes, new, newtopmost
830 829
831 830 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
832 831 """Move bookmark from old to newly created node"""
833 832 if not mapping:
834 833 # if nothing got rewritten there is not purpose for this function
835 834 return
836 835 moves = []
837 836 for bk, old in sorted(repo._bookmarks.iteritems()):
838 837 if old == oldtopmost:
839 838 # special case ensure bookmark stay on tip.
840 839 #
841 840 # This is arguably a feature and we may only want that for the
842 841 # active bookmark. But the behavior is kept compatible with the old
843 842 # version for now.
844 843 moves.append((bk, newtopmost))
845 844 continue
846 845 base = old
847 846 new = mapping.get(base, None)
848 847 if new is None:
849 848 continue
850 849 while not new:
851 850 # base is killed, trying with parent
852 851 base = repo[base].p1().node()
853 852 new = mapping.get(base, (base,))
854 853 # nothing to move
855 854 moves.append((bk, new[-1]))
856 855 if moves:
857 856 marks = repo._bookmarks
858 857 for mark, new in moves:
859 858 old = marks[mark]
860 859 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
861 860 % (mark, node.short(old), node.short(new)))
862 861 marks[mark] = new
863 862 marks.write()
864 863
865 864 def cleanupnode(ui, repo, name, nodes):
866 865 """strip a group of nodes from the repository
867 866
868 867 The set of node to strip may contains unknown nodes."""
869 868 ui.debug('should strip %s nodes %s\n' %
870 869 (name, ', '.join([node.short(n) for n in nodes])))
871 870 lock = None
872 871 try:
873 872 lock = repo.lock()
874 873 # Find all node that need to be stripped
875 874 # (we hg %lr instead of %ln to silently ignore unknown item
876 875 nm = repo.changelog.nodemap
877 876 nodes = [n for n in nodes if n in nm]
878 877 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
879 878 for c in roots:
880 879 # We should process node in reverse order to strip tip most first.
881 880 # but this trigger a bug in changegroup hook.
882 881 # This would reduce bundle overhead
883 882 repair.strip(ui, repo, c)
884 883 finally:
885 884 lockmod.release(lock)
886 885
887 886 def summaryhook(ui, repo):
888 887 if not os.path.exists(repo.join('histedit-state')):
889 888 return
890 889 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
891 890 if rules:
892 891 # i18n: column positioning for "hg summary"
893 892 ui.write(_('hist: %s (histedit --continue)\n') %
894 893 (ui.label(_('%d remaining'), 'histedit.remaining') %
895 894 len(rules)))
896 895
897 896 def extsetup(ui):
898 897 cmdutil.summaryhooks.add('histedit', summaryhook)
899 898 cmdutil.unfinishedstates.append(
900 899 ['histedit-state', False, True, _('histedit in progress'),
901 900 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
@@ -1,822 +1,839
1 1 # rebase.py - rebasing feature for mercurial
2 2 #
3 3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot 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 '''command to move sets of revisions to a different ancestor
9 9
10 10 This extension lets you rebase changesets in an existing Mercurial
11 11 repository.
12 12
13 13 For more information:
14 14 http://mercurial.selenic.com/wiki/RebaseExtension
15 15 '''
16 16
17 17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
18 18 from mercurial import extensions, patch, scmutil, phases, obsolete, error
19 19 from mercurial.commands import templateopts
20 20 from mercurial.node import nullrev
21 21 from mercurial.lock import release
22 22 from mercurial.i18n import _
23 23 import os, errno
24 24
25 25 nullmerge = -2
26 26 revignored = -3
27 27
28 28 cmdtable = {}
29 29 command = cmdutil.command(cmdtable)
30 30 testedwith = 'internal'
31 31
32 32 @command('rebase',
33 33 [('s', 'source', '',
34 34 _('rebase from the specified changeset'), _('REV')),
35 35 ('b', 'base', '',
36 36 _('rebase from the base of the specified changeset '
37 37 '(up to greatest common ancestor of base and dest)'),
38 38 _('REV')),
39 39 ('r', 'rev', [],
40 40 _('rebase these revisions'),
41 41 _('REV')),
42 42 ('d', 'dest', '',
43 43 _('rebase onto the specified changeset'), _('REV')),
44 44 ('', 'collapse', False, _('collapse the rebased changesets')),
45 45 ('m', 'message', '',
46 46 _('use text as collapse commit message'), _('TEXT')),
47 47 ('e', 'edit', False, _('invoke editor on commit messages')),
48 48 ('l', 'logfile', '',
49 49 _('read collapse commit message from file'), _('FILE')),
50 50 ('', 'keep', False, _('keep original changesets')),
51 51 ('', 'keepbranches', False, _('keep original branch names')),
52 52 ('D', 'detach', False, _('(DEPRECATED)')),
53 53 ('t', 'tool', '', _('specify merge tool')),
54 54 ('c', 'continue', False, _('continue an interrupted rebase')),
55 55 ('a', 'abort', False, _('abort an interrupted rebase'))] +
56 56 templateopts,
57 57 _('[-s REV | -b REV] [-d REV] [OPTION]'))
58 58 def rebase(ui, repo, **opts):
59 59 """move changeset (and descendants) to a different branch
60 60
61 61 Rebase uses repeated merging to graft changesets from one part of
62 62 history (the source) onto another (the destination). This can be
63 63 useful for linearizing *local* changes relative to a master
64 64 development tree.
65 65
66 66 You should not rebase changesets that have already been shared
67 67 with others. Doing so will force everybody else to perform the
68 68 same rebase or they will end up with duplicated changesets after
69 69 pulling in your rebased changesets.
70 70
71 71 In its default configuration, Mercurial will prevent you from
72 72 rebasing published changes. See :hg:`help phases` for details.
73 73
74 74 If you don't specify a destination changeset (``-d/--dest``),
75 75 rebase uses the current branch tip as the destination. (The
76 76 destination changeset is not modified by rebasing, but new
77 77 changesets are added as its descendants.)
78 78
79 79 You can specify which changesets to rebase in two ways: as a
80 80 "source" changeset or as a "base" changeset. Both are shorthand
81 81 for a topologically related set of changesets (the "source
82 82 branch"). If you specify source (``-s/--source``), rebase will
83 83 rebase that changeset and all of its descendants onto dest. If you
84 84 specify base (``-b/--base``), rebase will select ancestors of base
85 85 back to but not including the common ancestor with dest. Thus,
86 86 ``-b`` is less precise but more convenient than ``-s``: you can
87 87 specify any changeset in the source branch, and rebase will select
88 88 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
89 89 uses the parent of the working directory as the base.
90 90
91 91 For advanced usage, a third way is available through the ``--rev``
92 92 option. It allows you to specify an arbitrary set of changesets to
93 93 rebase. Descendants of revs you specify with this option are not
94 94 automatically included in the rebase.
95 95
96 96 By default, rebase recreates the changesets in the source branch
97 97 as descendants of dest and then destroys the originals. Use
98 98 ``--keep`` to preserve the original source changesets. Some
99 99 changesets in the source branch (e.g. merges from the destination
100 100 branch) may be dropped if they no longer contribute any change.
101 101
102 102 One result of the rules for selecting the destination changeset
103 103 and source branch is that, unlike ``merge``, rebase will do
104 104 nothing if you are at the branch tip of a named branch
105 105 with two heads. You need to explicitly specify source and/or
106 106 destination (or ``update`` to the other head, if it's the head of
107 107 the intended source branch).
108 108
109 109 If a rebase is interrupted to manually resolve a merge, it can be
110 110 continued with --continue/-c or aborted with --abort/-a.
111 111
112 112 Returns 0 on success, 1 if nothing to rebase.
113 113 """
114 114 originalwd = target = None
115 115 activebookmark = None
116 116 external = nullrev
117 117 state = {}
118 118 skipped = set()
119 119 targetancestors = set()
120 120
121 121 editor = None
122 122 if opts.get('edit'):
123 123 editor = cmdutil.commitforceeditor
124 124
125 125 lock = wlock = None
126 126 try:
127 127 wlock = repo.wlock()
128 128 lock = repo.lock()
129 129
130 130 # Validate input and define rebasing points
131 131 destf = opts.get('dest', None)
132 132 srcf = opts.get('source', None)
133 133 basef = opts.get('base', None)
134 134 revf = opts.get('rev', [])
135 135 contf = opts.get('continue')
136 136 abortf = opts.get('abort')
137 137 collapsef = opts.get('collapse', False)
138 138 collapsemsg = cmdutil.logmessage(ui, opts)
139 139 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
140 140 keepf = opts.get('keep', False)
141 141 keepbranchesf = opts.get('keepbranches', False)
142 142 # keepopen is not meant for use on the command line, but by
143 143 # other extensions
144 144 keepopen = opts.get('keepopen', False)
145 145
146 146 if collapsemsg and not collapsef:
147 147 raise util.Abort(
148 148 _('message can only be specified with collapse'))
149 149
150 150 if contf or abortf:
151 151 if contf and abortf:
152 152 raise util.Abort(_('cannot use both abort and continue'))
153 153 if collapsef:
154 154 raise util.Abort(
155 155 _('cannot use collapse with continue or abort'))
156 156 if srcf or basef or destf:
157 157 raise util.Abort(
158 158 _('abort and continue do not allow specifying revisions'))
159 159 if opts.get('tool', False):
160 160 ui.warn(_('tool option will be ignored\n'))
161 161
162 try:
162 163 (originalwd, target, state, skipped, collapsef, keepf,
163 164 keepbranchesf, external, activebookmark) = restorestatus(repo)
165 except error.RepoLookupError:
166 if abortf:
167 clearstatus(repo)
168 repo.ui.warn(_('rebase aborted (no revision is removed,'
169 ' only broken state is cleared)\n'))
170 return 0
171 else:
172 msg = _('cannot continue inconsistent rebase')
173 hint = _('use "hg rebase --abort" to clear borken state')
174 raise util.Abort(msg, hint=hint)
164 175 if abortf:
165 176 return abort(repo, originalwd, target, state)
166 177 else:
167 178 if srcf and basef:
168 179 raise util.Abort(_('cannot specify both a '
169 180 'source and a base'))
170 181 if revf and basef:
171 182 raise util.Abort(_('cannot specify both a '
172 183 'revision and a base'))
173 184 if revf and srcf:
174 185 raise util.Abort(_('cannot specify both a '
175 186 'revision and a source'))
176 187
177 188 cmdutil.checkunfinished(repo)
178 189 cmdutil.bailifchanged(repo)
179 190
180 191 if not destf:
181 192 # Destination defaults to the latest revision in the
182 193 # current branch
183 194 branch = repo[None].branch()
184 195 dest = repo[branch]
185 196 else:
186 197 dest = scmutil.revsingle(repo, destf)
187 198
188 199 if revf:
189 200 rebaseset = scmutil.revrange(repo, revf)
190 201 elif srcf:
191 202 src = scmutil.revrange(repo, [srcf])
192 203 rebaseset = repo.revs('(%ld)::', src)
193 204 else:
194 205 base = scmutil.revrange(repo, [basef or '.'])
195 206 rebaseset = repo.revs(
196 207 '(children(ancestor(%ld, %d)) and ::(%ld))::',
197 208 base, dest, base)
198 209 if rebaseset:
199 210 root = min(rebaseset)
200 211 else:
201 212 root = None
202 213
203 214 if not rebaseset:
204 215 repo.ui.debug('base is ancestor of destination\n')
205 216 result = None
206 217 elif (not (keepf or obsolete._enabled)
207 218 and repo.revs('first(children(%ld) - %ld)',
208 219 rebaseset, rebaseset)):
209 220 raise util.Abort(
210 221 _("can't remove original changesets with"
211 222 " unrebased descendants"),
212 223 hint=_('use --keep to keep original changesets'))
213 224 else:
214 225 result = buildstate(repo, dest, rebaseset, collapsef)
215 226
216 227 if not result:
217 228 # Empty state built, nothing to rebase
218 229 ui.status(_('nothing to rebase\n'))
219 230 return 1
220 231 elif not keepf and not repo[root].mutable():
221 232 raise util.Abort(_("can't rebase immutable changeset %s")
222 233 % repo[root],
223 234 hint=_('see hg help phases for details'))
224 235 else:
225 236 originalwd, target, state = result
226 237 if collapsef:
227 238 targetancestors = repo.changelog.ancestors([target],
228 239 inclusive=True)
229 240 external = checkexternal(repo, state, targetancestors)
230 241
231 242 if keepbranchesf:
232 243 assert not extrafn, 'cannot use both keepbranches and extrafn'
233 244 def extrafn(ctx, extra):
234 245 extra['branch'] = ctx.branch()
235 246 if collapsef:
236 247 branches = set()
237 248 for rev in state:
238 249 branches.add(repo[rev].branch())
239 250 if len(branches) > 1:
240 251 raise util.Abort(_('cannot collapse multiple named '
241 252 'branches'))
242 253
243 254
244 255 # Rebase
245 256 if not targetancestors:
246 257 targetancestors = repo.changelog.ancestors([target], inclusive=True)
247 258
248 259 # Keep track of the current bookmarks in order to reset them later
249 260 currentbookmarks = repo._bookmarks.copy()
250 261 activebookmark = activebookmark or repo._bookmarkcurrent
251 262 if activebookmark:
252 263 bookmarks.unsetcurrent(repo)
253 264
254 265 sortedstate = sorted(state)
255 266 total = len(sortedstate)
256 267 pos = 0
257 268 for rev in sortedstate:
258 269 pos += 1
259 270 if state[rev] == -1:
260 271 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
261 272 _('changesets'), total)
262 273 p1, p2 = defineparents(repo, rev, target, state,
263 274 targetancestors)
264 275 storestatus(repo, originalwd, target, state, collapsef, keepf,
265 276 keepbranchesf, external, activebookmark)
266 277 if len(repo.parents()) == 2:
267 278 repo.ui.debug('resuming interrupted rebase\n')
268 279 else:
269 280 try:
270 281 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
271 282 stats = rebasenode(repo, rev, p1, state, collapsef)
272 283 if stats and stats[3] > 0:
273 284 raise error.InterventionRequired(
274 285 _('unresolved conflicts (see hg '
275 286 'resolve, then hg rebase --continue)'))
276 287 finally:
277 288 ui.setconfig('ui', 'forcemerge', '')
278 289 cmdutil.duplicatecopies(repo, rev, target)
279 290 if not collapsef:
280 291 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
281 292 editor=editor)
282 293 else:
283 294 # Skip commit if we are collapsing
284 295 repo.setparents(repo[p1].node())
285 296 newrev = None
286 297 # Update the state
287 298 if newrev is not None:
288 299 state[rev] = repo[newrev].rev()
289 300 else:
290 301 if not collapsef:
291 302 ui.note(_('no changes, revision %d skipped\n') % rev)
292 303 ui.debug('next revision set to %s\n' % p1)
293 304 skipped.add(rev)
294 305 state[rev] = p1
295 306
296 307 ui.progress(_('rebasing'), None)
297 308 ui.note(_('rebase merging completed\n'))
298 309
299 310 if collapsef and not keepopen:
300 311 p1, p2 = defineparents(repo, min(state), target,
301 312 state, targetancestors)
302 313 if collapsemsg:
303 314 commitmsg = collapsemsg
304 315 else:
305 316 commitmsg = 'Collapsed revision'
306 317 for rebased in state:
307 318 if rebased not in skipped and state[rebased] > nullmerge:
308 319 commitmsg += '\n* %s' % repo[rebased].description()
309 320 commitmsg = ui.edit(commitmsg, repo.ui.username())
310 321 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
311 322 extrafn=extrafn, editor=editor)
312 323
313 324 if 'qtip' in repo.tags():
314 325 updatemq(repo, state, skipped, **opts)
315 326
316 327 if currentbookmarks:
317 328 # Nodeids are needed to reset bookmarks
318 329 nstate = {}
319 330 for k, v in state.iteritems():
320 331 if v > nullmerge:
321 332 nstate[repo[k].node()] = repo[v].node()
322 333 # XXX this is the same as dest.node() for the non-continue path --
323 334 # this should probably be cleaned up
324 335 targetnode = repo[target].node()
325 336
326 337 if not keepf:
327 338 collapsedas = None
328 339 if collapsef:
329 340 collapsedas = newrev
330 341 clearrebased(ui, repo, state, skipped, collapsedas)
331 342
332 343 if currentbookmarks:
333 344 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
334 345
335 346 clearstatus(repo)
336 347 ui.note(_("rebase completed\n"))
337 348 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
338 349 if skipped:
339 350 ui.note(_("%d revisions have been skipped\n") % len(skipped))
340 351
341 352 if (activebookmark and
342 353 repo['tip'].node() == repo._bookmarks[activebookmark]):
343 354 bookmarks.setcurrent(repo, activebookmark)
344 355
345 356 finally:
346 357 release(lock, wlock)
347 358
348 359 def checkexternal(repo, state, targetancestors):
349 360 """Check whether one or more external revisions need to be taken in
350 361 consideration. In the latter case, abort.
351 362 """
352 363 external = nullrev
353 364 source = min(state)
354 365 for rev in state:
355 366 if rev == source:
356 367 continue
357 368 # Check externals and fail if there are more than one
358 369 for p in repo[rev].parents():
359 370 if (p.rev() not in state
360 371 and p.rev() not in targetancestors):
361 372 if external != nullrev:
362 373 raise util.Abort(_('unable to collapse, there is more '
363 374 'than one external parent'))
364 375 external = p.rev()
365 376 return external
366 377
367 378 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
368 379 'Commit the changes and store useful information in extra'
369 380 try:
370 381 repo.setparents(repo[p1].node(), repo[p2].node())
371 382 ctx = repo[rev]
372 383 if commitmsg is None:
373 384 commitmsg = ctx.description()
374 385 extra = {'rebase_source': ctx.hex()}
375 386 if extrafn:
376 387 extrafn(ctx, extra)
377 388 # Commit might fail if unresolved files exist
378 389 newrev = repo.commit(text=commitmsg, user=ctx.user(),
379 390 date=ctx.date(), extra=extra, editor=editor)
380 391 repo.dirstate.setbranch(repo[newrev].branch())
381 392 targetphase = max(ctx.phase(), phases.draft)
382 393 # retractboundary doesn't overwrite upper phase inherited from parent
383 394 newnode = repo[newrev].node()
384 395 if newnode:
385 396 phases.retractboundary(repo, targetphase, [newnode])
386 397 return newrev
387 398 except util.Abort:
388 399 # Invalidate the previous setparents
389 400 repo.dirstate.invalidate()
390 401 raise
391 402
392 403 def rebasenode(repo, rev, p1, state, collapse):
393 404 'Rebase a single revision'
394 405 # Merge phase
395 406 # Update to target and merge it with local
396 407 if repo['.'].rev() != repo[p1].rev():
397 408 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
398 409 merge.update(repo, p1, False, True, False)
399 410 else:
400 411 repo.ui.debug(" already in target\n")
401 412 repo.dirstate.write()
402 413 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
403 414 base = None
404 415 if repo[rev].rev() != repo[min(state)].rev():
405 416 base = repo[rev].p1().node()
406 417 # When collapsing in-place, the parent is the common ancestor, we
407 418 # have to allow merging with it.
408 419 return merge.update(repo, rev, True, True, False, base, collapse)
409 420
410 421 def nearestrebased(repo, rev, state):
411 422 """return the nearest ancestors of rev in the rebase result"""
412 423 rebased = [r for r in state if state[r] > nullmerge]
413 424 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
414 425 if candidates:
415 426 return state[candidates[0]]
416 427 else:
417 428 return None
418 429
419 430 def defineparents(repo, rev, target, state, targetancestors):
420 431 'Return the new parent relationship of the revision that will be rebased'
421 432 parents = repo[rev].parents()
422 433 p1 = p2 = nullrev
423 434
424 435 P1n = parents[0].rev()
425 436 if P1n in targetancestors:
426 437 p1 = target
427 438 elif P1n in state:
428 439 if state[P1n] == nullmerge:
429 440 p1 = target
430 441 elif state[P1n] == revignored:
431 442 p1 = nearestrebased(repo, P1n, state)
432 443 if p1 is None:
433 444 p1 = target
434 445 else:
435 446 p1 = state[P1n]
436 447 else: # P1n external
437 448 p1 = target
438 449 p2 = P1n
439 450
440 451 if len(parents) == 2 and parents[1].rev() not in targetancestors:
441 452 P2n = parents[1].rev()
442 453 # interesting second parent
443 454 if P2n in state:
444 455 if p1 == target: # P1n in targetancestors or external
445 456 p1 = state[P2n]
446 457 elif state[P2n] == revignored:
447 458 p2 = nearestrebased(repo, P2n, state)
448 459 if p2 is None:
449 460 # no ancestors rebased yet, detach
450 461 p2 = target
451 462 else:
452 463 p2 = state[P2n]
453 464 else: # P2n external
454 465 if p2 != nullrev: # P1n external too => rev is a merged revision
455 466 raise util.Abort(_('cannot use revision %d as base, result '
456 467 'would have 3 parents') % rev)
457 468 p2 = P2n
458 469 repo.ui.debug(" future parents are %d and %d\n" %
459 470 (repo[p1].rev(), repo[p2].rev()))
460 471 return p1, p2
461 472
462 473 def isagitpatch(repo, patchname):
463 474 'Return true if the given patch is in git format'
464 475 mqpatch = os.path.join(repo.mq.path, patchname)
465 476 for line in patch.linereader(file(mqpatch, 'rb')):
466 477 if line.startswith('diff --git'):
467 478 return True
468 479 return False
469 480
470 481 def updatemq(repo, state, skipped, **opts):
471 482 'Update rebased mq patches - finalize and then import them'
472 483 mqrebase = {}
473 484 mq = repo.mq
474 485 original_series = mq.fullseries[:]
475 486 skippedpatches = set()
476 487
477 488 for p in mq.applied:
478 489 rev = repo[p.node].rev()
479 490 if rev in state:
480 491 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
481 492 (rev, p.name))
482 493 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
483 494 else:
484 495 # Applied but not rebased, not sure this should happen
485 496 skippedpatches.add(p.name)
486 497
487 498 if mqrebase:
488 499 mq.finish(repo, mqrebase.keys())
489 500
490 501 # We must start import from the newest revision
491 502 for rev in sorted(mqrebase, reverse=True):
492 503 if rev not in skipped:
493 504 name, isgit = mqrebase[rev]
494 505 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
495 506 mq.qimport(repo, (), patchname=name, git=isgit,
496 507 rev=[str(state[rev])])
497 508 else:
498 509 # Rebased and skipped
499 510 skippedpatches.add(mqrebase[rev][0])
500 511
501 512 # Patches were either applied and rebased and imported in
502 513 # order, applied and removed or unapplied. Discard the removed
503 514 # ones while preserving the original series order and guards.
504 515 newseries = [s for s in original_series
505 516 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
506 517 mq.fullseries[:] = newseries
507 518 mq.seriesdirty = True
508 519 mq.savedirty()
509 520
510 521 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
511 522 'Move bookmarks to their correct changesets, and delete divergent ones'
512 523 marks = repo._bookmarks
513 524 for k, v in originalbookmarks.iteritems():
514 525 if v in nstate:
515 526 # update the bookmarks for revs that have moved
516 527 marks[k] = nstate[v]
517 528 bookmarks.deletedivergent(repo, [targetnode], k)
518 529
519 530 marks.write()
520 531
521 532 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
522 533 external, activebookmark):
523 534 'Store the current status to allow recovery'
524 535 f = repo.opener("rebasestate", "w")
525 536 f.write(repo[originalwd].hex() + '\n')
526 537 f.write(repo[target].hex() + '\n')
527 538 f.write(repo[external].hex() + '\n')
528 539 f.write('%d\n' % int(collapse))
529 540 f.write('%d\n' % int(keep))
530 541 f.write('%d\n' % int(keepbranches))
531 542 f.write('%s\n' % (activebookmark or ''))
532 543 for d, v in state.iteritems():
533 544 oldrev = repo[d].hex()
534 545 if v > nullmerge:
535 546 newrev = repo[v].hex()
536 547 else:
537 548 newrev = v
538 549 f.write("%s:%s\n" % (oldrev, newrev))
539 550 f.close()
540 551 repo.ui.debug('rebase status stored\n')
541 552
542 553 def clearstatus(repo):
543 554 'Remove the status files'
544 555 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
545 556
546 557 def restorestatus(repo):
547 558 'Restore a previously stored status'
548 559 try:
549 560 target = None
550 561 collapse = False
551 562 external = nullrev
552 563 activebookmark = None
553 564 state = {}
554 565 f = repo.opener("rebasestate")
555 566 for i, l in enumerate(f.read().splitlines()):
556 567 if i == 0:
557 568 originalwd = repo[l].rev()
558 569 elif i == 1:
559 570 target = repo[l].rev()
560 571 elif i == 2:
561 572 external = repo[l].rev()
562 573 elif i == 3:
563 574 collapse = bool(int(l))
564 575 elif i == 4:
565 576 keep = bool(int(l))
566 577 elif i == 5:
567 578 keepbranches = bool(int(l))
568 579 elif i == 6 and not (len(l) == 81 and ':' in l):
569 580 # line 6 is a recent addition, so for backwards compatibility
570 581 # check that the line doesn't look like the oldrev:newrev lines
571 582 activebookmark = l
572 583 else:
573 584 oldrev, newrev = l.split(':')
574 585 if newrev in (str(nullmerge), str(revignored)):
575 586 state[repo[oldrev].rev()] = int(newrev)
576 587 else:
577 588 state[repo[oldrev].rev()] = repo[newrev].rev()
578 589 skipped = set()
579 590 # recompute the set of skipped revs
580 591 if not collapse:
581 592 seen = set([target])
582 593 for old, new in sorted(state.items()):
583 594 if new != nullrev and new in seen:
584 595 skipped.add(old)
585 596 seen.add(new)
586 597 repo.ui.debug('computed skipped revs: %s\n' % skipped)
587 598 repo.ui.debug('rebase status resumed\n')
588 599 return (originalwd, target, state, skipped,
589 600 collapse, keep, keepbranches, external, activebookmark)
590 601 except IOError, err:
591 602 if err.errno != errno.ENOENT:
592 603 raise
593 604 raise util.Abort(_('no rebase in progress'))
594 605
595 606 def inrebase(repo, originalwd, state):
596 607 '''check whether the workdir is in an interrupted rebase'''
597 608 parents = [p.rev() for p in repo.parents()]
598 609 if originalwd in parents:
599 610 return True
600 611
601 612 for newrev in state.itervalues():
602 613 if newrev in parents:
603 614 return True
604 615
605 616 return False
606 617
607 618 def abort(repo, originalwd, target, state):
608 619 'Restore the repository to its original state'
609 620 dstates = [s for s in state.values() if s != nullrev]
610 621 immutable = [d for d in dstates if not repo[d].mutable()]
611 622 cleanup = True
612 623 if immutable:
613 624 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
614 625 % ', '.join(str(repo[r]) for r in immutable),
615 626 hint=_('see hg help phases for details'))
616 627 cleanup = False
617 628
618 629 descendants = set()
619 630 if dstates:
620 631 descendants = set(repo.changelog.descendants(dstates))
621 632 if descendants - set(dstates):
622 633 repo.ui.warn(_("warning: new changesets detected on target branch, "
623 634 "can't strip\n"))
624 635 cleanup = False
625 636
626 637 if cleanup:
627 638 # Update away from the rebase if necessary
628 639 if inrebase(repo, originalwd, state):
629 640 merge.update(repo, repo[originalwd].rev(), False, True, False)
630 641
631 642 # Strip from the first rebased revision
632 643 rebased = filter(lambda x: x > -1 and x != target, state.values())
633 644 if rebased:
634 645 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
635 646 # no backup of rebased cset versions needed
636 647 repair.strip(repo.ui, repo, strippoints)
637 648
638 649 clearstatus(repo)
639 650 repo.ui.warn(_('rebase aborted\n'))
640 651 return 0
641 652
642 653 def buildstate(repo, dest, rebaseset, collapse):
643 654 '''Define which revisions are going to be rebased and where
644 655
645 656 repo: repo
646 657 dest: context
647 658 rebaseset: set of rev
648 659 '''
649 660
650 661 # This check isn't strictly necessary, since mq detects commits over an
651 662 # applied patch. But it prevents messing up the working directory when
652 663 # a partially completed rebase is blocked by mq.
653 664 if 'qtip' in repo.tags() and (dest.node() in
654 665 [s.node for s in repo.mq.applied]):
655 666 raise util.Abort(_('cannot rebase onto an applied mq patch'))
656 667
657 668 roots = list(repo.set('roots(%ld)', rebaseset))
658 669 if not roots:
659 670 raise util.Abort(_('no matching revisions'))
660 671 roots.sort()
661 672 state = {}
662 673 detachset = set()
663 674 for root in roots:
664 675 commonbase = root.ancestor(dest)
665 676 if commonbase == root:
666 677 raise util.Abort(_('source is ancestor of destination'))
667 678 if commonbase == dest:
668 679 samebranch = root.branch() == dest.branch()
669 680 if not collapse and samebranch and root in dest.children():
670 681 repo.ui.debug('source is a child of destination\n')
671 682 return None
672 683
673 684 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, roots))
674 685 state.update(dict.fromkeys(rebaseset, nullrev))
675 686 # Rebase tries to turn <dest> into a parent of <root> while
676 687 # preserving the number of parents of rebased changesets:
677 688 #
678 689 # - A changeset with a single parent will always be rebased as a
679 690 # changeset with a single parent.
680 691 #
681 692 # - A merge will be rebased as merge unless its parents are both
682 693 # ancestors of <dest> or are themselves in the rebased set and
683 694 # pruned while rebased.
684 695 #
685 696 # If one parent of <root> is an ancestor of <dest>, the rebased
686 697 # version of this parent will be <dest>. This is always true with
687 698 # --base option.
688 699 #
689 700 # Otherwise, we need to *replace* the original parents with
690 701 # <dest>. This "detaches" the rebased set from its former location
691 702 # and rebases it onto <dest>. Changes introduced by ancestors of
692 703 # <root> not common with <dest> (the detachset, marked as
693 704 # nullmerge) are "removed" from the rebased changesets.
694 705 #
695 706 # - If <root> has a single parent, set it to <dest>.
696 707 #
697 708 # - If <root> is a merge, we cannot decide which parent to
698 709 # replace, the rebase operation is not clearly defined.
699 710 #
700 711 # The table below sums up this behavior:
701 712 #
702 713 # +------------------+----------------------+-------------------------+
703 714 # | | one parent | merge |
704 715 # +------------------+----------------------+-------------------------+
705 716 # | parent in | new parent is <dest> | parents in ::<dest> are |
706 717 # | ::<dest> | | remapped to <dest> |
707 718 # +------------------+----------------------+-------------------------+
708 719 # | unrelated source | new parent is <dest> | ambiguous, abort |
709 720 # +------------------+----------------------+-------------------------+
710 721 #
711 722 # The actual abort is handled by `defineparents`
712 723 if len(root.parents()) <= 1:
713 724 # ancestors of <root> not ancestors of <dest>
714 725 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
715 726 [root.rev()]))
716 727 for r in detachset:
717 728 if r not in state:
718 729 state[r] = nullmerge
719 730 if len(roots) > 1:
720 731 # If we have multiple roots, we may have "hole" in the rebase set.
721 732 # Rebase roots that descend from those "hole" should not be detached as
722 733 # other root are. We use the special `revignored` to inform rebase that
723 734 # the revision should be ignored but that `defineparents` should search
724 735 # a rebase destination that make sense regarding rebased topology.
725 736 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
726 737 for ignored in set(rebasedomain) - set(rebaseset):
727 738 state[ignored] = revignored
728 739 return repo['.'].rev(), dest.rev(), state
729 740
730 741 def clearrebased(ui, repo, state, skipped, collapsedas=None):
731 742 """dispose of rebased revision at the end of the rebase
732 743
733 744 If `collapsedas` is not None, the rebase was a collapse whose result if the
734 745 `collapsedas` node."""
735 746 if obsolete._enabled:
736 747 markers = []
737 748 for rev, newrev in sorted(state.items()):
738 749 if newrev >= 0:
739 750 if rev in skipped:
740 751 succs = ()
741 752 elif collapsedas is not None:
742 753 succs = (repo[collapsedas],)
743 754 else:
744 755 succs = (repo[newrev],)
745 756 markers.append((repo[rev], succs))
746 757 if markers:
747 758 obsolete.createmarkers(repo, markers)
748 759 else:
749 760 rebased = [rev for rev in state if state[rev] > nullmerge]
750 761 if rebased:
751 762 stripped = []
752 763 for root in repo.set('roots(%ld)', rebased):
753 764 if set(repo.changelog.descendants([root.rev()])) - set(state):
754 765 ui.warn(_("warning: new changesets detected "
755 766 "on source branch, not stripping\n"))
756 767 else:
757 768 stripped.append(root.node())
758 769 if stripped:
759 770 # backup the old csets by default
760 771 repair.strip(ui, repo, stripped, "all")
761 772
762 773
763 774 def pullrebase(orig, ui, repo, *args, **opts):
764 775 'Call rebase after pull if the latter has been invoked with --rebase'
765 776 if opts.get('rebase'):
766 777 if opts.get('update'):
767 778 del opts['update']
768 779 ui.debug('--update and --rebase are not compatible, ignoring '
769 780 'the update flag\n')
770 781
771 782 movemarkfrom = repo['.'].node()
772 783 revsprepull = len(repo)
773 784 origpostincoming = commands.postincoming
774 785 def _dummy(*args, **kwargs):
775 786 pass
776 787 commands.postincoming = _dummy
777 788 try:
778 789 orig(ui, repo, *args, **opts)
779 790 finally:
780 791 commands.postincoming = origpostincoming
781 792 revspostpull = len(repo)
782 793 if revspostpull > revsprepull:
783 794 # --rev option from pull conflict with rebase own --rev
784 795 # dropping it
785 796 if 'rev' in opts:
786 797 del opts['rev']
787 798 rebase(ui, repo, **opts)
788 799 branch = repo[None].branch()
789 800 dest = repo[branch].rev()
790 801 if dest != repo['.'].rev():
791 802 # there was nothing to rebase we force an update
792 803 hg.update(repo, dest)
793 804 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
794 805 ui.status(_("updating bookmark %s\n")
795 806 % repo._bookmarkcurrent)
796 807 else:
797 808 if opts.get('tool'):
798 809 raise util.Abort(_('--tool can only be used with --rebase'))
799 810 orig(ui, repo, *args, **opts)
800 811
801 812 def summaryhook(ui, repo):
802 813 if not os.path.exists(repo.join('rebasestate')):
803 814 return
815 try:
804 816 state = restorestatus(repo)[2]
817 except error.RepoLookupError:
818 # i18n: column positioning for "hg summary"
819 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
820 ui.write(msg)
821 return
805 822 numrebased = len([i for i in state.itervalues() if i != -1])
806 823 # i18n: column positioning for "hg summary"
807 824 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
808 825 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
809 826 ui.label(_('%d remaining'), 'rebase.remaining') %
810 827 (len(state) - numrebased)))
811 828
812 829 def uisetup(ui):
813 830 'Replace pull with a decorator to provide --rebase option'
814 831 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
815 832 entry[1].append(('', 'rebase', None,
816 833 _("rebase working directory to branch head")))
817 834 entry[1].append(('t', 'tool', '',
818 835 _("specify merge tool for rebase")))
819 836 cmdutil.summaryhooks.add('rebase', summaryhook)
820 837 cmdutil.unfinishedstates.append(
821 838 ['rebasestate', False, False, _('rebase in progress'),
822 839 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
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