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

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

@@ -1,33 +1,34
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 e3bf16703e2601de99e563cdb3a5d50b64e6d320 0 iD8DBQBNH8WqywK+sNU5EO8RAiQTAJ9sBO+TeiGro4si77VVaQaA6jcRUgCfSA28dBbjj0oFoQwvPoZjANiZBH8=
@@ -1,45 +1,46
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 e3bf16703e2601de99e563cdb3a5d50b64e6d320 1.7.3
@@ -1,3241 +1,3241
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 You will by default be managing a patch queue named "patches". You can
42 42 create other, independent patch queues with the :hg:`qqueue` command.
43 43 '''
44 44
45 45 from mercurial.i18n import _
46 46 from mercurial.node import bin, hex, short, nullid, nullrev
47 47 from mercurial.lock import release
48 48 from mercurial import commands, cmdutil, hg, patch, util
49 49 from mercurial import repair, extensions, url, error
50 50 import os, sys, re, errno, shutil
51 51
52 52 commands.norepo += " qclone"
53 53
54 54 # Patch names looks like unix-file names.
55 55 # They must be joinable with queue directory and result in the patch path.
56 56 normname = util.normpath
57 57
58 58 class statusentry(object):
59 59 def __init__(self, node, name):
60 60 self.node, self.name = node, name
61 61 def __repr__(self):
62 62 return hex(self.node) + ':' + self.name
63 63
64 64 class patchheader(object):
65 65 def __init__(self, pf, plainmode=False):
66 66 def eatdiff(lines):
67 67 while lines:
68 68 l = lines[-1]
69 69 if (l.startswith("diff -") or
70 70 l.startswith("Index:") or
71 71 l.startswith("===========")):
72 72 del lines[-1]
73 73 else:
74 74 break
75 75 def eatempty(lines):
76 76 while lines:
77 77 if not lines[-1].strip():
78 78 del lines[-1]
79 79 else:
80 80 break
81 81
82 82 message = []
83 83 comments = []
84 84 user = None
85 85 date = None
86 86 parent = None
87 87 format = None
88 88 subject = None
89 89 diffstart = 0
90 90
91 91 for line in file(pf):
92 92 line = line.rstrip()
93 93 if (line.startswith('diff --git')
94 94 or (diffstart and line.startswith('+++ '))):
95 95 diffstart = 2
96 96 break
97 97 diffstart = 0 # reset
98 98 if line.startswith("--- "):
99 99 diffstart = 1
100 100 continue
101 101 elif format == "hgpatch":
102 102 # parse values when importing the result of an hg export
103 103 if line.startswith("# User "):
104 104 user = line[7:]
105 105 elif line.startswith("# Date "):
106 106 date = line[7:]
107 107 elif line.startswith("# Parent "):
108 108 parent = line[9:]
109 109 elif not line.startswith("# ") and line:
110 110 message.append(line)
111 111 format = None
112 112 elif line == '# HG changeset patch':
113 113 message = []
114 114 format = "hgpatch"
115 115 elif (format != "tagdone" and (line.startswith("Subject: ") or
116 116 line.startswith("subject: "))):
117 117 subject = line[9:]
118 118 format = "tag"
119 119 elif (format != "tagdone" and (line.startswith("From: ") or
120 120 line.startswith("from: "))):
121 121 user = line[6:]
122 122 format = "tag"
123 123 elif (format != "tagdone" and (line.startswith("Date: ") or
124 124 line.startswith("date: "))):
125 125 date = line[6:]
126 126 format = "tag"
127 127 elif format == "tag" and line == "":
128 128 # when looking for tags (subject: from: etc) they
129 129 # end once you find a blank line in the source
130 130 format = "tagdone"
131 131 elif message or line:
132 132 message.append(line)
133 133 comments.append(line)
134 134
135 135 eatdiff(message)
136 136 eatdiff(comments)
137 137 eatempty(message)
138 138 eatempty(comments)
139 139
140 140 # make sure message isn't empty
141 141 if format and format.startswith("tag") and subject:
142 142 message.insert(0, "")
143 143 message.insert(0, subject)
144 144
145 145 self.message = message
146 146 self.comments = comments
147 147 self.user = user
148 148 self.date = date
149 149 self.parent = parent
150 150 self.haspatch = diffstart > 1
151 151 self.plainmode = plainmode
152 152
153 153 def setuser(self, user):
154 154 if not self.updateheader(['From: ', '# User '], user):
155 155 try:
156 156 patchheaderat = self.comments.index('# HG changeset patch')
157 157 self.comments.insert(patchheaderat + 1, '# User ' + user)
158 158 except ValueError:
159 159 if self.plainmode or self._hasheader(['Date: ']):
160 160 self.comments = ['From: ' + user] + self.comments
161 161 else:
162 162 tmp = ['# HG changeset patch', '# User ' + user, '']
163 163 self.comments = tmp + self.comments
164 164 self.user = user
165 165
166 166 def setdate(self, date):
167 167 if not self.updateheader(['Date: ', '# Date '], date):
168 168 try:
169 169 patchheaderat = self.comments.index('# HG changeset patch')
170 170 self.comments.insert(patchheaderat + 1, '# Date ' + date)
171 171 except ValueError:
172 172 if self.plainmode or self._hasheader(['From: ']):
173 173 self.comments = ['Date: ' + date] + self.comments
174 174 else:
175 175 tmp = ['# HG changeset patch', '# Date ' + date, '']
176 176 self.comments = tmp + self.comments
177 177 self.date = date
178 178
179 179 def setparent(self, parent):
180 180 if not self.updateheader(['# Parent '], parent):
181 181 try:
182 182 patchheaderat = self.comments.index('# HG changeset patch')
183 183 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
184 184 except ValueError:
185 185 pass
186 186 self.parent = parent
187 187
188 188 def setmessage(self, message):
189 189 if self.comments:
190 190 self._delmsg()
191 191 self.message = [message]
192 192 self.comments += self.message
193 193
194 194 def updateheader(self, prefixes, new):
195 195 '''Update all references to a field in the patch header.
196 196 Return whether the field is present.'''
197 197 res = False
198 198 for prefix in prefixes:
199 199 for i in xrange(len(self.comments)):
200 200 if self.comments[i].startswith(prefix):
201 201 self.comments[i] = prefix + new
202 202 res = True
203 203 break
204 204 return res
205 205
206 206 def _hasheader(self, prefixes):
207 207 '''Check if a header starts with any of the given prefixes.'''
208 208 for prefix in prefixes:
209 209 for comment in self.comments:
210 210 if comment.startswith(prefix):
211 211 return True
212 212 return False
213 213
214 214 def __str__(self):
215 215 if not self.comments:
216 216 return ''
217 217 return '\n'.join(self.comments) + '\n\n'
218 218
219 219 def _delmsg(self):
220 220 '''Remove existing message, keeping the rest of the comments fields.
221 221 If comments contains 'subject: ', message will prepend
222 222 the field and a blank line.'''
223 223 if self.message:
224 224 subj = 'subject: ' + self.message[0].lower()
225 225 for i in xrange(len(self.comments)):
226 226 if subj == self.comments[i].lower():
227 227 del self.comments[i]
228 228 self.message = self.message[2:]
229 229 break
230 230 ci = 0
231 231 for mi in self.message:
232 232 while mi != self.comments[ci]:
233 233 ci += 1
234 234 del self.comments[ci]
235 235
236 236 class queue(object):
237 237 def __init__(self, ui, path, patchdir=None):
238 238 self.basepath = path
239 239 try:
240 240 fh = open(os.path.join(path, 'patches.queue'))
241 241 cur = fh.read().rstrip()
242 242 if not cur:
243 243 curpath = os.path.join(path, 'patches')
244 244 else:
245 245 curpath = os.path.join(path, 'patches-' + cur)
246 246 except IOError:
247 247 curpath = os.path.join(path, 'patches')
248 248 self.path = patchdir or curpath
249 249 self.opener = util.opener(self.path)
250 250 self.ui = ui
251 251 self.applied_dirty = 0
252 252 self.series_dirty = 0
253 253 self.added = []
254 254 self.series_path = "series"
255 255 self.status_path = "status"
256 256 self.guards_path = "guards"
257 257 self.active_guards = None
258 258 self.guards_dirty = False
259 259 # Handle mq.git as a bool with extended values
260 260 try:
261 261 gitmode = ui.configbool('mq', 'git', None)
262 262 if gitmode is None:
263 263 raise error.ConfigError()
264 264 self.gitmode = gitmode and 'yes' or 'no'
265 265 except error.ConfigError:
266 266 self.gitmode = ui.config('mq', 'git', 'auto').lower()
267 267 self.plainmode = ui.configbool('mq', 'plain', False)
268 268
269 269 @util.propertycache
270 270 def applied(self):
271 271 if os.path.exists(self.join(self.status_path)):
272 272 def parse(l):
273 273 n, name = l.split(':', 1)
274 274 return statusentry(bin(n), name)
275 275 lines = self.opener(self.status_path).read().splitlines()
276 276 return [parse(l) for l in lines]
277 277 return []
278 278
279 279 @util.propertycache
280 280 def full_series(self):
281 281 if os.path.exists(self.join(self.series_path)):
282 282 return self.opener(self.series_path).read().splitlines()
283 283 return []
284 284
285 285 @util.propertycache
286 286 def series(self):
287 287 self.parse_series()
288 288 return self.series
289 289
290 290 @util.propertycache
291 291 def series_guards(self):
292 292 self.parse_series()
293 293 return self.series_guards
294 294
295 295 def invalidate(self):
296 296 for a in 'applied full_series series series_guards'.split():
297 297 if a in self.__dict__:
298 298 delattr(self, a)
299 299 self.applied_dirty = 0
300 300 self.series_dirty = 0
301 301 self.guards_dirty = False
302 302 self.active_guards = None
303 303
304 304 def diffopts(self, opts={}, patchfn=None):
305 305 diffopts = patch.diffopts(self.ui, opts)
306 306 if self.gitmode == 'auto':
307 307 diffopts.upgrade = True
308 308 elif self.gitmode == 'keep':
309 309 pass
310 310 elif self.gitmode in ('yes', 'no'):
311 311 diffopts.git = self.gitmode == 'yes'
312 312 else:
313 313 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
314 314 ' got %s') % self.gitmode)
315 315 if patchfn:
316 316 diffopts = self.patchopts(diffopts, patchfn)
317 317 return diffopts
318 318
319 319 def patchopts(self, diffopts, *patches):
320 320 """Return a copy of input diff options with git set to true if
321 321 referenced patch is a git patch and should be preserved as such.
322 322 """
323 323 diffopts = diffopts.copy()
324 324 if not diffopts.git and self.gitmode == 'keep':
325 325 for patchfn in patches:
326 326 patchf = self.opener(patchfn, 'r')
327 327 # if the patch was a git patch, refresh it as a git patch
328 328 for line in patchf:
329 329 if line.startswith('diff --git'):
330 330 diffopts.git = True
331 331 break
332 332 patchf.close()
333 333 return diffopts
334 334
335 335 def join(self, *p):
336 336 return os.path.join(self.path, *p)
337 337
338 338 def find_series(self, patch):
339 339 def matchpatch(l):
340 340 l = l.split('#', 1)[0]
341 341 return l.strip() == patch
342 342 for index, l in enumerate(self.full_series):
343 343 if matchpatch(l):
344 344 return index
345 345 return None
346 346
347 347 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
348 348
349 349 def parse_series(self):
350 350 self.series = []
351 351 self.series_guards = []
352 352 for l in self.full_series:
353 353 h = l.find('#')
354 354 if h == -1:
355 355 patch = l
356 356 comment = ''
357 357 elif h == 0:
358 358 continue
359 359 else:
360 360 patch = l[:h]
361 361 comment = l[h:]
362 362 patch = patch.strip()
363 363 if patch:
364 364 if patch in self.series:
365 365 raise util.Abort(_('%s appears more than once in %s') %
366 366 (patch, self.join(self.series_path)))
367 367 self.series.append(patch)
368 368 self.series_guards.append(self.guard_re.findall(comment))
369 369
370 370 def check_guard(self, guard):
371 371 if not guard:
372 372 return _('guard cannot be an empty string')
373 373 bad_chars = '# \t\r\n\f'
374 374 first = guard[0]
375 375 if first in '-+':
376 376 return (_('guard %r starts with invalid character: %r') %
377 377 (guard, first))
378 378 for c in bad_chars:
379 379 if c in guard:
380 380 return _('invalid character in guard %r: %r') % (guard, c)
381 381
382 382 def set_active(self, guards):
383 383 for guard in guards:
384 384 bad = self.check_guard(guard)
385 385 if bad:
386 386 raise util.Abort(bad)
387 387 guards = sorted(set(guards))
388 388 self.ui.debug('active guards: %s\n' % ' '.join(guards))
389 389 self.active_guards = guards
390 390 self.guards_dirty = True
391 391
392 392 def active(self):
393 393 if self.active_guards is None:
394 394 self.active_guards = []
395 395 try:
396 396 guards = self.opener(self.guards_path).read().split()
397 397 except IOError, err:
398 398 if err.errno != errno.ENOENT:
399 399 raise
400 400 guards = []
401 401 for i, guard in enumerate(guards):
402 402 bad = self.check_guard(guard)
403 403 if bad:
404 404 self.ui.warn('%s:%d: %s\n' %
405 405 (self.join(self.guards_path), i + 1, bad))
406 406 else:
407 407 self.active_guards.append(guard)
408 408 return self.active_guards
409 409
410 410 def set_guards(self, idx, guards):
411 411 for g in guards:
412 412 if len(g) < 2:
413 413 raise util.Abort(_('guard %r too short') % g)
414 414 if g[0] not in '-+':
415 415 raise util.Abort(_('guard %r starts with invalid char') % g)
416 416 bad = self.check_guard(g[1:])
417 417 if bad:
418 418 raise util.Abort(bad)
419 419 drop = self.guard_re.sub('', self.full_series[idx])
420 420 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
421 421 self.parse_series()
422 422 self.series_dirty = True
423 423
424 424 def pushable(self, idx):
425 425 if isinstance(idx, str):
426 426 idx = self.series.index(idx)
427 427 patchguards = self.series_guards[idx]
428 428 if not patchguards:
429 429 return True, None
430 430 guards = self.active()
431 431 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
432 432 if exactneg:
433 433 return False, exactneg[0]
434 434 pos = [g for g in patchguards if g[0] == '+']
435 435 exactpos = [g for g in pos if g[1:] in guards]
436 436 if pos:
437 437 if exactpos:
438 438 return True, exactpos[0]
439 439 return False, pos
440 440 return True, ''
441 441
442 442 def explain_pushable(self, idx, all_patches=False):
443 443 write = all_patches and self.ui.write or self.ui.warn
444 444 if all_patches or self.ui.verbose:
445 445 if isinstance(idx, str):
446 446 idx = self.series.index(idx)
447 447 pushable, why = self.pushable(idx)
448 448 if all_patches and pushable:
449 449 if why is None:
450 450 write(_('allowing %s - no guards in effect\n') %
451 451 self.series[idx])
452 452 else:
453 453 if not why:
454 454 write(_('allowing %s - no matching negative guards\n') %
455 455 self.series[idx])
456 456 else:
457 457 write(_('allowing %s - guarded by %r\n') %
458 458 (self.series[idx], why))
459 459 if not pushable:
460 460 if why:
461 461 write(_('skipping %s - guarded by %r\n') %
462 462 (self.series[idx], why))
463 463 else:
464 464 write(_('skipping %s - no matching guards\n') %
465 465 self.series[idx])
466 466
467 467 def save_dirty(self):
468 468 def write_list(items, path):
469 469 fp = self.opener(path, 'w')
470 470 for i in items:
471 471 fp.write("%s\n" % i)
472 472 fp.close()
473 473 if self.applied_dirty:
474 474 write_list(map(str, self.applied), self.status_path)
475 475 if self.series_dirty:
476 476 write_list(self.full_series, self.series_path)
477 477 if self.guards_dirty:
478 478 write_list(self.active_guards, self.guards_path)
479 479 if self.added:
480 480 qrepo = self.qrepo()
481 481 if qrepo:
482 482 qrepo[None].add(f for f in self.added if f not in qrepo[None])
483 483 self.added = []
484 484
485 485 def removeundo(self, repo):
486 486 undo = repo.sjoin('undo')
487 487 if not os.path.exists(undo):
488 488 return
489 489 try:
490 490 os.unlink(undo)
491 491 except OSError, inst:
492 492 self.ui.warn(_('error removing undo: %s\n') % str(inst))
493 493
494 494 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
495 495 fp=None, changes=None, opts={}):
496 496 stat = opts.get('stat')
497 497 m = cmdutil.match(repo, files, opts)
498 498 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
499 499 changes, stat, fp)
500 500
501 501 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
502 502 # first try just applying the patch
503 503 (err, n) = self.apply(repo, [patch], update_status=False,
504 504 strict=True, merge=rev)
505 505
506 506 if err == 0:
507 507 return (err, n)
508 508
509 509 if n is None:
510 510 raise util.Abort(_("apply failed for patch %s") % patch)
511 511
512 512 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
513 513
514 514 # apply failed, strip away that rev and merge.
515 515 hg.clean(repo, head)
516 516 self.strip(repo, [n], update=False, backup='strip')
517 517
518 518 ctx = repo[rev]
519 519 ret = hg.merge(repo, rev)
520 520 if ret:
521 521 raise util.Abort(_("update returned %d") % ret)
522 522 n = repo.commit(ctx.description(), ctx.user(), force=True)
523 523 if n is None:
524 524 raise util.Abort(_("repo commit failed"))
525 525 try:
526 526 ph = patchheader(mergeq.join(patch), self.plainmode)
527 527 except:
528 528 raise util.Abort(_("unable to read %s") % patch)
529 529
530 530 diffopts = self.patchopts(diffopts, patch)
531 531 patchf = self.opener(patch, "w")
532 532 comments = str(ph)
533 533 if comments:
534 534 patchf.write(comments)
535 535 self.printdiff(repo, diffopts, head, n, fp=patchf)
536 536 patchf.close()
537 537 self.removeundo(repo)
538 538 return (0, n)
539 539
540 540 def qparents(self, repo, rev=None):
541 541 if rev is None:
542 542 (p1, p2) = repo.dirstate.parents()
543 543 if p2 == nullid:
544 544 return p1
545 545 if not self.applied:
546 546 return None
547 547 return self.applied[-1].node
548 548 p1, p2 = repo.changelog.parents(rev)
549 549 if p2 != nullid and p2 in [x.node for x in self.applied]:
550 550 return p2
551 551 return p1
552 552
553 553 def mergepatch(self, repo, mergeq, series, diffopts):
554 554 if not self.applied:
555 555 # each of the patches merged in will have two parents. This
556 556 # can confuse the qrefresh, qdiff, and strip code because it
557 557 # needs to know which parent is actually in the patch queue.
558 558 # so, we insert a merge marker with only one parent. This way
559 559 # the first patch in the queue is never a merge patch
560 560 #
561 561 pname = ".hg.patches.merge.marker"
562 562 n = repo.commit('[mq]: merge marker', force=True)
563 563 self.removeundo(repo)
564 564 self.applied.append(statusentry(n, pname))
565 565 self.applied_dirty = 1
566 566
567 567 head = self.qparents(repo)
568 568
569 569 for patch in series:
570 570 patch = mergeq.lookup(patch, strict=True)
571 571 if not patch:
572 572 self.ui.warn(_("patch %s does not exist\n") % patch)
573 573 return (1, None)
574 574 pushable, reason = self.pushable(patch)
575 575 if not pushable:
576 576 self.explain_pushable(patch, all_patches=True)
577 577 continue
578 578 info = mergeq.isapplied(patch)
579 579 if not info:
580 580 self.ui.warn(_("patch %s is not applied\n") % patch)
581 581 return (1, None)
582 582 rev = info[1]
583 583 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
584 584 if head:
585 585 self.applied.append(statusentry(head, patch))
586 586 self.applied_dirty = 1
587 587 if err:
588 588 return (err, head)
589 589 self.save_dirty()
590 590 return (0, head)
591 591
592 592 def patch(self, repo, patchfile):
593 593 '''Apply patchfile to the working directory.
594 594 patchfile: name of patch file'''
595 595 files = {}
596 596 try:
597 597 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
598 598 files=files, eolmode=None)
599 599 except Exception, inst:
600 600 self.ui.note(str(inst) + '\n')
601 601 if not self.ui.verbose:
602 602 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
603 603 return (False, files, False)
604 604
605 605 return (True, files, fuzz)
606 606
607 607 def apply(self, repo, series, list=False, update_status=True,
608 608 strict=False, patchdir=None, merge=None, all_files=None):
609 609 wlock = lock = tr = None
610 610 try:
611 611 wlock = repo.wlock()
612 612 lock = repo.lock()
613 613 tr = repo.transaction("qpush")
614 614 try:
615 615 ret = self._apply(repo, series, list, update_status,
616 616 strict, patchdir, merge, all_files=all_files)
617 617 tr.close()
618 618 self.save_dirty()
619 619 return ret
620 620 except:
621 621 try:
622 622 tr.abort()
623 623 finally:
624 624 repo.invalidate()
625 625 repo.dirstate.invalidate()
626 626 raise
627 627 finally:
628 628 release(tr, lock, wlock)
629 629 self.removeundo(repo)
630 630
631 631 def _apply(self, repo, series, list=False, update_status=True,
632 632 strict=False, patchdir=None, merge=None, all_files=None):
633 633 '''returns (error, hash)
634 634 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
635 635 # TODO unify with commands.py
636 636 if not patchdir:
637 637 patchdir = self.path
638 638 err = 0
639 639 n = None
640 640 for patchname in series:
641 641 pushable, reason = self.pushable(patchname)
642 642 if not pushable:
643 643 self.explain_pushable(patchname, all_patches=True)
644 644 continue
645 645 self.ui.status(_("applying %s\n") % patchname)
646 646 pf = os.path.join(patchdir, patchname)
647 647
648 648 try:
649 649 ph = patchheader(self.join(patchname), self.plainmode)
650 650 except:
651 651 self.ui.warn(_("unable to read %s\n") % patchname)
652 652 err = 1
653 653 break
654 654
655 655 message = ph.message
656 656 if not message:
657 657 # The commit message should not be translated
658 658 message = "imported patch %s\n" % patchname
659 659 else:
660 660 if list:
661 661 # The commit message should not be translated
662 662 message.append("\nimported patch %s" % patchname)
663 663 message = '\n'.join(message)
664 664
665 665 if ph.haspatch:
666 666 (patcherr, files, fuzz) = self.patch(repo, pf)
667 667 if all_files is not None:
668 668 all_files.update(files)
669 669 patcherr = not patcherr
670 670 else:
671 671 self.ui.warn(_("patch %s is empty\n") % patchname)
672 672 patcherr, files, fuzz = 0, [], 0
673 673
674 674 if merge and files:
675 675 # Mark as removed/merged and update dirstate parent info
676 676 removed = []
677 677 merged = []
678 678 for f in files:
679 679 if os.path.lexists(repo.wjoin(f)):
680 680 merged.append(f)
681 681 else:
682 682 removed.append(f)
683 683 for f in removed:
684 684 repo.dirstate.remove(f)
685 685 for f in merged:
686 686 repo.dirstate.merge(f)
687 687 p1, p2 = repo.dirstate.parents()
688 688 repo.dirstate.setparents(p1, merge)
689 689
690 690 files = cmdutil.updatedir(self.ui, repo, files)
691 691 match = cmdutil.matchfiles(repo, files or [])
692 692 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
693 693
694 694 if n is None:
695 695 raise util.Abort(_("repository commit failed"))
696 696
697 697 if update_status:
698 698 self.applied.append(statusentry(n, patchname))
699 699
700 700 if patcherr:
701 701 self.ui.warn(_("patch failed, rejects left in working dir\n"))
702 702 err = 2
703 703 break
704 704
705 705 if fuzz and strict:
706 706 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
707 707 err = 3
708 708 break
709 709 return (err, n)
710 710
711 711 def _cleanup(self, patches, numrevs, keep=False):
712 712 if not keep:
713 713 r = self.qrepo()
714 714 if r:
715 715 r[None].remove(patches, True)
716 716 else:
717 717 for p in patches:
718 718 os.unlink(self.join(p))
719 719
720 720 if numrevs:
721 721 del self.applied[:numrevs]
722 722 self.applied_dirty = 1
723 723
724 724 for i in sorted([self.find_series(p) for p in patches], reverse=True):
725 725 del self.full_series[i]
726 726 self.parse_series()
727 727 self.series_dirty = 1
728 728
729 729 def _revpatches(self, repo, revs):
730 730 firstrev = repo[self.applied[0].node].rev()
731 731 patches = []
732 732 for i, rev in enumerate(revs):
733 733
734 734 if rev < firstrev:
735 735 raise util.Abort(_('revision %d is not managed') % rev)
736 736
737 737 ctx = repo[rev]
738 738 base = self.applied[i].node
739 739 if ctx.node() != base:
740 740 msg = _('cannot delete revision %d above applied patches')
741 741 raise util.Abort(msg % rev)
742 742
743 743 patch = self.applied[i].name
744 744 for fmt in ('[mq]: %s', 'imported patch %s'):
745 745 if ctx.description() == fmt % patch:
746 746 msg = _('patch %s finalized without changeset message\n')
747 747 repo.ui.status(msg % patch)
748 748 break
749 749
750 750 patches.append(patch)
751 751 return patches
752 752
753 753 def finish(self, repo, revs):
754 754 patches = self._revpatches(repo, sorted(revs))
755 755 self._cleanup(patches, len(patches))
756 756
757 757 def delete(self, repo, patches, opts):
758 758 if not patches and not opts.get('rev'):
759 759 raise util.Abort(_('qdelete requires at least one revision or '
760 760 'patch name'))
761 761
762 762 realpatches = []
763 763 for patch in patches:
764 764 patch = self.lookup(patch, strict=True)
765 765 info = self.isapplied(patch)
766 766 if info:
767 767 raise util.Abort(_("cannot delete applied patch %s") % patch)
768 768 if patch not in self.series:
769 769 raise util.Abort(_("patch %s not in series file") % patch)
770 770 if patch not in realpatches:
771 771 realpatches.append(patch)
772 772
773 773 numrevs = 0
774 774 if opts.get('rev'):
775 775 if not self.applied:
776 776 raise util.Abort(_('no patches applied'))
777 777 revs = cmdutil.revrange(repo, opts.get('rev'))
778 778 if len(revs) > 1 and revs[0] > revs[1]:
779 779 revs.reverse()
780 780 revpatches = self._revpatches(repo, revs)
781 781 realpatches += revpatches
782 782 numrevs = len(revpatches)
783 783
784 784 self._cleanup(realpatches, numrevs, opts.get('keep'))
785 785
786 786 def check_toppatch(self, repo):
787 787 if self.applied:
788 788 top = self.applied[-1].node
789 789 patch = self.applied[-1].name
790 790 pp = repo.dirstate.parents()
791 791 if top not in pp:
792 792 raise util.Abort(_("working directory revision is not qtip"))
793 793 return top, patch
794 794 return None, None
795 795
796 796 def check_substate(self, repo):
797 797 '''return list of subrepos at a different revision than substate.
798 798 Abort if any subrepos have uncommitted changes.'''
799 799 inclsubs = []
800 800 wctx = repo[None]
801 801 for s in wctx.substate:
802 802 if wctx.sub(s).dirty(True):
803 803 raise util.Abort(
804 804 _("uncommitted changes in subrepository %s") % s)
805 805 elif wctx.sub(s).dirty():
806 806 inclsubs.append(s)
807 807 return inclsubs
808 808
809 809 def check_localchanges(self, repo, force=False, refresh=True):
810 810 m, a, r, d = repo.status()[:4]
811 811 if (m or a or r or d) and not force:
812 812 if refresh:
813 813 raise util.Abort(_("local changes found, refresh first"))
814 814 else:
815 815 raise util.Abort(_("local changes found"))
816 816 return m, a, r, d
817 817
818 818 _reserved = ('series', 'status', 'guards')
819 819 def check_reserved_name(self, name):
820 820 if (name in self._reserved or name.startswith('.hg')
821 821 or name.startswith('.mq') or '#' in name or ':' in name):
822 822 raise util.Abort(_('"%s" cannot be used as the name of a patch')
823 823 % name)
824 824
825 825 def new(self, repo, patchfn, *pats, **opts):
826 826 """options:
827 827 msg: a string or a no-argument function returning a string
828 828 """
829 829 msg = opts.get('msg')
830 830 user = opts.get('user')
831 831 date = opts.get('date')
832 832 if date:
833 833 date = util.parsedate(date)
834 834 diffopts = self.diffopts({'git': opts.get('git')})
835 835 self.check_reserved_name(patchfn)
836 836 if os.path.exists(self.join(patchfn)):
837 837 if os.path.isdir(self.join(patchfn)):
838 838 raise util.Abort(_('"%s" already exists as a directory')
839 839 % patchfn)
840 840 else:
841 841 raise util.Abort(_('patch "%s" already exists') % patchfn)
842 842
843 843 inclsubs = self.check_substate(repo)
844 844 if inclsubs:
845 845 inclsubs.append('.hgsubstate')
846 846 if opts.get('include') or opts.get('exclude') or pats:
847 847 if inclsubs:
848 848 pats = list(pats or []) + inclsubs
849 849 match = cmdutil.match(repo, pats, opts)
850 850 # detect missing files in pats
851 851 def badfn(f, msg):
852 852 if f != '.hgsubstate': # .hgsubstate is auto-created
853 853 raise util.Abort('%s: %s' % (f, msg))
854 854 match.bad = badfn
855 855 m, a, r, d = repo.status(match=match)[:4]
856 856 else:
857 857 m, a, r, d = self.check_localchanges(repo, force=True)
858 858 match = cmdutil.matchfiles(repo, m + a + r + inclsubs)
859 859 if len(repo[None].parents()) > 1:
860 860 raise util.Abort(_('cannot manage merge changesets'))
861 861 commitfiles = m + a + r
862 862 self.check_toppatch(repo)
863 863 insert = self.full_series_end()
864 864 wlock = repo.wlock()
865 865 try:
866 866 try:
867 867 # if patch file write fails, abort early
868 868 p = self.opener(patchfn, "w")
869 869 except IOError, e:
870 870 raise util.Abort(_('cannot write patch "%s": %s')
871 871 % (patchfn, e.strerror))
872 872 try:
873 873 if self.plainmode:
874 874 if user:
875 875 p.write("From: " + user + "\n")
876 876 if not date:
877 877 p.write("\n")
878 878 if date:
879 879 p.write("Date: %d %d\n\n" % date)
880 880 else:
881 881 p.write("# HG changeset patch\n")
882 882 p.write("# Parent "
883 883 + hex(repo[None].parents()[0].node()) + "\n")
884 884 if user:
885 885 p.write("# User " + user + "\n")
886 886 if date:
887 887 p.write("# Date %s %s\n\n" % date)
888 888 if hasattr(msg, '__call__'):
889 889 msg = msg()
890 890 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
891 891 n = repo.commit(commitmsg, user, date, match=match, force=True)
892 892 if n is None:
893 893 raise util.Abort(_("repo commit failed"))
894 894 try:
895 895 self.full_series[insert:insert] = [patchfn]
896 896 self.applied.append(statusentry(n, patchfn))
897 897 self.parse_series()
898 898 self.series_dirty = 1
899 899 self.applied_dirty = 1
900 900 if msg:
901 901 msg = msg + "\n\n"
902 902 p.write(msg)
903 903 if commitfiles:
904 904 parent = self.qparents(repo, n)
905 905 chunks = patch.diff(repo, node1=parent, node2=n,
906 906 match=match, opts=diffopts)
907 907 for chunk in chunks:
908 908 p.write(chunk)
909 909 p.close()
910 910 wlock.release()
911 911 wlock = None
912 912 r = self.qrepo()
913 913 if r:
914 914 r[None].add([patchfn])
915 915 except:
916 916 repo.rollback()
917 917 raise
918 918 except Exception:
919 919 patchpath = self.join(patchfn)
920 920 try:
921 921 os.unlink(patchpath)
922 922 except:
923 923 self.ui.warn(_('error unlinking %s\n') % patchpath)
924 924 raise
925 925 self.removeundo(repo)
926 926 finally:
927 927 release(wlock)
928 928
929 929 def strip(self, repo, revs, update=True, backup="all", force=None):
930 930 wlock = lock = None
931 931 try:
932 932 wlock = repo.wlock()
933 933 lock = repo.lock()
934 934
935 935 if update:
936 936 self.check_localchanges(repo, force=force, refresh=False)
937 937 urev = self.qparents(repo, revs[0])
938 938 hg.clean(repo, urev)
939 939 repo.dirstate.write()
940 940
941 941 self.removeundo(repo)
942 942 for rev in revs:
943 943 repair.strip(self.ui, repo, rev, backup)
944 944 # strip may have unbundled a set of backed up revisions after
945 945 # the actual strip
946 946 self.removeundo(repo)
947 947 finally:
948 948 release(lock, wlock)
949 949
950 950 def isapplied(self, patch):
951 951 """returns (index, rev, patch)"""
952 952 for i, a in enumerate(self.applied):
953 953 if a.name == patch:
954 954 return (i, a.node, a.name)
955 955 return None
956 956
957 957 # if the exact patch name does not exist, we try a few
958 958 # variations. If strict is passed, we try only #1
959 959 #
960 960 # 1) a number to indicate an offset in the series file
961 961 # 2) a unique substring of the patch name was given
962 962 # 3) patchname[-+]num to indicate an offset in the series file
963 963 def lookup(self, patch, strict=False):
964 964 patch = patch and str(patch)
965 965
966 966 def partial_name(s):
967 967 if s in self.series:
968 968 return s
969 969 matches = [x for x in self.series if s in x]
970 970 if len(matches) > 1:
971 971 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
972 972 for m in matches:
973 973 self.ui.warn(' %s\n' % m)
974 974 return None
975 975 if matches:
976 976 return matches[0]
977 977 if self.series and self.applied:
978 978 if s == 'qtip':
979 979 return self.series[self.series_end(True)-1]
980 980 if s == 'qbase':
981 981 return self.series[0]
982 982 return None
983 983
984 984 if patch is None:
985 985 return None
986 986 if patch in self.series:
987 987 return patch
988 988
989 989 if not os.path.isfile(self.join(patch)):
990 990 try:
991 991 sno = int(patch)
992 992 except (ValueError, OverflowError):
993 993 pass
994 994 else:
995 995 if -len(self.series) <= sno < len(self.series):
996 996 return self.series[sno]
997 997
998 998 if not strict:
999 999 res = partial_name(patch)
1000 1000 if res:
1001 1001 return res
1002 1002 minus = patch.rfind('-')
1003 1003 if minus >= 0:
1004 1004 res = partial_name(patch[:minus])
1005 1005 if res:
1006 1006 i = self.series.index(res)
1007 1007 try:
1008 1008 off = int(patch[minus + 1:] or 1)
1009 1009 except (ValueError, OverflowError):
1010 1010 pass
1011 1011 else:
1012 1012 if i - off >= 0:
1013 1013 return self.series[i - off]
1014 1014 plus = patch.rfind('+')
1015 1015 if plus >= 0:
1016 1016 res = partial_name(patch[:plus])
1017 1017 if res:
1018 1018 i = self.series.index(res)
1019 1019 try:
1020 1020 off = int(patch[plus + 1:] or 1)
1021 1021 except (ValueError, OverflowError):
1022 1022 pass
1023 1023 else:
1024 1024 if i + off < len(self.series):
1025 1025 return self.series[i + off]
1026 1026 raise util.Abort(_("patch %s not in series") % patch)
1027 1027
1028 1028 def push(self, repo, patch=None, force=False, list=False,
1029 1029 mergeq=None, all=False, move=False, exact=False):
1030 1030 diffopts = self.diffopts()
1031 1031 wlock = repo.wlock()
1032 1032 try:
1033 1033 heads = []
1034 1034 for b, ls in repo.branchmap().iteritems():
1035 1035 heads += ls
1036 1036 if not heads:
1037 1037 heads = [nullid]
1038 1038 if repo.dirstate.parents()[0] not in heads and not exact:
1039 1039 self.ui.status(_("(working directory not at a head)\n"))
1040 1040
1041 1041 if not self.series:
1042 1042 self.ui.warn(_('no patches in series\n'))
1043 1043 return 0
1044 1044
1045 1045 patch = self.lookup(patch)
1046 1046 # Suppose our series file is: A B C and the current 'top'
1047 1047 # patch is B. qpush C should be performed (moving forward)
1048 1048 # qpush B is a NOP (no change) qpush A is an error (can't
1049 1049 # go backwards with qpush)
1050 1050 if patch:
1051 1051 info = self.isapplied(patch)
1052 1052 if info:
1053 1053 if info[0] < len(self.applied) - 1:
1054 1054 raise util.Abort(
1055 1055 _("cannot push to a previous patch: %s") % patch)
1056 1056 self.ui.warn(
1057 1057 _('qpush: %s is already at the top\n') % patch)
1058 1058 return 0
1059 1059 pushable, reason = self.pushable(patch)
1060 1060 if not pushable:
1061 1061 if reason:
1062 1062 reason = _('guarded by %r') % reason
1063 1063 else:
1064 1064 reason = _('no matching guards')
1065 1065 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1066 1066 return 1
1067 1067 elif all:
1068 1068 patch = self.series[-1]
1069 1069 if self.isapplied(patch):
1070 1070 self.ui.warn(_('all patches are currently applied\n'))
1071 1071 return 0
1072 1072
1073 1073 # Following the above example, starting at 'top' of B:
1074 1074 # qpush should be performed (pushes C), but a subsequent
1075 1075 # qpush without an argument is an error (nothing to
1076 1076 # apply). This allows a loop of "...while hg qpush..." to
1077 1077 # work as it detects an error when done
1078 1078 start = self.series_end()
1079 1079 if start == len(self.series):
1080 1080 self.ui.warn(_('patch series already fully applied\n'))
1081 1081 return 1
1082 1082 if not force:
1083 1083 self.check_localchanges(repo)
1084 1084
1085 1085 if exact:
1086 1086 if move:
1087 1087 raise util.Abort(_("cannot use --exact and --move together"))
1088 1088 if self.applied:
1089 1089 raise util.Abort(_("cannot push --exact with applied patches"))
1090 1090 root = self.series[start]
1091 1091 target = patchheader(self.join(root), self.plainmode).parent
1092 1092 if not target:
1093 1093 raise util.Abort(_("%s does not have a parent recorded" % root))
1094 1094 if not repo[target] == repo['.']:
1095 1095 hg.update(repo, target)
1096 1096
1097 1097 if move:
1098 1098 if not patch:
1099 1099 raise util.Abort(_("please specify the patch to move"))
1100 1100 for i, rpn in enumerate(self.full_series[start:]):
1101 1101 # strip markers for patch guards
1102 1102 if self.guard_re.split(rpn, 1)[0] == patch:
1103 1103 break
1104 1104 index = start + i
1105 1105 assert index < len(self.full_series)
1106 1106 fullpatch = self.full_series[index]
1107 1107 del self.full_series[index]
1108 1108 self.full_series.insert(start, fullpatch)
1109 1109 self.parse_series()
1110 1110 self.series_dirty = 1
1111 1111
1112 1112 self.applied_dirty = 1
1113 1113 if start > 0:
1114 1114 self.check_toppatch(repo)
1115 1115 if not patch:
1116 1116 patch = self.series[start]
1117 1117 end = start + 1
1118 1118 else:
1119 1119 end = self.series.index(patch, start) + 1
1120 1120
1121 1121 s = self.series[start:end]
1122 1122 all_files = set()
1123 1123 try:
1124 1124 if mergeq:
1125 1125 ret = self.mergepatch(repo, mergeq, s, diffopts)
1126 1126 else:
1127 1127 ret = self.apply(repo, s, list, all_files=all_files)
1128 1128 except:
1129 1129 self.ui.warn(_('cleaning up working directory...'))
1130 1130 node = repo.dirstate.parents()[0]
1131 1131 hg.revert(repo, node, None)
1132 1132 # only remove unknown files that we know we touched or
1133 1133 # created while patching
1134 1134 for f in all_files:
1135 1135 if f not in repo.dirstate:
1136 1136 try:
1137 1137 util.unlink(repo.wjoin(f))
1138 1138 except OSError, inst:
1139 1139 if inst.errno != errno.ENOENT:
1140 1140 raise
1141 1141 self.ui.warn(_('done\n'))
1142 1142 raise
1143 1143
1144 1144 if not self.applied:
1145 1145 return ret[0]
1146 1146 top = self.applied[-1].name
1147 1147 if ret[0] and ret[0] > 1:
1148 1148 msg = _("errors during apply, please fix and refresh %s\n")
1149 1149 self.ui.write(msg % top)
1150 1150 else:
1151 1151 self.ui.write(_("now at: %s\n") % top)
1152 1152 return ret[0]
1153 1153
1154 1154 finally:
1155 1155 wlock.release()
1156 1156
1157 1157 def pop(self, repo, patch=None, force=False, update=True, all=False):
1158 1158 wlock = repo.wlock()
1159 1159 try:
1160 1160 if patch:
1161 1161 # index, rev, patch
1162 1162 info = self.isapplied(patch)
1163 1163 if not info:
1164 1164 patch = self.lookup(patch)
1165 1165 info = self.isapplied(patch)
1166 1166 if not info:
1167 1167 raise util.Abort(_("patch %s is not applied") % patch)
1168 1168
1169 1169 if not self.applied:
1170 1170 # Allow qpop -a to work repeatedly,
1171 1171 # but not qpop without an argument
1172 1172 self.ui.warn(_("no patches applied\n"))
1173 1173 return not all
1174 1174
1175 1175 if all:
1176 1176 start = 0
1177 1177 elif patch:
1178 1178 start = info[0] + 1
1179 1179 else:
1180 1180 start = len(self.applied) - 1
1181 1181
1182 1182 if start >= len(self.applied):
1183 1183 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1184 1184 return
1185 1185
1186 1186 if not update:
1187 1187 parents = repo.dirstate.parents()
1188 1188 rr = [x.node for x in self.applied]
1189 1189 for p in parents:
1190 1190 if p in rr:
1191 1191 self.ui.warn(_("qpop: forcing dirstate update\n"))
1192 1192 update = True
1193 1193 else:
1194 1194 parents = [p.node() for p in repo[None].parents()]
1195 1195 needupdate = False
1196 1196 for entry in self.applied[start:]:
1197 1197 if entry.node in parents:
1198 1198 needupdate = True
1199 1199 break
1200 1200 update = needupdate
1201 1201
1202 1202 if not force and update:
1203 1203 self.check_localchanges(repo)
1204 1204
1205 1205 self.applied_dirty = 1
1206 1206 end = len(self.applied)
1207 1207 rev = self.applied[start].node
1208 1208 if update:
1209 1209 top = self.check_toppatch(repo)[0]
1210 1210
1211 1211 try:
1212 1212 heads = repo.changelog.heads(rev)
1213 1213 except error.LookupError:
1214 1214 node = short(rev)
1215 1215 raise util.Abort(_('trying to pop unknown node %s') % node)
1216 1216
1217 1217 if heads != [self.applied[-1].node]:
1218 1218 raise util.Abort(_("popping would remove a revision not "
1219 1219 "managed by this patch queue"))
1220 1220
1221 1221 # we know there are no local changes, so we can make a simplified
1222 1222 # form of hg.update.
1223 1223 if update:
1224 1224 qp = self.qparents(repo, rev)
1225 1225 ctx = repo[qp]
1226 1226 m, a, r, d = repo.status(qp, top)[:4]
1227 1227 if d:
1228 1228 raise util.Abort(_("deletions found between repo revs"))
1229 1229 for f in a:
1230 1230 try:
1231 1231 util.unlink(repo.wjoin(f))
1232 1232 except OSError, e:
1233 1233 if e.errno != errno.ENOENT:
1234 1234 raise
1235 1235 repo.dirstate.forget(f)
1236 1236 for f in m + r:
1237 1237 fctx = ctx[f]
1238 1238 repo.wwrite(f, fctx.data(), fctx.flags())
1239 1239 repo.dirstate.normal(f)
1240 1240 repo.dirstate.setparents(qp, nullid)
1241 1241 for patch in reversed(self.applied[start:end]):
1242 1242 self.ui.status(_("popping %s\n") % patch.name)
1243 1243 del self.applied[start:end]
1244 1244 self.strip(repo, [rev], update=False, backup='strip')
1245 1245 if self.applied:
1246 1246 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1247 1247 else:
1248 1248 self.ui.write(_("patch queue now empty\n"))
1249 1249 finally:
1250 1250 wlock.release()
1251 1251
1252 1252 def diff(self, repo, pats, opts):
1253 1253 top, patch = self.check_toppatch(repo)
1254 1254 if not top:
1255 1255 self.ui.write(_("no patches applied\n"))
1256 1256 return
1257 1257 qp = self.qparents(repo, top)
1258 1258 if opts.get('reverse'):
1259 1259 node1, node2 = None, qp
1260 1260 else:
1261 1261 node1, node2 = qp, None
1262 1262 diffopts = self.diffopts(opts, patch)
1263 1263 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1264 1264
1265 1265 def refresh(self, repo, pats=None, **opts):
1266 1266 if not self.applied:
1267 1267 self.ui.write(_("no patches applied\n"))
1268 1268 return 1
1269 1269 msg = opts.get('msg', '').rstrip()
1270 1270 newuser = opts.get('user')
1271 1271 newdate = opts.get('date')
1272 1272 if newdate:
1273 1273 newdate = '%d %d' % util.parsedate(newdate)
1274 1274 wlock = repo.wlock()
1275 1275
1276 1276 try:
1277 1277 self.check_toppatch(repo)
1278 1278 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1279 1279 if repo.changelog.heads(top) != [top]:
1280 1280 raise util.Abort(_("cannot refresh a revision with children"))
1281 1281
1282 1282 inclsubs = self.check_substate(repo)
1283 1283
1284 1284 cparents = repo.changelog.parents(top)
1285 1285 patchparent = self.qparents(repo, top)
1286 1286 ph = patchheader(self.join(patchfn), self.plainmode)
1287 1287 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1288 1288 if msg:
1289 1289 ph.setmessage(msg)
1290 1290 if newuser:
1291 1291 ph.setuser(newuser)
1292 1292 if newdate:
1293 1293 ph.setdate(newdate)
1294 1294 ph.setparent(hex(patchparent))
1295 1295
1296 1296 # only commit new patch when write is complete
1297 1297 patchf = self.opener(patchfn, 'w', atomictemp=True)
1298 1298
1299 1299 comments = str(ph)
1300 1300 if comments:
1301 1301 patchf.write(comments)
1302 1302
1303 1303 # update the dirstate in place, strip off the qtip commit
1304 1304 # and then commit.
1305 1305 #
1306 1306 # this should really read:
1307 1307 # mm, dd, aa = repo.status(top, patchparent)[:3]
1308 1308 # but we do it backwards to take advantage of manifest/chlog
1309 1309 # caching against the next repo.status call
1310 1310 mm, aa, dd = repo.status(patchparent, top)[:3]
1311 1311 changes = repo.changelog.read(top)
1312 1312 man = repo.manifest.read(changes[0])
1313 1313 aaa = aa[:]
1314 1314 matchfn = cmdutil.match(repo, pats, opts)
1315 1315 # in short mode, we only diff the files included in the
1316 1316 # patch already plus specified files
1317 1317 if opts.get('short'):
1318 1318 # if amending a patch, we start with existing
1319 1319 # files plus specified files - unfiltered
1320 1320 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1321 1321 # filter with inc/exl options
1322 1322 matchfn = cmdutil.match(repo, opts=opts)
1323 1323 else:
1324 1324 match = cmdutil.matchall(repo)
1325 1325 m, a, r, d = repo.status(match=match)[:4]
1326 1326 mm = set(mm)
1327 1327 aa = set(aa)
1328 1328 dd = set(dd)
1329 1329
1330 1330 # we might end up with files that were added between
1331 1331 # qtip and the dirstate parent, but then changed in the
1332 1332 # local dirstate. in this case, we want them to only
1333 1333 # show up in the added section
1334 1334 for x in m:
1335 1335 if x not in aa:
1336 1336 mm.add(x)
1337 1337 # we might end up with files added by the local dirstate that
1338 1338 # were deleted by the patch. In this case, they should only
1339 1339 # show up in the changed section.
1340 1340 for x in a:
1341 1341 if x in dd:
1342 1342 dd.remove(x)
1343 1343 mm.add(x)
1344 1344 else:
1345 1345 aa.add(x)
1346 1346 # make sure any files deleted in the local dirstate
1347 1347 # are not in the add or change column of the patch
1348 1348 forget = []
1349 1349 for x in d + r:
1350 1350 if x in aa:
1351 1351 aa.remove(x)
1352 1352 forget.append(x)
1353 1353 continue
1354 1354 else:
1355 1355 mm.discard(x)
1356 1356 dd.add(x)
1357 1357
1358 1358 m = list(mm)
1359 1359 r = list(dd)
1360 1360 a = list(aa)
1361 1361 c = [filter(matchfn, l) for l in (m, a, r)]
1362 1362 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1363 1363 chunks = patch.diff(repo, patchparent, match=match,
1364 1364 changes=c, opts=diffopts)
1365 1365 for chunk in chunks:
1366 1366 patchf.write(chunk)
1367 1367
1368 1368 try:
1369 1369 if diffopts.git or diffopts.upgrade:
1370 1370 copies = {}
1371 1371 for dst in a:
1372 1372 src = repo.dirstate.copied(dst)
1373 1373 # during qfold, the source file for copies may
1374 1374 # be removed. Treat this as a simple add.
1375 1375 if src is not None and src in repo.dirstate:
1376 1376 copies.setdefault(src, []).append(dst)
1377 1377 repo.dirstate.add(dst)
1378 1378 # remember the copies between patchparent and qtip
1379 1379 for dst in aaa:
1380 1380 f = repo.file(dst)
1381 1381 src = f.renamed(man[dst])
1382 1382 if src:
1383 1383 copies.setdefault(src[0], []).extend(
1384 1384 copies.get(dst, []))
1385 1385 if dst in a:
1386 1386 copies[src[0]].append(dst)
1387 1387 # we can't copy a file created by the patch itself
1388 1388 if dst in copies:
1389 1389 del copies[dst]
1390 1390 for src, dsts in copies.iteritems():
1391 1391 for dst in dsts:
1392 1392 repo.dirstate.copy(src, dst)
1393 1393 else:
1394 1394 for dst in a:
1395 1395 repo.dirstate.add(dst)
1396 1396 # Drop useless copy information
1397 1397 for f in list(repo.dirstate.copies()):
1398 1398 repo.dirstate.copy(None, f)
1399 1399 for f in r:
1400 1400 repo.dirstate.remove(f)
1401 1401 # if the patch excludes a modified file, mark that
1402 1402 # file with mtime=0 so status can see it.
1403 1403 mm = []
1404 1404 for i in xrange(len(m)-1, -1, -1):
1405 1405 if not matchfn(m[i]):
1406 1406 mm.append(m[i])
1407 1407 del m[i]
1408 1408 for f in m:
1409 1409 repo.dirstate.normal(f)
1410 1410 for f in mm:
1411 1411 repo.dirstate.normallookup(f)
1412 1412 for f in forget:
1413 1413 repo.dirstate.forget(f)
1414 1414
1415 1415 if not msg:
1416 1416 if not ph.message:
1417 1417 message = "[mq]: %s\n" % patchfn
1418 1418 else:
1419 1419 message = "\n".join(ph.message)
1420 1420 else:
1421 1421 message = msg
1422 1422
1423 1423 user = ph.user or changes[1]
1424 1424
1425 1425 # assumes strip can roll itself back if interrupted
1426 1426 repo.dirstate.setparents(*cparents)
1427 1427 self.applied.pop()
1428 1428 self.applied_dirty = 1
1429 1429 self.strip(repo, [top], update=False,
1430 1430 backup='strip')
1431 1431 except:
1432 1432 repo.dirstate.invalidate()
1433 1433 raise
1434 1434
1435 1435 try:
1436 1436 # might be nice to attempt to roll back strip after this
1437 1437 patchf.rename()
1438 1438 n = repo.commit(message, user, ph.date, match=match,
1439 1439 force=True)
1440 1440 self.applied.append(statusentry(n, patchfn))
1441 1441 except:
1442 1442 ctx = repo[cparents[0]]
1443 1443 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1444 1444 self.save_dirty()
1445 1445 self.ui.warn(_('refresh interrupted while patch was popped! '
1446 1446 '(revert --all, qpush to recover)\n'))
1447 1447 raise
1448 1448 finally:
1449 1449 wlock.release()
1450 1450 self.removeundo(repo)
1451 1451
1452 1452 def init(self, repo, create=False):
1453 1453 if not create and os.path.isdir(self.path):
1454 1454 raise util.Abort(_("patch queue directory already exists"))
1455 1455 try:
1456 1456 os.mkdir(self.path)
1457 1457 except OSError, inst:
1458 1458 if inst.errno != errno.EEXIST or not create:
1459 1459 raise
1460 1460 if create:
1461 1461 return self.qrepo(create=True)
1462 1462
1463 1463 def unapplied(self, repo, patch=None):
1464 1464 if patch and patch not in self.series:
1465 1465 raise util.Abort(_("patch %s is not in series file") % patch)
1466 1466 if not patch:
1467 1467 start = self.series_end()
1468 1468 else:
1469 1469 start = self.series.index(patch) + 1
1470 1470 unapplied = []
1471 1471 for i in xrange(start, len(self.series)):
1472 1472 pushable, reason = self.pushable(i)
1473 1473 if pushable:
1474 1474 unapplied.append((i, self.series[i]))
1475 1475 self.explain_pushable(i)
1476 1476 return unapplied
1477 1477
1478 1478 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1479 1479 summary=False):
1480 1480 def displayname(pfx, patchname, state):
1481 1481 if pfx:
1482 1482 self.ui.write(pfx)
1483 1483 if summary:
1484 1484 ph = patchheader(self.join(patchname), self.plainmode)
1485 1485 msg = ph.message and ph.message[0] or ''
1486 1486 if self.ui.formatted():
1487 1487 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1488 1488 if width > 0:
1489 1489 msg = util.ellipsis(msg, width)
1490 1490 else:
1491 1491 msg = ''
1492 1492 self.ui.write(patchname, label='qseries.' + state)
1493 1493 self.ui.write(': ')
1494 1494 self.ui.write(msg, label='qseries.message.' + state)
1495 1495 else:
1496 1496 self.ui.write(patchname, label='qseries.' + state)
1497 1497 self.ui.write('\n')
1498 1498
1499 1499 applied = set([p.name for p in self.applied])
1500 1500 if length is None:
1501 1501 length = len(self.series) - start
1502 1502 if not missing:
1503 1503 if self.ui.verbose:
1504 1504 idxwidth = len(str(start + length - 1))
1505 1505 for i in xrange(start, start + length):
1506 1506 patch = self.series[i]
1507 1507 if patch in applied:
1508 1508 char, state = 'A', 'applied'
1509 1509 elif self.pushable(i)[0]:
1510 1510 char, state = 'U', 'unapplied'
1511 1511 else:
1512 1512 char, state = 'G', 'guarded'
1513 1513 pfx = ''
1514 1514 if self.ui.verbose:
1515 1515 pfx = '%*d %s ' % (idxwidth, i, char)
1516 1516 elif status and status != char:
1517 1517 continue
1518 1518 displayname(pfx, patch, state)
1519 1519 else:
1520 1520 msng_list = []
1521 1521 for root, dirs, files in os.walk(self.path):
1522 1522 d = root[len(self.path) + 1:]
1523 1523 for f in files:
1524 1524 fl = os.path.join(d, f)
1525 1525 if (fl not in self.series and
1526 1526 fl not in (self.status_path, self.series_path,
1527 1527 self.guards_path)
1528 1528 and not fl.startswith('.')):
1529 1529 msng_list.append(fl)
1530 1530 for x in sorted(msng_list):
1531 1531 pfx = self.ui.verbose and ('D ') or ''
1532 1532 displayname(pfx, x, 'missing')
1533 1533
1534 1534 def issaveline(self, l):
1535 1535 if l.name == '.hg.patches.save.line':
1536 1536 return True
1537 1537
1538 1538 def qrepo(self, create=False):
1539 1539 ui = self.ui.copy()
1540 1540 ui.setconfig('paths', 'default', '', overlay=False)
1541 1541 ui.setconfig('paths', 'default-push', '', overlay=False)
1542 1542 if create or os.path.isdir(self.join(".hg")):
1543 1543 return hg.repository(ui, path=self.path, create=create)
1544 1544
1545 1545 def restore(self, repo, rev, delete=None, qupdate=None):
1546 1546 desc = repo[rev].description().strip()
1547 1547 lines = desc.splitlines()
1548 1548 i = 0
1549 1549 datastart = None
1550 1550 series = []
1551 1551 applied = []
1552 1552 qpp = None
1553 1553 for i, line in enumerate(lines):
1554 1554 if line == 'Patch Data:':
1555 1555 datastart = i + 1
1556 1556 elif line.startswith('Dirstate:'):
1557 1557 l = line.rstrip()
1558 1558 l = l[10:].split(' ')
1559 1559 qpp = [bin(x) for x in l]
1560 1560 elif datastart is not None:
1561 1561 l = line.rstrip()
1562 1562 n, name = l.split(':', 1)
1563 1563 if n:
1564 1564 applied.append(statusentry(bin(n), name))
1565 1565 else:
1566 1566 series.append(l)
1567 1567 if datastart is None:
1568 1568 self.ui.warn(_("No saved patch data found\n"))
1569 1569 return 1
1570 1570 self.ui.warn(_("restoring status: %s\n") % lines[0])
1571 1571 self.full_series = series
1572 1572 self.applied = applied
1573 1573 self.parse_series()
1574 1574 self.series_dirty = 1
1575 1575 self.applied_dirty = 1
1576 1576 heads = repo.changelog.heads()
1577 1577 if delete:
1578 1578 if rev not in heads:
1579 1579 self.ui.warn(_("save entry has children, leaving it alone\n"))
1580 1580 else:
1581 1581 self.ui.warn(_("removing save entry %s\n") % short(rev))
1582 1582 pp = repo.dirstate.parents()
1583 1583 if rev in pp:
1584 1584 update = True
1585 1585 else:
1586 1586 update = False
1587 1587 self.strip(repo, [rev], update=update, backup='strip')
1588 1588 if qpp:
1589 1589 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1590 1590 (short(qpp[0]), short(qpp[1])))
1591 1591 if qupdate:
1592 1592 self.ui.status(_("updating queue directory\n"))
1593 1593 r = self.qrepo()
1594 1594 if not r:
1595 1595 self.ui.warn(_("Unable to load queue repository\n"))
1596 1596 return 1
1597 1597 hg.clean(r, qpp[0])
1598 1598
1599 1599 def save(self, repo, msg=None):
1600 1600 if not self.applied:
1601 1601 self.ui.warn(_("save: no patches applied, exiting\n"))
1602 1602 return 1
1603 1603 if self.issaveline(self.applied[-1]):
1604 1604 self.ui.warn(_("status is already saved\n"))
1605 1605 return 1
1606 1606
1607 1607 if not msg:
1608 1608 msg = _("hg patches saved state")
1609 1609 else:
1610 1610 msg = "hg patches: " + msg.rstrip('\r\n')
1611 1611 r = self.qrepo()
1612 1612 if r:
1613 1613 pp = r.dirstate.parents()
1614 1614 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1615 1615 msg += "\n\nPatch Data:\n"
1616 1616 msg += ''.join('%s\n' % x for x in self.applied)
1617 1617 msg += ''.join(':%s\n' % x for x in self.full_series)
1618 1618 n = repo.commit(msg, force=True)
1619 1619 if not n:
1620 1620 self.ui.warn(_("repo commit failed\n"))
1621 1621 return 1
1622 1622 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1623 1623 self.applied_dirty = 1
1624 1624 self.removeundo(repo)
1625 1625
1626 1626 def full_series_end(self):
1627 1627 if self.applied:
1628 1628 p = self.applied[-1].name
1629 1629 end = self.find_series(p)
1630 1630 if end is None:
1631 1631 return len(self.full_series)
1632 1632 return end + 1
1633 1633 return 0
1634 1634
1635 1635 def series_end(self, all_patches=False):
1636 1636 """If all_patches is False, return the index of the next pushable patch
1637 1637 in the series, or the series length. If all_patches is True, return the
1638 1638 index of the first patch past the last applied one.
1639 1639 """
1640 1640 end = 0
1641 1641 def next(start):
1642 1642 if all_patches or start >= len(self.series):
1643 1643 return start
1644 1644 for i in xrange(start, len(self.series)):
1645 1645 p, reason = self.pushable(i)
1646 1646 if p:
1647 1647 break
1648 1648 self.explain_pushable(i)
1649 1649 return i
1650 1650 if self.applied:
1651 1651 p = self.applied[-1].name
1652 1652 try:
1653 1653 end = self.series.index(p)
1654 1654 except ValueError:
1655 1655 return 0
1656 1656 return next(end + 1)
1657 1657 return next(end)
1658 1658
1659 1659 def appliedname(self, index):
1660 1660 pname = self.applied[index].name
1661 1661 if not self.ui.verbose:
1662 1662 p = pname
1663 1663 else:
1664 1664 p = str(self.series.index(pname)) + " " + pname
1665 1665 return p
1666 1666
1667 1667 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1668 1668 force=None, git=False):
1669 1669 def checkseries(patchname):
1670 1670 if patchname in self.series:
1671 1671 raise util.Abort(_('patch %s is already in the series file')
1672 1672 % patchname)
1673 1673 def checkfile(patchname):
1674 1674 if not force and os.path.exists(self.join(patchname)):
1675 1675 raise util.Abort(_('patch "%s" already exists')
1676 1676 % patchname)
1677 1677
1678 1678 if rev:
1679 1679 if files:
1680 1680 raise util.Abort(_('option "-r" not valid when importing '
1681 1681 'files'))
1682 1682 rev = cmdutil.revrange(repo, rev)
1683 1683 rev.sort(reverse=True)
1684 1684 if (len(files) > 1 or len(rev) > 1) and patchname:
1685 1685 raise util.Abort(_('option "-n" not valid when importing multiple '
1686 1686 'patches'))
1687 1687 if rev:
1688 1688 # If mq patches are applied, we can only import revisions
1689 1689 # that form a linear path to qbase.
1690 1690 # Otherwise, they should form a linear path to a head.
1691 1691 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1692 1692 if len(heads) > 1:
1693 1693 raise util.Abort(_('revision %d is the root of more than one '
1694 1694 'branch') % rev[-1])
1695 1695 if self.applied:
1696 1696 base = repo.changelog.node(rev[0])
1697 1697 if base in [n.node for n in self.applied]:
1698 1698 raise util.Abort(_('revision %d is already managed')
1699 1699 % rev[0])
1700 1700 if heads != [self.applied[-1].node]:
1701 1701 raise util.Abort(_('revision %d is not the parent of '
1702 1702 'the queue') % rev[0])
1703 1703 base = repo.changelog.rev(self.applied[0].node)
1704 1704 lastparent = repo.changelog.parentrevs(base)[0]
1705 1705 else:
1706 1706 if heads != [repo.changelog.node(rev[0])]:
1707 1707 raise util.Abort(_('revision %d has unmanaged children')
1708 1708 % rev[0])
1709 1709 lastparent = None
1710 1710
1711 1711 diffopts = self.diffopts({'git': git})
1712 1712 for r in rev:
1713 1713 p1, p2 = repo.changelog.parentrevs(r)
1714 1714 n = repo.changelog.node(r)
1715 1715 if p2 != nullrev:
1716 1716 raise util.Abort(_('cannot import merge revision %d') % r)
1717 1717 if lastparent and lastparent != r:
1718 1718 raise util.Abort(_('revision %d is not the parent of %d')
1719 1719 % (r, lastparent))
1720 1720 lastparent = p1
1721 1721
1722 1722 if not patchname:
1723 1723 patchname = normname('%d.diff' % r)
1724 1724 self.check_reserved_name(patchname)
1725 1725 checkseries(patchname)
1726 1726 checkfile(patchname)
1727 1727 self.full_series.insert(0, patchname)
1728 1728
1729 1729 patchf = self.opener(patchname, "w")
1730 1730 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1731 1731 patchf.close()
1732 1732
1733 1733 se = statusentry(n, patchname)
1734 1734 self.applied.insert(0, se)
1735 1735
1736 1736 self.added.append(patchname)
1737 1737 patchname = None
1738 1738 self.parse_series()
1739 1739 self.applied_dirty = 1
1740 1740 self.series_dirty = True
1741 1741
1742 1742 for i, filename in enumerate(files):
1743 1743 if existing:
1744 1744 if filename == '-':
1745 1745 raise util.Abort(_('-e is incompatible with import from -'))
1746 1746 filename = normname(filename)
1747 1747 self.check_reserved_name(filename)
1748 1748 originpath = self.join(filename)
1749 1749 if not os.path.isfile(originpath):
1750 1750 raise util.Abort(_("patch %s does not exist") % filename)
1751 1751
1752 1752 if patchname:
1753 1753 self.check_reserved_name(patchname)
1754 1754 checkfile(patchname)
1755 1755
1756 1756 self.ui.write(_('renaming %s to %s\n')
1757 1757 % (filename, patchname))
1758 1758 util.rename(originpath, self.join(patchname))
1759 1759 else:
1760 1760 patchname = filename
1761 1761
1762 1762 else:
1763 1763 try:
1764 1764 if filename == '-':
1765 1765 if not patchname:
1766 1766 raise util.Abort(
1767 1767 _('need --name to import a patch from -'))
1768 1768 text = sys.stdin.read()
1769 1769 else:
1770 1770 text = url.open(self.ui, filename).read()
1771 1771 except (OSError, IOError):
1772 1772 raise util.Abort(_("unable to read file %s") % filename)
1773 1773 if not patchname:
1774 1774 patchname = normname(os.path.basename(filename))
1775 1775 self.check_reserved_name(patchname)
1776 1776 checkfile(patchname)
1777 1777 patchf = self.opener(patchname, "w")
1778 1778 patchf.write(text)
1779 1779 if not force:
1780 1780 checkseries(patchname)
1781 1781 if patchname not in self.series:
1782 1782 index = self.full_series_end() + i
1783 1783 self.full_series[index:index] = [patchname]
1784 1784 self.parse_series()
1785 1785 self.series_dirty = True
1786 1786 self.ui.warn(_("adding %s to series file\n") % patchname)
1787 1787 self.added.append(patchname)
1788 1788 patchname = None
1789 1789
1790 1790 def delete(ui, repo, *patches, **opts):
1791 1791 """remove patches from queue
1792 1792
1793 1793 The patches must not be applied, and at least one patch is required. With
1794 1794 -k/--keep, the patch files are preserved in the patch directory.
1795 1795
1796 1796 To stop managing a patch and move it into permanent history,
1797 1797 use the :hg:`qfinish` command."""
1798 1798 q = repo.mq
1799 1799 q.delete(repo, patches, opts)
1800 1800 q.save_dirty()
1801 1801 return 0
1802 1802
1803 1803 def applied(ui, repo, patch=None, **opts):
1804 1804 """print the patches already applied
1805 1805
1806 1806 Returns 0 on success."""
1807 1807
1808 1808 q = repo.mq
1809 1809
1810 1810 if patch:
1811 1811 if patch not in q.series:
1812 1812 raise util.Abort(_("patch %s is not in series file") % patch)
1813 1813 end = q.series.index(patch) + 1
1814 1814 else:
1815 1815 end = q.series_end(True)
1816 1816
1817 1817 if opts.get('last') and not end:
1818 1818 ui.write(_("no patches applied\n"))
1819 1819 return 1
1820 1820 elif opts.get('last') and end == 1:
1821 1821 ui.write(_("only one patch applied\n"))
1822 1822 return 1
1823 1823 elif opts.get('last'):
1824 1824 start = end - 2
1825 1825 end = 1
1826 1826 else:
1827 1827 start = 0
1828 1828
1829 1829 q.qseries(repo, length=end, start=start, status='A',
1830 1830 summary=opts.get('summary'))
1831 1831
1832 1832
1833 1833 def unapplied(ui, repo, patch=None, **opts):
1834 1834 """print the patches not yet applied
1835 1835
1836 1836 Returns 0 on success."""
1837 1837
1838 1838 q = repo.mq
1839 1839 if patch:
1840 1840 if patch not in q.series:
1841 1841 raise util.Abort(_("patch %s is not in series file") % patch)
1842 1842 start = q.series.index(patch) + 1
1843 1843 else:
1844 1844 start = q.series_end(True)
1845 1845
1846 1846 if start == len(q.series) and opts.get('first'):
1847 1847 ui.write(_("all patches applied\n"))
1848 1848 return 1
1849 1849
1850 1850 length = opts.get('first') and 1 or None
1851 1851 q.qseries(repo, start=start, length=length, status='U',
1852 1852 summary=opts.get('summary'))
1853 1853
1854 1854 def qimport(ui, repo, *filename, **opts):
1855 1855 """import a patch
1856 1856
1857 1857 The patch is inserted into the series after the last applied
1858 1858 patch. If no patches have been applied, qimport prepends the patch
1859 1859 to the series.
1860 1860
1861 1861 The patch will have the same name as its source file unless you
1862 1862 give it a new one with -n/--name.
1863 1863
1864 1864 You can register an existing patch inside the patch directory with
1865 1865 the -e/--existing flag.
1866 1866
1867 1867 With -f/--force, an existing patch of the same name will be
1868 1868 overwritten.
1869 1869
1870 1870 An existing changeset may be placed under mq control with -r/--rev
1871 1871 (e.g. qimport --rev tip -n patch will place tip under mq control).
1872 1872 With -g/--git, patches imported with --rev will use the git diff
1873 1873 format. See the diffs help topic for information on why this is
1874 1874 important for preserving rename/copy information and permission
1875 1875 changes.
1876 1876
1877 1877 To import a patch from standard input, pass - as the patch file.
1878 1878 When importing from standard input, a patch name must be specified
1879 1879 using the --name flag.
1880 1880
1881 1881 To import an existing patch while renaming it::
1882 1882
1883 1883 hg qimport -e existing-patch -n new-name
1884 1884
1885 1885 Returns 0 if import succeeded.
1886 1886 """
1887 1887 q = repo.mq
1888 1888 try:
1889 1889 q.qimport(repo, filename, patchname=opts.get('name'),
1890 1890 existing=opts.get('existing'), force=opts.get('force'),
1891 1891 rev=opts.get('rev'), git=opts.get('git'))
1892 1892 finally:
1893 1893 q.save_dirty()
1894 1894
1895 1895 if opts.get('push') and not opts.get('rev'):
1896 1896 return q.push(repo, None)
1897 1897 return 0
1898 1898
1899 1899 def qinit(ui, repo, create):
1900 1900 """initialize a new queue repository
1901 1901
1902 1902 This command also creates a series file for ordering patches, and
1903 1903 an mq-specific .hgignore file in the queue repository, to exclude
1904 1904 the status and guards files (these contain mostly transient state).
1905 1905
1906 1906 Returns 0 if initialization succeeded."""
1907 1907 q = repo.mq
1908 1908 r = q.init(repo, create)
1909 1909 q.save_dirty()
1910 1910 if r:
1911 1911 if not os.path.exists(r.wjoin('.hgignore')):
1912 1912 fp = r.wopener('.hgignore', 'w')
1913 1913 fp.write('^\\.hg\n')
1914 1914 fp.write('^\\.mq\n')
1915 1915 fp.write('syntax: glob\n')
1916 1916 fp.write('status\n')
1917 1917 fp.write('guards\n')
1918 1918 fp.close()
1919 1919 if not os.path.exists(r.wjoin('series')):
1920 1920 r.wopener('series', 'w').close()
1921 1921 r[None].add(['.hgignore', 'series'])
1922 1922 commands.add(ui, r)
1923 1923 return 0
1924 1924
1925 1925 def init(ui, repo, **opts):
1926 1926 """init a new queue repository (DEPRECATED)
1927 1927
1928 1928 The queue repository is unversioned by default. If
1929 1929 -c/--create-repo is specified, qinit will create a separate nested
1930 1930 repository for patches (qinit -c may also be run later to convert
1931 1931 an unversioned patch repository into a versioned one). You can use
1932 1932 qcommit to commit changes to this queue repository.
1933 1933
1934 1934 This command is deprecated. Without -c, it's implied by other relevant
1935 1935 commands. With -c, use :hg:`init --mq` instead."""
1936 1936 return qinit(ui, repo, create=opts.get('create_repo'))
1937 1937
1938 1938 def clone(ui, source, dest=None, **opts):
1939 1939 '''clone main and patch repository at same time
1940 1940
1941 1941 If source is local, destination will have no patches applied. If
1942 1942 source is remote, this command can not check if patches are
1943 1943 applied in source, so cannot guarantee that patches are not
1944 1944 applied in destination. If you clone remote repository, be sure
1945 1945 before that it has no patches applied.
1946 1946
1947 1947 Source patch repository is looked for in <src>/.hg/patches by
1948 1948 default. Use -p <url> to change.
1949 1949
1950 1950 The patch directory must be a nested Mercurial repository, as
1951 1951 would be created by :hg:`init --mq`.
1952 1952
1953 1953 Return 0 on success.
1954 1954 '''
1955 1955 def patchdir(repo):
1956 1956 url = repo.url()
1957 1957 if url.endswith('/'):
1958 1958 url = url[:-1]
1959 1959 return url + '/.hg/patches'
1960 1960 if dest is None:
1961 1961 dest = hg.defaultdest(source)
1962 1962 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
1963 1963 if opts.get('patches'):
1964 1964 patchespath = ui.expandpath(opts.get('patches'))
1965 1965 else:
1966 1966 patchespath = patchdir(sr)
1967 1967 try:
1968 1968 hg.repository(ui, patchespath)
1969 1969 except error.RepoError:
1970 1970 raise util.Abort(_('versioned patch repository not found'
1971 1971 ' (see init --mq)'))
1972 1972 qbase, destrev = None, None
1973 1973 if sr.local():
1974 1974 if sr.mq.applied:
1975 1975 qbase = sr.mq.applied[0].node
1976 1976 if not hg.islocal(dest):
1977 1977 heads = set(sr.heads())
1978 1978 destrev = list(heads.difference(sr.heads(qbase)))
1979 1979 destrev.append(sr.changelog.parents(qbase)[0])
1980 1980 elif sr.capable('lookup'):
1981 1981 try:
1982 1982 qbase = sr.lookup('qbase')
1983 1983 except error.RepoError:
1984 1984 pass
1985 1985 ui.note(_('cloning main repository\n'))
1986 1986 sr, dr = hg.clone(ui, sr.url(), dest,
1987 1987 pull=opts.get('pull'),
1988 1988 rev=destrev,
1989 1989 update=False,
1990 1990 stream=opts.get('uncompressed'))
1991 1991 ui.note(_('cloning patch repository\n'))
1992 1992 hg.clone(ui, opts.get('patches') or patchdir(sr), patchdir(dr),
1993 1993 pull=opts.get('pull'), update=not opts.get('noupdate'),
1994 1994 stream=opts.get('uncompressed'))
1995 1995 if dr.local():
1996 1996 if qbase:
1997 1997 ui.note(_('stripping applied patches from destination '
1998 1998 'repository\n'))
1999 1999 dr.mq.strip(dr, [qbase], update=False, backup=None)
2000 2000 if not opts.get('noupdate'):
2001 2001 ui.note(_('updating destination repository\n'))
2002 2002 hg.update(dr, dr.changelog.tip())
2003 2003
2004 2004 def commit(ui, repo, *pats, **opts):
2005 2005 """commit changes in the queue repository (DEPRECATED)
2006 2006
2007 2007 This command is deprecated; use :hg:`commit --mq` instead."""
2008 2008 q = repo.mq
2009 2009 r = q.qrepo()
2010 2010 if not r:
2011 2011 raise util.Abort('no queue repository')
2012 2012 commands.commit(r.ui, r, *pats, **opts)
2013 2013
2014 2014 def series(ui, repo, **opts):
2015 2015 """print the entire series file
2016 2016
2017 2017 Returns 0 on success."""
2018 2018 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
2019 2019 return 0
2020 2020
2021 2021 def top(ui, repo, **opts):
2022 2022 """print the name of the current patch
2023 2023
2024 2024 Returns 0 on success."""
2025 2025 q = repo.mq
2026 2026 t = q.applied and q.series_end(True) or 0
2027 2027 if t:
2028 2028 q.qseries(repo, start=t - 1, length=1, status='A',
2029 2029 summary=opts.get('summary'))
2030 2030 else:
2031 2031 ui.write(_("no patches applied\n"))
2032 2032 return 1
2033 2033
2034 2034 def next(ui, repo, **opts):
2035 2035 """print the name of the next patch
2036 2036
2037 2037 Returns 0 on success."""
2038 2038 q = repo.mq
2039 2039 end = q.series_end()
2040 2040 if end == len(q.series):
2041 2041 ui.write(_("all patches applied\n"))
2042 2042 return 1
2043 2043 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2044 2044
2045 2045 def prev(ui, repo, **opts):
2046 2046 """print the name of the previous patch
2047 2047
2048 2048 Returns 0 on success."""
2049 2049 q = repo.mq
2050 2050 l = len(q.applied)
2051 2051 if l == 1:
2052 2052 ui.write(_("only one patch applied\n"))
2053 2053 return 1
2054 2054 if not l:
2055 2055 ui.write(_("no patches applied\n"))
2056 2056 return 1
2057 2057 q.qseries(repo, start=l - 2, length=1, status='A',
2058 2058 summary=opts.get('summary'))
2059 2059
2060 2060 def setupheaderopts(ui, opts):
2061 2061 if not opts.get('user') and opts.get('currentuser'):
2062 2062 opts['user'] = ui.username()
2063 2063 if not opts.get('date') and opts.get('currentdate'):
2064 2064 opts['date'] = "%d %d" % util.makedate()
2065 2065
2066 2066 def new(ui, repo, patch, *args, **opts):
2067 2067 """create a new patch
2068 2068
2069 2069 qnew creates a new patch on top of the currently-applied patch (if
2070 2070 any). The patch will be initialized with any outstanding changes
2071 2071 in the working directory. You may also use -I/--include,
2072 2072 -X/--exclude, and/or a list of files after the patch name to add
2073 2073 only changes to matching files to the new patch, leaving the rest
2074 2074 as uncommitted modifications.
2075 2075
2076 2076 -u/--user and -d/--date can be used to set the (given) user and
2077 2077 date, respectively. -U/--currentuser and -D/--currentdate set user
2078 2078 to current user and date to current date.
2079 2079
2080 2080 -e/--edit, -m/--message or -l/--logfile set the patch header as
2081 2081 well as the commit message. If none is specified, the header is
2082 2082 empty and the commit message is '[mq]: PATCH'.
2083 2083
2084 2084 Use the -g/--git option to keep the patch in the git extended diff
2085 2085 format. Read the diffs help topic for more information on why this
2086 2086 is important for preserving permission changes and copy/rename
2087 2087 information.
2088 2088
2089 2089 Returns 0 on successful creation of a new patch.
2090 2090 """
2091 2091 msg = cmdutil.logmessage(opts)
2092 2092 def getmsg():
2093 2093 return ui.edit(msg, opts.get('user') or ui.username())
2094 2094 q = repo.mq
2095 2095 opts['msg'] = msg
2096 2096 if opts.get('edit'):
2097 2097 opts['msg'] = getmsg
2098 2098 else:
2099 2099 opts['msg'] = msg
2100 2100 setupheaderopts(ui, opts)
2101 2101 q.new(repo, patch, *args, **opts)
2102 2102 q.save_dirty()
2103 2103 return 0
2104 2104
2105 2105 def refresh(ui, repo, *pats, **opts):
2106 2106 """update the current patch
2107 2107
2108 2108 If any file patterns are provided, the refreshed patch will
2109 2109 contain only the modifications that match those patterns; the
2110 2110 remaining modifications will remain in the working directory.
2111 2111
2112 2112 If -s/--short is specified, files currently included in the patch
2113 2113 will be refreshed just like matched files and remain in the patch.
2114 2114
2115 2115 If -e/--edit is specified, Mercurial will start your configured editor for
2116 2116 you to enter a message. In case qrefresh fails, you will find a backup of
2117 2117 your message in ``.hg/last-message.txt``.
2118 2118
2119 2119 hg add/remove/copy/rename work as usual, though you might want to
2120 2120 use git-style patches (-g/--git or [diff] git=1) to track copies
2121 2121 and renames. See the diffs help topic for more information on the
2122 2122 git diff format.
2123 2123
2124 2124 Returns 0 on success.
2125 2125 """
2126 2126 q = repo.mq
2127 2127 message = cmdutil.logmessage(opts)
2128 2128 if opts.get('edit'):
2129 2129 if not q.applied:
2130 2130 ui.write(_("no patches applied\n"))
2131 2131 return 1
2132 2132 if message:
2133 2133 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2134 2134 patch = q.applied[-1].name
2135 2135 ph = patchheader(q.join(patch), q.plainmode)
2136 2136 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2137 2137 # We don't want to lose the patch message if qrefresh fails (issue2062)
2138 2138 msgfile = repo.opener('last-message.txt', 'wb')
2139 2139 msgfile.write(message)
2140 2140 msgfile.close()
2141 2141 setupheaderopts(ui, opts)
2142 2142 ret = q.refresh(repo, pats, msg=message, **opts)
2143 2143 q.save_dirty()
2144 2144 return ret
2145 2145
2146 2146 def diff(ui, repo, *pats, **opts):
2147 2147 """diff of the current patch and subsequent modifications
2148 2148
2149 2149 Shows a diff which includes the current patch as well as any
2150 2150 changes which have been made in the working directory since the
2151 2151 last refresh (thus showing what the current patch would become
2152 2152 after a qrefresh).
2153 2153
2154 2154 Use :hg:`diff` if you only want to see the changes made since the
2155 2155 last qrefresh, or :hg:`export qtip` if you want to see changes
2156 2156 made by the current patch without including changes made since the
2157 2157 qrefresh.
2158 2158
2159 2159 Returns 0 on success.
2160 2160 """
2161 2161 repo.mq.diff(repo, pats, opts)
2162 2162 return 0
2163 2163
2164 2164 def fold(ui, repo, *files, **opts):
2165 2165 """fold the named patches into the current patch
2166 2166
2167 2167 Patches must not yet be applied. Each patch will be successively
2168 2168 applied to the current patch in the order given. If all the
2169 2169 patches apply successfully, the current patch will be refreshed
2170 2170 with the new cumulative patch, and the folded patches will be
2171 2171 deleted. With -k/--keep, the folded patch files will not be
2172 2172 removed afterwards.
2173 2173
2174 2174 The header for each folded patch will be concatenated with the
2175 2175 current patch header, separated by a line of ``* * *``.
2176 2176
2177 2177 Returns 0 on success."""
2178 2178
2179 2179 q = repo.mq
2180 2180
2181 2181 if not files:
2182 2182 raise util.Abort(_('qfold requires at least one patch name'))
2183 2183 if not q.check_toppatch(repo)[0]:
2184 2184 raise util.Abort(_('no patches applied'))
2185 2185 q.check_localchanges(repo)
2186 2186
2187 2187 message = cmdutil.logmessage(opts)
2188 2188 if opts.get('edit'):
2189 2189 if message:
2190 2190 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2191 2191
2192 2192 parent = q.lookup('qtip')
2193 2193 patches = []
2194 2194 messages = []
2195 2195 for f in files:
2196 2196 p = q.lookup(f)
2197 2197 if p in patches or p == parent:
2198 2198 ui.warn(_('Skipping already folded patch %s\n') % p)
2199 2199 if q.isapplied(p):
2200 2200 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2201 2201 patches.append(p)
2202 2202
2203 2203 for p in patches:
2204 2204 if not message:
2205 2205 ph = patchheader(q.join(p), q.plainmode)
2206 2206 if ph.message:
2207 2207 messages.append(ph.message)
2208 2208 pf = q.join(p)
2209 2209 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2210 2210 if not patchsuccess:
2211 2211 raise util.Abort(_('error folding patch %s') % p)
2212 2212 cmdutil.updatedir(ui, repo, files)
2213 2213
2214 2214 if not message:
2215 2215 ph = patchheader(q.join(parent), q.plainmode)
2216 2216 message, user = ph.message, ph.user
2217 2217 for msg in messages:
2218 2218 message.append('* * *')
2219 2219 message.extend(msg)
2220 2220 message = '\n'.join(message)
2221 2221
2222 2222 if opts.get('edit'):
2223 2223 message = ui.edit(message, user or ui.username())
2224 2224
2225 2225 diffopts = q.patchopts(q.diffopts(), *patches)
2226 2226 q.refresh(repo, msg=message, git=diffopts.git)
2227 2227 q.delete(repo, patches, opts)
2228 2228 q.save_dirty()
2229 2229
2230 2230 def goto(ui, repo, patch, **opts):
2231 2231 '''push or pop patches until named patch is at top of stack
2232 2232
2233 2233 Returns 0 on success.'''
2234 2234 q = repo.mq
2235 2235 patch = q.lookup(patch)
2236 2236 if q.isapplied(patch):
2237 2237 ret = q.pop(repo, patch, force=opts.get('force'))
2238 2238 else:
2239 2239 ret = q.push(repo, patch, force=opts.get('force'))
2240 2240 q.save_dirty()
2241 2241 return ret
2242 2242
2243 2243 def guard(ui, repo, *args, **opts):
2244 2244 '''set or print guards for a patch
2245 2245
2246 2246 Guards control whether a patch can be pushed. A patch with no
2247 2247 guards is always pushed. A patch with a positive guard ("+foo") is
2248 2248 pushed only if the :hg:`qselect` command has activated it. A patch with
2249 2249 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2250 2250 has activated it.
2251 2251
2252 2252 With no arguments, print the currently active guards.
2253 2253 With arguments, set guards for the named patch.
2254 2254
2255 2255 .. note::
2256 2256 Specifying negative guards now requires '--'.
2257 2257
2258 2258 To set guards on another patch::
2259 2259
2260 2260 hg qguard other.patch -- +2.6.17 -stable
2261 2261
2262 2262 Returns 0 on success.
2263 2263 '''
2264 2264 def status(idx):
2265 2265 guards = q.series_guards[idx] or ['unguarded']
2266 2266 if q.series[idx] in applied:
2267 2267 state = 'applied'
2268 2268 elif q.pushable(idx)[0]:
2269 2269 state = 'unapplied'
2270 2270 else:
2271 2271 state = 'guarded'
2272 2272 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2273 2273 ui.write('%s: ' % ui.label(q.series[idx], label))
2274 2274
2275 2275 for i, guard in enumerate(guards):
2276 2276 if guard.startswith('+'):
2277 2277 ui.write(guard, label='qguard.positive')
2278 2278 elif guard.startswith('-'):
2279 2279 ui.write(guard, label='qguard.negative')
2280 2280 else:
2281 2281 ui.write(guard, label='qguard.unguarded')
2282 2282 if i != len(guards) - 1:
2283 2283 ui.write(' ')
2284 2284 ui.write('\n')
2285 2285 q = repo.mq
2286 2286 applied = set(p.name for p in q.applied)
2287 2287 patch = None
2288 2288 args = list(args)
2289 2289 if opts.get('list'):
2290 2290 if args or opts.get('none'):
2291 2291 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2292 2292 for i in xrange(len(q.series)):
2293 2293 status(i)
2294 2294 return
2295 2295 if not args or args[0][0:1] in '-+':
2296 2296 if not q.applied:
2297 2297 raise util.Abort(_('no patches applied'))
2298 2298 patch = q.applied[-1].name
2299 2299 if patch is None and args[0][0:1] not in '-+':
2300 2300 patch = args.pop(0)
2301 2301 if patch is None:
2302 2302 raise util.Abort(_('no patch to work with'))
2303 2303 if args or opts.get('none'):
2304 2304 idx = q.find_series(patch)
2305 2305 if idx is None:
2306 2306 raise util.Abort(_('no patch named %s') % patch)
2307 2307 q.set_guards(idx, args)
2308 2308 q.save_dirty()
2309 2309 else:
2310 2310 status(q.series.index(q.lookup(patch)))
2311 2311
2312 2312 def header(ui, repo, patch=None):
2313 2313 """print the header of the topmost or specified patch
2314 2314
2315 2315 Returns 0 on success."""
2316 2316 q = repo.mq
2317 2317
2318 2318 if patch:
2319 2319 patch = q.lookup(patch)
2320 2320 else:
2321 2321 if not q.applied:
2322 2322 ui.write(_('no patches applied\n'))
2323 2323 return 1
2324 2324 patch = q.lookup('qtip')
2325 2325 ph = patchheader(q.join(patch), q.plainmode)
2326 2326
2327 2327 ui.write('\n'.join(ph.message) + '\n')
2328 2328
2329 2329 def lastsavename(path):
2330 2330 (directory, base) = os.path.split(path)
2331 2331 names = os.listdir(directory)
2332 2332 namere = re.compile("%s.([0-9]+)" % base)
2333 2333 maxindex = None
2334 2334 maxname = None
2335 2335 for f in names:
2336 2336 m = namere.match(f)
2337 2337 if m:
2338 2338 index = int(m.group(1))
2339 2339 if maxindex is None or index > maxindex:
2340 2340 maxindex = index
2341 2341 maxname = f
2342 2342 if maxname:
2343 2343 return (os.path.join(directory, maxname), maxindex)
2344 2344 return (None, None)
2345 2345
2346 2346 def savename(path):
2347 2347 (last, index) = lastsavename(path)
2348 2348 if last is None:
2349 2349 index = 0
2350 2350 newpath = path + ".%d" % (index + 1)
2351 2351 return newpath
2352 2352
2353 2353 def push(ui, repo, patch=None, **opts):
2354 2354 """push the next patch onto the stack
2355 2355
2356 2356 When -f/--force is applied, all local changes in patched files
2357 2357 will be lost.
2358 2358
2359 2359 Return 0 on succces.
2360 2360 """
2361 2361 q = repo.mq
2362 2362 mergeq = None
2363 2363
2364 2364 if opts.get('merge'):
2365 2365 if opts.get('name'):
2366 2366 newpath = repo.join(opts.get('name'))
2367 2367 else:
2368 2368 newpath, i = lastsavename(q.path)
2369 2369 if not newpath:
2370 2370 ui.warn(_("no saved queues found, please use -n\n"))
2371 2371 return 1
2372 2372 mergeq = queue(ui, repo.join(""), newpath)
2373 2373 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2374 2374 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2375 2375 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2376 2376 exact=opts.get('exact'))
2377 2377 return ret
2378 2378
2379 2379 def pop(ui, repo, patch=None, **opts):
2380 2380 """pop the current patch off the stack
2381 2381
2382 2382 By default, pops off the top of the patch stack. If given a patch
2383 2383 name, keeps popping off patches until the named patch is at the
2384 2384 top of the stack.
2385 2385
2386 2386 Return 0 on success.
2387 2387 """
2388 2388 localupdate = True
2389 2389 if opts.get('name'):
2390 2390 q = queue(ui, repo.join(""), repo.join(opts.get('name')))
2391 2391 ui.warn(_('using patch queue: %s\n') % q.path)
2392 2392 localupdate = False
2393 2393 else:
2394 2394 q = repo.mq
2395 2395 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2396 2396 all=opts.get('all'))
2397 2397 q.save_dirty()
2398 2398 return ret
2399 2399
2400 2400 def rename(ui, repo, patch, name=None, **opts):
2401 2401 """rename a patch
2402 2402
2403 2403 With one argument, renames the current patch to PATCH1.
2404 2404 With two arguments, renames PATCH1 to PATCH2.
2405 2405
2406 2406 Returns 0 on success."""
2407 2407
2408 2408 q = repo.mq
2409 2409
2410 2410 if not name:
2411 2411 name = patch
2412 2412 patch = None
2413 2413
2414 2414 if patch:
2415 2415 patch = q.lookup(patch)
2416 2416 else:
2417 2417 if not q.applied:
2418 2418 ui.write(_('no patches applied\n'))
2419 2419 return
2420 2420 patch = q.lookup('qtip')
2421 2421 absdest = q.join(name)
2422 2422 if os.path.isdir(absdest):
2423 2423 name = normname(os.path.join(name, os.path.basename(patch)))
2424 2424 absdest = q.join(name)
2425 2425 if os.path.exists(absdest):
2426 2426 raise util.Abort(_('%s already exists') % absdest)
2427 2427
2428 2428 if name in q.series:
2429 2429 raise util.Abort(
2430 2430 _('A patch named %s already exists in the series file') % name)
2431 2431
2432 2432 ui.note(_('renaming %s to %s\n') % (patch, name))
2433 2433 i = q.find_series(patch)
2434 2434 guards = q.guard_re.findall(q.full_series[i])
2435 2435 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2436 2436 q.parse_series()
2437 2437 q.series_dirty = 1
2438 2438
2439 2439 info = q.isapplied(patch)
2440 2440 if info:
2441 2441 q.applied[info[0]] = statusentry(info[1], name)
2442 2442 q.applied_dirty = 1
2443 2443
2444 2444 destdir = os.path.dirname(absdest)
2445 2445 if not os.path.isdir(destdir):
2446 2446 os.makedirs(destdir)
2447 2447 util.rename(q.join(patch), absdest)
2448 2448 r = q.qrepo()
2449 2449 if r and patch in r.dirstate:
2450 2450 wctx = r[None]
2451 2451 wlock = r.wlock()
2452 2452 try:
2453 2453 if r.dirstate[patch] == 'a':
2454 2454 r.dirstate.forget(patch)
2455 2455 r.dirstate.add(name)
2456 2456 else:
2457 2457 if r.dirstate[name] == 'r':
2458 2458 wctx.undelete([name])
2459 2459 wctx.copy(patch, name)
2460 2460 wctx.remove([patch], False)
2461 2461 finally:
2462 2462 wlock.release()
2463 2463
2464 2464 q.save_dirty()
2465 2465
2466 2466 def restore(ui, repo, rev, **opts):
2467 2467 """restore the queue state saved by a revision (DEPRECATED)
2468 2468
2469 2469 This command is deprecated, use :hg:`rebase` instead."""
2470 2470 rev = repo.lookup(rev)
2471 2471 q = repo.mq
2472 2472 q.restore(repo, rev, delete=opts.get('delete'),
2473 2473 qupdate=opts.get('update'))
2474 2474 q.save_dirty()
2475 2475 return 0
2476 2476
2477 2477 def save(ui, repo, **opts):
2478 2478 """save current queue state (DEPRECATED)
2479 2479
2480 2480 This command is deprecated, use :hg:`rebase` instead."""
2481 2481 q = repo.mq
2482 2482 message = cmdutil.logmessage(opts)
2483 2483 ret = q.save(repo, msg=message)
2484 2484 if ret:
2485 2485 return ret
2486 2486 q.save_dirty()
2487 2487 if opts.get('copy'):
2488 2488 path = q.path
2489 2489 if opts.get('name'):
2490 2490 newpath = os.path.join(q.basepath, opts.get('name'))
2491 2491 if os.path.exists(newpath):
2492 2492 if not os.path.isdir(newpath):
2493 2493 raise util.Abort(_('destination %s exists and is not '
2494 2494 'a directory') % newpath)
2495 2495 if not opts.get('force'):
2496 2496 raise util.Abort(_('destination %s exists, '
2497 2497 'use -f to force') % newpath)
2498 2498 else:
2499 2499 newpath = savename(path)
2500 2500 ui.warn(_("copy %s to %s\n") % (path, newpath))
2501 2501 util.copyfiles(path, newpath)
2502 2502 if opts.get('empty'):
2503 2503 try:
2504 2504 os.unlink(q.join(q.status_path))
2505 2505 except:
2506 2506 pass
2507 2507 return 0
2508 2508
2509 2509 def strip(ui, repo, *revs, **opts):
2510 2510 """strip changesets and all their descendants from the repository
2511 2511
2512 2512 The strip command removes the specified changesets and all their
2513 2513 descendants. If the working directory has uncommitted changes,
2514 2514 the operation is aborted unless the --force flag is supplied.
2515 2515
2516 2516 If a parent of the working directory is stripped, then the working
2517 2517 directory will automatically be updated to the most recent
2518 2518 available ancestor of the stripped parent after the operation
2519 2519 completes.
2520 2520
2521 2521 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2522 2522 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2523 2523 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2524 2524 where BUNDLE is the bundle file created by the strip. Note that
2525 2525 the local revision numbers will in general be different after the
2526 2526 restore.
2527 2527
2528 2528 Use the --no-backup option to discard the backup bundle once the
2529 2529 operation completes.
2530 2530
2531 2531 Return 0 on success.
2532 2532 """
2533 2533 backup = 'all'
2534 2534 if opts.get('backup'):
2535 2535 backup = 'strip'
2536 elif opts.get('no-backup') or opts.get('nobackup'):
2536 elif opts.get('no_backup') or opts.get('nobackup'):
2537 2537 backup = 'none'
2538 2538
2539 2539 cl = repo.changelog
2540 2540 revs = set(cmdutil.revrange(repo, revs))
2541 2541 if not revs:
2542 2542 raise util.Abort(_('empty revision set'))
2543 2543
2544 2544 descendants = set(cl.descendants(*revs))
2545 2545 strippedrevs = revs.union(descendants)
2546 2546 roots = revs.difference(descendants)
2547 2547
2548 2548 update = False
2549 2549 # if one of the wdir parent is stripped we'll need
2550 2550 # to update away to an earlier revision
2551 2551 for p in repo.dirstate.parents():
2552 2552 if p != nullid and cl.rev(p) in strippedrevs:
2553 2553 update = True
2554 2554 break
2555 2555
2556 2556 rootnodes = set(cl.node(r) for r in roots)
2557 2557
2558 2558 q = repo.mq
2559 2559 if q.applied:
2560 2560 # refresh queue state if we're about to strip
2561 2561 # applied patches
2562 2562 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2563 2563 q.applied_dirty = True
2564 2564 start = 0
2565 2565 end = len(q.applied)
2566 2566 for i, statusentry in enumerate(q.applied):
2567 2567 if statusentry.node in rootnodes:
2568 2568 # if one of the stripped roots is an applied
2569 2569 # patch, only part of the queue is stripped
2570 2570 start = i
2571 2571 break
2572 2572 del q.applied[start:end]
2573 2573 q.save_dirty()
2574 2574
2575 2575 revs = list(rootnodes)
2576 2576 if update and opts.get('keep'):
2577 2577 wlock = repo.wlock()
2578 2578 try:
2579 2579 urev = repo.mq.qparents(repo, revs[0])
2580 2580 repo.dirstate.rebuild(urev, repo[urev].manifest())
2581 2581 repo.dirstate.write()
2582 2582 update = False
2583 2583 finally:
2584 2584 wlock.release()
2585 2585
2586 2586 repo.mq.strip(repo, revs, backup=backup, update=update,
2587 2587 force=opts.get('force'))
2588 2588 return 0
2589 2589
2590 2590 def select(ui, repo, *args, **opts):
2591 2591 '''set or print guarded patches to push
2592 2592
2593 2593 Use the :hg:`qguard` command to set or print guards on patch, then use
2594 2594 qselect to tell mq which guards to use. A patch will be pushed if
2595 2595 it has no guards or any positive guards match the currently
2596 2596 selected guard, but will not be pushed if any negative guards
2597 2597 match the current guard. For example::
2598 2598
2599 2599 qguard foo.patch -stable (negative guard)
2600 2600 qguard bar.patch +stable (positive guard)
2601 2601 qselect stable
2602 2602
2603 2603 This activates the "stable" guard. mq will skip foo.patch (because
2604 2604 it has a negative match) but push bar.patch (because it has a
2605 2605 positive match).
2606 2606
2607 2607 With no arguments, prints the currently active guards.
2608 2608 With one argument, sets the active guard.
2609 2609
2610 2610 Use -n/--none to deactivate guards (no other arguments needed).
2611 2611 When no guards are active, patches with positive guards are
2612 2612 skipped and patches with negative guards are pushed.
2613 2613
2614 2614 qselect can change the guards on applied patches. It does not pop
2615 2615 guarded patches by default. Use --pop to pop back to the last
2616 2616 applied patch that is not guarded. Use --reapply (which implies
2617 2617 --pop) to push back to the current patch afterwards, but skip
2618 2618 guarded patches.
2619 2619
2620 2620 Use -s/--series to print a list of all guards in the series file
2621 2621 (no other arguments needed). Use -v for more information.
2622 2622
2623 2623 Returns 0 on success.'''
2624 2624
2625 2625 q = repo.mq
2626 2626 guards = q.active()
2627 2627 if args or opts.get('none'):
2628 2628 old_unapplied = q.unapplied(repo)
2629 2629 old_guarded = [i for i in xrange(len(q.applied)) if
2630 2630 not q.pushable(i)[0]]
2631 2631 q.set_active(args)
2632 2632 q.save_dirty()
2633 2633 if not args:
2634 2634 ui.status(_('guards deactivated\n'))
2635 2635 if not opts.get('pop') and not opts.get('reapply'):
2636 2636 unapplied = q.unapplied(repo)
2637 2637 guarded = [i for i in xrange(len(q.applied))
2638 2638 if not q.pushable(i)[0]]
2639 2639 if len(unapplied) != len(old_unapplied):
2640 2640 ui.status(_('number of unguarded, unapplied patches has '
2641 2641 'changed from %d to %d\n') %
2642 2642 (len(old_unapplied), len(unapplied)))
2643 2643 if len(guarded) != len(old_guarded):
2644 2644 ui.status(_('number of guarded, applied patches has changed '
2645 2645 'from %d to %d\n') %
2646 2646 (len(old_guarded), len(guarded)))
2647 2647 elif opts.get('series'):
2648 2648 guards = {}
2649 2649 noguards = 0
2650 2650 for gs in q.series_guards:
2651 2651 if not gs:
2652 2652 noguards += 1
2653 2653 for g in gs:
2654 2654 guards.setdefault(g, 0)
2655 2655 guards[g] += 1
2656 2656 if ui.verbose:
2657 2657 guards['NONE'] = noguards
2658 2658 guards = guards.items()
2659 2659 guards.sort(key=lambda x: x[0][1:])
2660 2660 if guards:
2661 2661 ui.note(_('guards in series file:\n'))
2662 2662 for guard, count in guards:
2663 2663 ui.note('%2d ' % count)
2664 2664 ui.write(guard, '\n')
2665 2665 else:
2666 2666 ui.note(_('no guards in series file\n'))
2667 2667 else:
2668 2668 if guards:
2669 2669 ui.note(_('active guards:\n'))
2670 2670 for g in guards:
2671 2671 ui.write(g, '\n')
2672 2672 else:
2673 2673 ui.write(_('no active guards\n'))
2674 2674 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2675 2675 popped = False
2676 2676 if opts.get('pop') or opts.get('reapply'):
2677 2677 for i in xrange(len(q.applied)):
2678 2678 pushable, reason = q.pushable(i)
2679 2679 if not pushable:
2680 2680 ui.status(_('popping guarded patches\n'))
2681 2681 popped = True
2682 2682 if i == 0:
2683 2683 q.pop(repo, all=True)
2684 2684 else:
2685 2685 q.pop(repo, i - 1)
2686 2686 break
2687 2687 if popped:
2688 2688 try:
2689 2689 if reapply:
2690 2690 ui.status(_('reapplying unguarded patches\n'))
2691 2691 q.push(repo, reapply)
2692 2692 finally:
2693 2693 q.save_dirty()
2694 2694
2695 2695 def finish(ui, repo, *revrange, **opts):
2696 2696 """move applied patches into repository history
2697 2697
2698 2698 Finishes the specified revisions (corresponding to applied
2699 2699 patches) by moving them out of mq control into regular repository
2700 2700 history.
2701 2701
2702 2702 Accepts a revision range or the -a/--applied option. If --applied
2703 2703 is specified, all applied mq revisions are removed from mq
2704 2704 control. Otherwise, the given revisions must be at the base of the
2705 2705 stack of applied patches.
2706 2706
2707 2707 This can be especially useful if your changes have been applied to
2708 2708 an upstream repository, or if you are about to push your changes
2709 2709 to upstream.
2710 2710
2711 2711 Returns 0 on success.
2712 2712 """
2713 2713 if not opts.get('applied') and not revrange:
2714 2714 raise util.Abort(_('no revisions specified'))
2715 2715 elif opts.get('applied'):
2716 2716 revrange = ('qbase::qtip',) + revrange
2717 2717
2718 2718 q = repo.mq
2719 2719 if not q.applied:
2720 2720 ui.status(_('no patches applied\n'))
2721 2721 return 0
2722 2722
2723 2723 revs = cmdutil.revrange(repo, revrange)
2724 2724 q.finish(repo, revs)
2725 2725 q.save_dirty()
2726 2726 return 0
2727 2727
2728 2728 def qqueue(ui, repo, name=None, **opts):
2729 2729 '''manage multiple patch queues
2730 2730
2731 2731 Supports switching between different patch queues, as well as creating
2732 2732 new patch queues and deleting existing ones.
2733 2733
2734 2734 Omitting a queue name or specifying -l/--list will show you the registered
2735 2735 queues - by default the "normal" patches queue is registered. The currently
2736 2736 active queue will be marked with "(active)".
2737 2737
2738 2738 To create a new queue, use -c/--create. The queue is automatically made
2739 2739 active, except in the case where there are applied patches from the
2740 2740 currently active queue in the repository. Then the queue will only be
2741 2741 created and switching will fail.
2742 2742
2743 2743 To delete an existing queue, use --delete. You cannot delete the currently
2744 2744 active queue.
2745 2745
2746 2746 Returns 0 on success.
2747 2747 '''
2748 2748
2749 2749 q = repo.mq
2750 2750
2751 2751 _defaultqueue = 'patches'
2752 2752 _allqueues = 'patches.queues'
2753 2753 _activequeue = 'patches.queue'
2754 2754
2755 2755 def _getcurrent():
2756 2756 cur = os.path.basename(q.path)
2757 2757 if cur.startswith('patches-'):
2758 2758 cur = cur[8:]
2759 2759 return cur
2760 2760
2761 2761 def _noqueues():
2762 2762 try:
2763 2763 fh = repo.opener(_allqueues, 'r')
2764 2764 fh.close()
2765 2765 except IOError:
2766 2766 return True
2767 2767
2768 2768 return False
2769 2769
2770 2770 def _getqueues():
2771 2771 current = _getcurrent()
2772 2772
2773 2773 try:
2774 2774 fh = repo.opener(_allqueues, 'r')
2775 2775 queues = [queue.strip() for queue in fh if queue.strip()]
2776 2776 if current not in queues:
2777 2777 queues.append(current)
2778 2778 except IOError:
2779 2779 queues = [_defaultqueue]
2780 2780
2781 2781 return sorted(queues)
2782 2782
2783 2783 def _setactive(name):
2784 2784 if q.applied:
2785 2785 raise util.Abort(_('patches applied - cannot set new queue active'))
2786 2786 _setactivenocheck(name)
2787 2787
2788 2788 def _setactivenocheck(name):
2789 2789 fh = repo.opener(_activequeue, 'w')
2790 2790 if name != 'patches':
2791 2791 fh.write(name)
2792 2792 fh.close()
2793 2793
2794 2794 def _addqueue(name):
2795 2795 fh = repo.opener(_allqueues, 'a')
2796 2796 fh.write('%s\n' % (name,))
2797 2797 fh.close()
2798 2798
2799 2799 def _queuedir(name):
2800 2800 if name == 'patches':
2801 2801 return repo.join('patches')
2802 2802 else:
2803 2803 return repo.join('patches-' + name)
2804 2804
2805 2805 def _validname(name):
2806 2806 for n in name:
2807 2807 if n in ':\\/.':
2808 2808 return False
2809 2809 return True
2810 2810
2811 2811 def _delete(name):
2812 2812 if name not in existing:
2813 2813 raise util.Abort(_('cannot delete queue that does not exist'))
2814 2814
2815 2815 current = _getcurrent()
2816 2816
2817 2817 if name == current:
2818 2818 raise util.Abort(_('cannot delete currently active queue'))
2819 2819
2820 2820 fh = repo.opener('patches.queues.new', 'w')
2821 2821 for queue in existing:
2822 2822 if queue == name:
2823 2823 continue
2824 2824 fh.write('%s\n' % (queue,))
2825 2825 fh.close()
2826 2826 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2827 2827
2828 2828 if not name or opts.get('list'):
2829 2829 current = _getcurrent()
2830 2830 for queue in _getqueues():
2831 2831 ui.write('%s' % (queue,))
2832 2832 if queue == current and not ui.quiet:
2833 2833 ui.write(_(' (active)\n'))
2834 2834 else:
2835 2835 ui.write('\n')
2836 2836 return
2837 2837
2838 2838 if not _validname(name):
2839 2839 raise util.Abort(
2840 2840 _('invalid queue name, may not contain the characters ":\\/."'))
2841 2841
2842 2842 existing = _getqueues()
2843 2843
2844 2844 if opts.get('create'):
2845 2845 if name in existing:
2846 2846 raise util.Abort(_('queue "%s" already exists') % name)
2847 2847 if _noqueues():
2848 2848 _addqueue(_defaultqueue)
2849 2849 _addqueue(name)
2850 2850 _setactive(name)
2851 2851 elif opts.get('rename'):
2852 2852 current = _getcurrent()
2853 2853 if name == current:
2854 2854 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
2855 2855 if name in existing:
2856 2856 raise util.Abort(_('queue "%s" already exists') % name)
2857 2857
2858 2858 olddir = _queuedir(current)
2859 2859 newdir = _queuedir(name)
2860 2860
2861 2861 if os.path.exists(newdir):
2862 2862 raise util.Abort(_('non-queue directory "%s" already exists') %
2863 2863 newdir)
2864 2864
2865 2865 fh = repo.opener('patches.queues.new', 'w')
2866 2866 for queue in existing:
2867 2867 if queue == current:
2868 2868 fh.write('%s\n' % (name,))
2869 2869 if os.path.exists(olddir):
2870 2870 util.rename(olddir, newdir)
2871 2871 else:
2872 2872 fh.write('%s\n' % (queue,))
2873 2873 fh.close()
2874 2874 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2875 2875 _setactivenocheck(name)
2876 2876 elif opts.get('delete'):
2877 2877 _delete(name)
2878 2878 elif opts.get('purge'):
2879 2879 if name in existing:
2880 2880 _delete(name)
2881 2881 qdir = _queuedir(name)
2882 2882 if os.path.exists(qdir):
2883 2883 shutil.rmtree(qdir)
2884 2884 else:
2885 2885 if name not in existing:
2886 2886 raise util.Abort(_('use --create to create a new queue'))
2887 2887 _setactive(name)
2888 2888
2889 2889 def reposetup(ui, repo):
2890 2890 class mqrepo(repo.__class__):
2891 2891 @util.propertycache
2892 2892 def mq(self):
2893 2893 return queue(self.ui, self.join(""))
2894 2894
2895 2895 def abort_if_wdir_patched(self, errmsg, force=False):
2896 2896 if self.mq.applied and not force:
2897 2897 parent = self.dirstate.parents()[0]
2898 2898 if parent in [s.node for s in self.mq.applied]:
2899 2899 raise util.Abort(errmsg)
2900 2900
2901 2901 def commit(self, text="", user=None, date=None, match=None,
2902 2902 force=False, editor=False, extra={}):
2903 2903 self.abort_if_wdir_patched(
2904 2904 _('cannot commit over an applied mq patch'),
2905 2905 force)
2906 2906
2907 2907 return super(mqrepo, self).commit(text, user, date, match, force,
2908 2908 editor, extra)
2909 2909
2910 2910 def push(self, remote, force=False, revs=None, newbranch=False):
2911 2911 if self.mq.applied and not force:
2912 2912 haspatches = True
2913 2913 if revs:
2914 2914 # Assume applied patches have no non-patch descendants
2915 2915 # and are not on remote already. If they appear in the
2916 2916 # set of resolved 'revs', bail out.
2917 2917 applied = set(e.node for e in self.mq.applied)
2918 2918 haspatches = bool([n for n in revs if n in applied])
2919 2919 if haspatches:
2920 2920 raise util.Abort(_('source has mq patches applied'))
2921 2921 return super(mqrepo, self).push(remote, force, revs, newbranch)
2922 2922
2923 2923 def _findtags(self):
2924 2924 '''augment tags from base class with patch tags'''
2925 2925 result = super(mqrepo, self)._findtags()
2926 2926
2927 2927 q = self.mq
2928 2928 if not q.applied:
2929 2929 return result
2930 2930
2931 2931 mqtags = [(patch.node, patch.name) for patch in q.applied]
2932 2932
2933 2933 if mqtags[-1][0] not in self.changelog.nodemap:
2934 2934 self.ui.warn(_('mq status file refers to unknown node %s\n')
2935 2935 % short(mqtags[-1][0]))
2936 2936 return result
2937 2937
2938 2938 mqtags.append((mqtags[-1][0], 'qtip'))
2939 2939 mqtags.append((mqtags[0][0], 'qbase'))
2940 2940 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2941 2941 tags = result[0]
2942 2942 for patch in mqtags:
2943 2943 if patch[1] in tags:
2944 2944 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2945 2945 % patch[1])
2946 2946 else:
2947 2947 tags[patch[1]] = patch[0]
2948 2948
2949 2949 return result
2950 2950
2951 2951 def _branchtags(self, partial, lrev):
2952 2952 q = self.mq
2953 2953 if not q.applied:
2954 2954 return super(mqrepo, self)._branchtags(partial, lrev)
2955 2955
2956 2956 cl = self.changelog
2957 2957 qbasenode = q.applied[0].node
2958 2958 if qbasenode not in cl.nodemap:
2959 2959 self.ui.warn(_('mq status file refers to unknown node %s\n')
2960 2960 % short(qbasenode))
2961 2961 return super(mqrepo, self)._branchtags(partial, lrev)
2962 2962
2963 2963 qbase = cl.rev(qbasenode)
2964 2964 start = lrev + 1
2965 2965 if start < qbase:
2966 2966 # update the cache (excluding the patches) and save it
2967 2967 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
2968 2968 self._updatebranchcache(partial, ctxgen)
2969 2969 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2970 2970 start = qbase
2971 2971 # if start = qbase, the cache is as updated as it should be.
2972 2972 # if start > qbase, the cache includes (part of) the patches.
2973 2973 # we might as well use it, but we won't save it.
2974 2974
2975 2975 # update the cache up to the tip
2976 2976 ctxgen = (self[r] for r in xrange(start, len(cl)))
2977 2977 self._updatebranchcache(partial, ctxgen)
2978 2978
2979 2979 return partial
2980 2980
2981 2981 if repo.local():
2982 2982 repo.__class__ = mqrepo
2983 2983
2984 2984 def mqimport(orig, ui, repo, *args, **kwargs):
2985 2985 if (hasattr(repo, 'abort_if_wdir_patched')
2986 2986 and not kwargs.get('no_commit', False)):
2987 2987 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2988 2988 kwargs.get('force'))
2989 2989 return orig(ui, repo, *args, **kwargs)
2990 2990
2991 2991 def mqinit(orig, ui, *args, **kwargs):
2992 2992 mq = kwargs.pop('mq', None)
2993 2993
2994 2994 if not mq:
2995 2995 return orig(ui, *args, **kwargs)
2996 2996
2997 2997 if args:
2998 2998 repopath = args[0]
2999 2999 if not hg.islocal(repopath):
3000 3000 raise util.Abort(_('only a local queue repository '
3001 3001 'may be initialized'))
3002 3002 else:
3003 3003 repopath = cmdutil.findrepo(os.getcwd())
3004 3004 if not repopath:
3005 3005 raise util.Abort(_('there is no Mercurial repository here '
3006 3006 '(.hg not found)'))
3007 3007 repo = hg.repository(ui, repopath)
3008 3008 return qinit(ui, repo, True)
3009 3009
3010 3010 def mqcommand(orig, ui, repo, *args, **kwargs):
3011 3011 """Add --mq option to operate on patch repository instead of main"""
3012 3012
3013 3013 # some commands do not like getting unknown options
3014 3014 mq = kwargs.pop('mq', None)
3015 3015
3016 3016 if not mq:
3017 3017 return orig(ui, repo, *args, **kwargs)
3018 3018
3019 3019 q = repo.mq
3020 3020 r = q.qrepo()
3021 3021 if not r:
3022 3022 raise util.Abort(_('no queue repository'))
3023 3023 return orig(r.ui, r, *args, **kwargs)
3024 3024
3025 3025 def summary(orig, ui, repo, *args, **kwargs):
3026 3026 r = orig(ui, repo, *args, **kwargs)
3027 3027 q = repo.mq
3028 3028 m = []
3029 3029 a, u = len(q.applied), len(q.unapplied(repo))
3030 3030 if a:
3031 3031 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3032 3032 if u:
3033 3033 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3034 3034 if m:
3035 3035 ui.write("mq: %s\n" % ', '.join(m))
3036 3036 else:
3037 3037 ui.note(_("mq: (empty queue)\n"))
3038 3038 return r
3039 3039
3040 3040 def uisetup(ui):
3041 3041 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3042 3042
3043 3043 extensions.wrapcommand(commands.table, 'import', mqimport)
3044 3044 extensions.wrapcommand(commands.table, 'summary', summary)
3045 3045
3046 3046 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3047 3047 entry[1].extend(mqopt)
3048 3048
3049 3049 nowrap = set(commands.norepo.split(" ") + ['qrecord'])
3050 3050
3051 3051 def dotable(cmdtable):
3052 3052 for cmd in cmdtable.keys():
3053 3053 cmd = cmdutil.parsealiases(cmd)[0]
3054 3054 if cmd in nowrap:
3055 3055 continue
3056 3056 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3057 3057 entry[1].extend(mqopt)
3058 3058
3059 3059 dotable(commands.table)
3060 3060
3061 3061 for extname, extmodule in extensions.extensions():
3062 3062 if extmodule.__file__ != __file__:
3063 3063 dotable(getattr(extmodule, 'cmdtable', {}))
3064 3064
3065 3065 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
3066 3066
3067 3067 cmdtable = {
3068 3068 "qapplied":
3069 3069 (applied,
3070 3070 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
3071 3071 _('hg qapplied [-1] [-s] [PATCH]')),
3072 3072 "qclone":
3073 3073 (clone,
3074 3074 [('', 'pull', None, _('use pull protocol to copy metadata')),
3075 3075 ('U', 'noupdate', None, _('do not update the new working directories')),
3076 3076 ('', 'uncompressed', None,
3077 3077 _('use uncompressed transfer (fast over LAN)')),
3078 3078 ('p', 'patches', '',
3079 3079 _('location of source patch repository'), _('REPO')),
3080 3080 ] + commands.remoteopts,
3081 3081 _('hg qclone [OPTION]... SOURCE [DEST]')),
3082 3082 "qcommit|qci":
3083 3083 (commit,
3084 3084 commands.table["^commit|ci"][1],
3085 3085 _('hg qcommit [OPTION]... [FILE]...')),
3086 3086 "^qdiff":
3087 3087 (diff,
3088 3088 commands.diffopts + commands.diffopts2 + commands.walkopts,
3089 3089 _('hg qdiff [OPTION]... [FILE]...')),
3090 3090 "qdelete|qremove|qrm":
3091 3091 (delete,
3092 3092 [('k', 'keep', None, _('keep patch file')),
3093 3093 ('r', 'rev', [],
3094 3094 _('stop managing a revision (DEPRECATED)'), _('REV'))],
3095 3095 _('hg qdelete [-k] [PATCH]...')),
3096 3096 'qfold':
3097 3097 (fold,
3098 3098 [('e', 'edit', None, _('edit patch header')),
3099 3099 ('k', 'keep', None, _('keep folded patch files')),
3100 3100 ] + commands.commitopts,
3101 3101 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
3102 3102 'qgoto':
3103 3103 (goto,
3104 3104 [('f', 'force', None, _('overwrite any local changes'))],
3105 3105 _('hg qgoto [OPTION]... PATCH')),
3106 3106 'qguard':
3107 3107 (guard,
3108 3108 [('l', 'list', None, _('list all patches and guards')),
3109 3109 ('n', 'none', None, _('drop all guards'))],
3110 3110 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
3111 3111 'qheader': (header, [], _('hg qheader [PATCH]')),
3112 3112 "qimport":
3113 3113 (qimport,
3114 3114 [('e', 'existing', None, _('import file in patch directory')),
3115 3115 ('n', 'name', '',
3116 3116 _('name of patch file'), _('NAME')),
3117 3117 ('f', 'force', None, _('overwrite existing files')),
3118 3118 ('r', 'rev', [],
3119 3119 _('place existing revisions under mq control'), _('REV')),
3120 3120 ('g', 'git', None, _('use git extended diff format')),
3121 3121 ('P', 'push', None, _('qpush after importing'))],
3122 3122 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
3123 3123 "^qinit":
3124 3124 (init,
3125 3125 [('c', 'create-repo', None, _('create queue repository'))],
3126 3126 _('hg qinit [-c]')),
3127 3127 "^qnew":
3128 3128 (new,
3129 3129 [('e', 'edit', None, _('edit commit message')),
3130 3130 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
3131 3131 ('g', 'git', None, _('use git extended diff format')),
3132 3132 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
3133 3133 ('u', 'user', '',
3134 3134 _('add "From: <USER>" to patch'), _('USER')),
3135 3135 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
3136 3136 ('d', 'date', '',
3137 3137 _('add "Date: <DATE>" to patch'), _('DATE'))
3138 3138 ] + commands.walkopts + commands.commitopts,
3139 3139 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')),
3140 3140 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
3141 3141 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
3142 3142 "^qpop":
3143 3143 (pop,
3144 3144 [('a', 'all', None, _('pop all patches')),
3145 3145 ('n', 'name', '',
3146 3146 _('queue name to pop (DEPRECATED)'), _('NAME')),
3147 3147 ('f', 'force', None, _('forget any local changes to patched files'))],
3148 3148 _('hg qpop [-a] [-f] [PATCH | INDEX]')),
3149 3149 "^qpush":
3150 3150 (push,
3151 3151 [('f', 'force', None, _('apply on top of local changes')),
3152 3152 ('e', 'exact', None, _('apply the target patch to its recorded parent')),
3153 3153 ('l', 'list', None, _('list patch name in commit text')),
3154 3154 ('a', 'all', None, _('apply all patches')),
3155 3155 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
3156 3156 ('n', 'name', '',
3157 3157 _('merge queue name (DEPRECATED)'), _('NAME')),
3158 3158 ('', 'move', None, _('reorder patch series and apply only the patch'))],
3159 3159 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]')),
3160 3160 "^qrefresh":
3161 3161 (refresh,
3162 3162 [('e', 'edit', None, _('edit commit message')),
3163 3163 ('g', 'git', None, _('use git extended diff format')),
3164 3164 ('s', 'short', None,
3165 3165 _('refresh only files already in the patch and specified files')),
3166 3166 ('U', 'currentuser', None,
3167 3167 _('add/update author field in patch with current user')),
3168 3168 ('u', 'user', '',
3169 3169 _('add/update author field in patch with given user'), _('USER')),
3170 3170 ('D', 'currentdate', None,
3171 3171 _('add/update date field in patch with current date')),
3172 3172 ('d', 'date', '',
3173 3173 _('add/update date field in patch with given date'), _('DATE'))
3174 3174 ] + commands.walkopts + commands.commitopts,
3175 3175 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
3176 3176 'qrename|qmv':
3177 3177 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
3178 3178 "qrestore":
3179 3179 (restore,
3180 3180 [('d', 'delete', None, _('delete save entry')),
3181 3181 ('u', 'update', None, _('update queue working directory'))],
3182 3182 _('hg qrestore [-d] [-u] REV')),
3183 3183 "qsave":
3184 3184 (save,
3185 3185 [('c', 'copy', None, _('copy patch directory')),
3186 3186 ('n', 'name', '',
3187 3187 _('copy directory name'), _('NAME')),
3188 3188 ('e', 'empty', None, _('clear queue status file')),
3189 3189 ('f', 'force', None, _('force copy'))] + commands.commitopts,
3190 3190 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
3191 3191 "qselect":
3192 3192 (select,
3193 3193 [('n', 'none', None, _('disable all guards')),
3194 3194 ('s', 'series', None, _('list all guards in series file')),
3195 3195 ('', 'pop', None, _('pop to before first guarded applied patch')),
3196 3196 ('', 'reapply', None, _('pop, then reapply patches'))],
3197 3197 _('hg qselect [OPTION]... [GUARD]...')),
3198 3198 "qseries":
3199 3199 (series,
3200 3200 [('m', 'missing', None, _('print patches not in series')),
3201 3201 ] + seriesopts,
3202 3202 _('hg qseries [-ms]')),
3203 3203 "strip":
3204 3204 (strip,
3205 3205 [('f', 'force', None, _('force removal of changesets even if the '
3206 3206 'working directory has uncommitted changes')),
3207 3207 ('b', 'backup', None, _('bundle only changesets with local revision'
3208 3208 ' number greater than REV which are not'
3209 3209 ' descendants of REV (DEPRECATED)')),
3210 3210 ('n', 'no-backup', None, _('no backups')),
3211 3211 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
3212 3212 ('k', 'keep', None, _("do not modify working copy during strip"))],
3213 3213 _('hg strip [-k] [-f] [-n] REV...')),
3214 3214 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
3215 3215 "qunapplied":
3216 3216 (unapplied,
3217 3217 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
3218 3218 _('hg qunapplied [-1] [-s] [PATCH]')),
3219 3219 "qfinish":
3220 3220 (finish,
3221 3221 [('a', 'applied', None, _('finish all applied changesets'))],
3222 3222 _('hg qfinish [-a] [REV]...')),
3223 3223 'qqueue':
3224 3224 (qqueue,
3225 3225 [
3226 3226 ('l', 'list', False, _('list all available queues')),
3227 3227 ('c', 'create', False, _('create new queue')),
3228 3228 ('', 'rename', False, _('rename active queue')),
3229 3229 ('', 'delete', False, _('delete reference to queue')),
3230 3230 ('', 'purge', False, _('delete queue, and remove patch dir')),
3231 3231 ],
3232 3232 _('[OPTION] [QUEUE]')),
3233 3233 }
3234 3234
3235 3235 colortable = {'qguard.negative': 'red',
3236 3236 'qguard.positive': 'yellow',
3237 3237 'qguard.unguarded': 'green',
3238 3238 'qseries.applied': 'blue bold underline',
3239 3239 'qseries.guarded': 'black bold',
3240 3240 'qseries.missing': 'red bold',
3241 3241 '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
General Comments 0
You need to be logged in to leave comments. Login now