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