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