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