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