##// END OF EJS Templates
mq: properly take the wlock during the full qfold operation...
marmoute -
r50905:f93bccad default
parent child Browse files
Show More
@@ -1,4304 +1,4304 b''
1 # mq.py - patch queues for mercurial
1 # mq.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''manage a stack of patches
8 '''manage a stack of patches
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use :hg:`help COMMAND` for more details)::
17 Common tasks (use :hg:`help COMMAND` for more details)::
18
18
19 create new patch qnew
19 create new patch qnew
20 import existing patch qimport
20 import existing patch qimport
21
21
22 print patch series qseries
22 print patch series qseries
23 print applied patches qapplied
23 print applied patches qapplied
24
24
25 add known patch to applied stack qpush
25 add known patch to applied stack qpush
26 remove patch from applied stack qpop
26 remove patch from applied stack qpop
27 refresh contents of top applied patch qrefresh
27 refresh contents of top applied patch qrefresh
28
28
29 By default, mq will automatically use git patches when required to
29 By default, mq will automatically use git patches when required to
30 avoid losing file mode changes, copy records, binary files or empty
30 avoid losing file mode changes, copy records, binary files or empty
31 files creations or deletions. This behavior can be configured with::
31 files creations or deletions. This behavior can be configured with::
32
32
33 [mq]
33 [mq]
34 git = auto/keep/yes/no
34 git = auto/keep/yes/no
35
35
36 If set to 'keep', mq will obey the [diff] section configuration while
36 If set to 'keep', mq will obey the [diff] section configuration while
37 preserving existing git patches upon qrefresh. If set to 'yes' or
37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 'no', mq will override the [diff] section and always generate git or
38 'no', mq will override the [diff] section and always generate git or
39 regular patches, possibly losing data in the second case.
39 regular patches, possibly losing data in the second case.
40
40
41 It may be desirable for mq changesets to be kept in the secret phase (see
41 It may be desirable for mq changesets to be kept in the secret phase (see
42 :hg:`help phases`), which can be enabled with the following setting::
42 :hg:`help phases`), which can be enabled with the following setting::
43
43
44 [mq]
44 [mq]
45 secret = True
45 secret = True
46
46
47 You will by default be managing a patch queue named "patches". You can
47 You will by default be managing a patch queue named "patches". You can
48 create other, independent patch queues with the :hg:`qqueue` command.
48 create other, independent patch queues with the :hg:`qqueue` command.
49
49
50 If the working directory contains uncommitted files, qpush, qpop and
50 If the working directory contains uncommitted files, qpush, qpop and
51 qgoto abort immediately. If -f/--force is used, the changes are
51 qgoto abort immediately. If -f/--force is used, the changes are
52 discarded. Setting::
52 discarded. Setting::
53
53
54 [mq]
54 [mq]
55 keepchanges = True
55 keepchanges = True
56
56
57 make them behave as if --keep-changes were passed, and non-conflicting
57 make them behave as if --keep-changes were passed, and non-conflicting
58 local changes will be tolerated and preserved. If incompatible options
58 local changes will be tolerated and preserved. If incompatible options
59 such as -f/--force or --exact are passed, this setting is ignored.
59 such as -f/--force or --exact are passed, this setting is ignored.
60
60
61 This extension used to provide a strip command. This command now lives
61 This extension used to provide a strip command. This command now lives
62 in the strip extension.
62 in the strip extension.
63 '''
63 '''
64
64
65
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.changing_parents(repo):
1085 with repo.dirstate.changing_parents(repo):
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
1181 if rev < firstrev:
1180 if rev < firstrev:
1182 raise error.Abort(_(b'revision %d is not managed') % rev)
1181 raise error.Abort(_(b'revision %d is not managed') % rev)
1183
1182
1184 ctx = repo[rev]
1183 ctx = repo[rev]
1185 base = self.applied[i].node
1184 base = self.applied[i].node
1186 if ctx.node() != base:
1185 if ctx.node() != base:
1187 msg = _(b'cannot delete revision %d above applied patches')
1186 msg = _(b'cannot delete revision %d above applied patches')
1188 raise error.Abort(msg % rev)
1187 raise error.Abort(msg % rev)
1189
1188
1190 patch = self.applied[i].name
1189 patch = self.applied[i].name
1191 for fmt in (b'[mq]: %s', b'imported patch %s'):
1190 for fmt in (b'[mq]: %s', b'imported patch %s'):
1192 if ctx.description() == fmt % patch:
1191 if ctx.description() == fmt % patch:
1193 msg = _(b'patch %s finalized without changeset message\n')
1192 msg = _(b'patch %s finalized without changeset message\n')
1194 repo.ui.status(msg % patch)
1193 repo.ui.status(msg % patch)
1195 break
1194 break
1196
1195
1197 patches.append(patch)
1196 patches.append(patch)
1198 return patches
1197 return patches
1199
1198
1200 def finish(self, repo, revs):
1199 def finish(self, repo, revs):
1201 # Manually trigger phase computation to ensure phasedefaults is
1200 # Manually trigger phase computation to ensure phasedefaults is
1202 # executed before we remove the patches.
1201 # executed before we remove the patches.
1203 repo._phasecache
1202 repo._phasecache
1204 patches = self._revpatches(repo, sorted(revs))
1203 patches = self._revpatches(repo, sorted(revs))
1205 qfinished = self._cleanup(patches, len(patches))
1204 qfinished = self._cleanup(patches, len(patches))
1206 if qfinished and repo.ui.configbool(b'mq', b'secret'):
1205 if qfinished and repo.ui.configbool(b'mq', b'secret'):
1207 # only use this logic when the secret option is added
1206 # only use this logic when the secret option is added
1208 oldqbase = repo[qfinished[0]]
1207 oldqbase = repo[qfinished[0]]
1209 tphase = phases.newcommitphase(repo.ui)
1208 tphase = phases.newcommitphase(repo.ui)
1210 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
1209 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
1211 with repo.transaction(b'qfinish') as tr:
1210 with repo.transaction(b'qfinish') as tr:
1212 phases.advanceboundary(repo, tr, tphase, qfinished)
1211 phases.advanceboundary(repo, tr, tphase, qfinished)
1213
1212
1214 def delete(self, repo, patches, opts):
1213 def delete(self, repo, patches, opts):
1215 if not patches and not opts.get(b'rev'):
1214 if not patches and not opts.get(b'rev'):
1216 raise error.Abort(
1215 raise error.Abort(
1217 _(b'qdelete requires at least one revision or patch name')
1216 _(b'qdelete requires at least one revision or patch name')
1218 )
1217 )
1219
1218
1220 realpatches = []
1219 realpatches = []
1221 for patch in patches:
1220 for patch in patches:
1222 patch = self.lookup(patch, strict=True)
1221 patch = self.lookup(patch, strict=True)
1223 info = self.isapplied(patch)
1222 info = self.isapplied(patch)
1224 if info:
1223 if info:
1225 raise error.Abort(_(b"cannot delete applied patch %s") % patch)
1224 raise error.Abort(_(b"cannot delete applied patch %s") % patch)
1226 if patch not in self.series:
1225 if patch not in self.series:
1227 raise error.Abort(_(b"patch %s not in series file") % patch)
1226 raise error.Abort(_(b"patch %s not in series file") % patch)
1228 if patch not in realpatches:
1227 if patch not in realpatches:
1229 realpatches.append(patch)
1228 realpatches.append(patch)
1230
1229
1231 numrevs = 0
1230 numrevs = 0
1232 if opts.get(b'rev'):
1231 if opts.get(b'rev'):
1233 if not self.applied:
1232 if not self.applied:
1234 raise error.Abort(_(b'no patches applied'))
1233 raise error.Abort(_(b'no patches applied'))
1235 revs = logcmdutil.revrange(repo, opts.get(b'rev'))
1234 revs = logcmdutil.revrange(repo, opts.get(b'rev'))
1236 revs.sort()
1235 revs.sort()
1237 revpatches = self._revpatches(repo, revs)
1236 revpatches = self._revpatches(repo, revs)
1238 realpatches += revpatches
1237 realpatches += revpatches
1239 numrevs = len(revpatches)
1238 numrevs = len(revpatches)
1240
1239
1241 self._cleanup(realpatches, numrevs, opts.get(b'keep'))
1240 self._cleanup(realpatches, numrevs, opts.get(b'keep'))
1242
1241
1243 def checktoppatch(self, repo):
1242 def checktoppatch(self, repo):
1244 '''check that working directory is at qtip'''
1243 '''check that working directory is at qtip'''
1245 if self.applied:
1244 if self.applied:
1246 top = self.applied[-1].node
1245 top = self.applied[-1].node
1247 patch = self.applied[-1].name
1246 patch = self.applied[-1].name
1248 if repo.dirstate.p1() != top:
1247 if repo.dirstate.p1() != top:
1249 raise error.Abort(_(b"working directory revision is not qtip"))
1248 raise error.Abort(_(b"working directory revision is not qtip"))
1250 return top, patch
1249 return top, patch
1251 return None, None
1250 return None, None
1252
1251
1253 def putsubstate2changes(self, substatestate, changes):
1252 def putsubstate2changes(self, substatestate, changes):
1254 if isinstance(changes, list):
1253 if isinstance(changes, list):
1255 mar = changes[:3]
1254 mar = changes[:3]
1256 else:
1255 else:
1257 mar = (changes.modified, changes.added, changes.removed)
1256 mar = (changes.modified, changes.added, changes.removed)
1258 if any((b'.hgsubstate' in files for files in mar)):
1257 if any((b'.hgsubstate' in files for files in mar)):
1259 return # already listed up
1258 return # already listed up
1260 # not yet listed up
1259 # not yet listed up
1261 if substatestate.added or not substatestate.any_tracked:
1260 if substatestate.added or not substatestate.any_tracked:
1262 mar[1].append(b'.hgsubstate')
1261 mar[1].append(b'.hgsubstate')
1263 elif substatestate.removed:
1262 elif substatestate.removed:
1264 mar[2].append(b'.hgsubstate')
1263 mar[2].append(b'.hgsubstate')
1265 else: # modified
1264 else: # modified
1266 mar[0].append(b'.hgsubstate')
1265 mar[0].append(b'.hgsubstate')
1267
1266
1268 def checklocalchanges(self, repo, force=False, refresh=True):
1267 def checklocalchanges(self, repo, force=False, refresh=True):
1269 excsuffix = b''
1268 excsuffix = b''
1270 if refresh:
1269 if refresh:
1271 excsuffix = b', qrefresh first'
1270 excsuffix = b', qrefresh first'
1272 # plain versions for i18n tool to detect them
1271 # plain versions for i18n tool to detect them
1273 _(b"local changes found, qrefresh first")
1272 _(b"local changes found, qrefresh first")
1274 _(b"local changed subrepos found, qrefresh first")
1273 _(b"local changed subrepos found, qrefresh first")
1275
1274
1276 s = repo.status()
1275 s = repo.status()
1277 if not force:
1276 if not force:
1278 cmdutil.checkunfinished(repo)
1277 cmdutil.checkunfinished(repo)
1279 if s.modified or s.added or s.removed or s.deleted:
1278 if s.modified or s.added or s.removed or s.deleted:
1280 _(b"local changes found") # i18n tool detection
1279 _(b"local changes found") # i18n tool detection
1281 raise error.Abort(_(b"local changes found" + excsuffix))
1280 raise error.Abort(_(b"local changes found" + excsuffix))
1282 if checksubstate(repo):
1281 if checksubstate(repo):
1283 _(b"local changed subrepos found") # i18n tool detection
1282 _(b"local changed subrepos found") # i18n tool detection
1284 raise error.Abort(
1283 raise error.Abort(
1285 _(b"local changed subrepos found" + excsuffix)
1284 _(b"local changed subrepos found" + excsuffix)
1286 )
1285 )
1287 else:
1286 else:
1288 cmdutil.checkunfinished(repo, skipmerge=True)
1287 cmdutil.checkunfinished(repo, skipmerge=True)
1289 return s
1288 return s
1290
1289
1291 _reserved = (b'series', b'status', b'guards', b'.', b'..')
1290 _reserved = (b'series', b'status', b'guards', b'.', b'..')
1292
1291
1293 def checkreservedname(self, name):
1292 def checkreservedname(self, name):
1294 if name in self._reserved:
1293 if name in self._reserved:
1295 raise error.Abort(
1294 raise error.Abort(
1296 _(b'"%s" cannot be used as the name of a patch') % name
1295 _(b'"%s" cannot be used as the name of a patch') % name
1297 )
1296 )
1298 if name != name.strip():
1297 if name != name.strip():
1299 # whitespace is stripped by parseseries()
1298 # whitespace is stripped by parseseries()
1300 raise error.Abort(
1299 raise error.Abort(
1301 _(b'patch name cannot begin or end with whitespace')
1300 _(b'patch name cannot begin or end with whitespace')
1302 )
1301 )
1303 for prefix in (b'.hg', b'.mq'):
1302 for prefix in (b'.hg', b'.mq'):
1304 if name.startswith(prefix):
1303 if name.startswith(prefix):
1305 raise error.Abort(
1304 raise error.Abort(
1306 _(b'patch name cannot begin with "%s"') % prefix
1305 _(b'patch name cannot begin with "%s"') % prefix
1307 )
1306 )
1308 for c in (b'#', b':', b'\r', b'\n'):
1307 for c in (b'#', b':', b'\r', b'\n'):
1309 if c in name:
1308 if c in name:
1310 raise error.Abort(
1309 raise error.Abort(
1311 _(b'%r cannot be used in the name of a patch')
1310 _(b'%r cannot be used in the name of a patch')
1312 % pycompat.bytestr(c)
1311 % pycompat.bytestr(c)
1313 )
1312 )
1314
1313
1315 def checkpatchname(self, name, force=False):
1314 def checkpatchname(self, name, force=False):
1316 self.checkreservedname(name)
1315 self.checkreservedname(name)
1317 if not force and os.path.exists(self.join(name)):
1316 if not force and os.path.exists(self.join(name)):
1318 if os.path.isdir(self.join(name)):
1317 if os.path.isdir(self.join(name)):
1319 raise error.Abort(
1318 raise error.Abort(
1320 _(b'"%s" already exists as a directory') % name
1319 _(b'"%s" already exists as a directory') % name
1321 )
1320 )
1322 else:
1321 else:
1323 raise error.Abort(_(b'patch "%s" already exists') % name)
1322 raise error.Abort(_(b'patch "%s" already exists') % name)
1324
1323
1325 def makepatchname(self, title, fallbackname):
1324 def makepatchname(self, title, fallbackname):
1326 """Return a suitable filename for title, adding a suffix to make
1325 """Return a suitable filename for title, adding a suffix to make
1327 it unique in the existing list"""
1326 it unique in the existing list"""
1328 namebase = re.sub(br'[\s\W_]+', b'_', title.lower()).strip(b'_')
1327 namebase = re.sub(br'[\s\W_]+', b'_', title.lower()).strip(b'_')
1329 namebase = namebase[:75] # avoid too long name (issue5117)
1328 namebase = namebase[:75] # avoid too long name (issue5117)
1330 if namebase:
1329 if namebase:
1331 try:
1330 try:
1332 self.checkreservedname(namebase)
1331 self.checkreservedname(namebase)
1333 except error.Abort:
1332 except error.Abort:
1334 namebase = fallbackname
1333 namebase = fallbackname
1335 else:
1334 else:
1336 namebase = fallbackname
1335 namebase = fallbackname
1337 name = namebase
1336 name = namebase
1338 i = 0
1337 i = 0
1339 while True:
1338 while True:
1340 if name not in self.fullseries:
1339 if name not in self.fullseries:
1341 try:
1340 try:
1342 self.checkpatchname(name)
1341 self.checkpatchname(name)
1343 break
1342 break
1344 except error.Abort:
1343 except error.Abort:
1345 pass
1344 pass
1346 i += 1
1345 i += 1
1347 name = b'%s__%d' % (namebase, i)
1346 name = b'%s__%d' % (namebase, i)
1348 return name
1347 return name
1349
1348
1350 def checkkeepchanges(self, keepchanges, force):
1349 def checkkeepchanges(self, keepchanges, force):
1351 if force and keepchanges:
1350 if force and keepchanges:
1352 raise error.Abort(_(b'cannot use both --force and --keep-changes'))
1351 raise error.Abort(_(b'cannot use both --force and --keep-changes'))
1353
1352
1354 def new(self, repo, patchfn, *pats, **opts):
1353 def new(self, repo, patchfn, *pats, **opts):
1355 """options:
1354 """options:
1356 msg: a string or a no-argument function returning a string
1355 msg: a string or a no-argument function returning a string
1357 """
1356 """
1358 opts = pycompat.byteskwargs(opts)
1357 opts = pycompat.byteskwargs(opts)
1359 msg = opts.get(b'msg')
1358 msg = opts.get(b'msg')
1360 edit = opts.get(b'edit')
1359 edit = opts.get(b'edit')
1361 editform = opts.get(b'editform', b'mq.qnew')
1360 editform = opts.get(b'editform', b'mq.qnew')
1362 user = opts.get(b'user')
1361 user = opts.get(b'user')
1363 date = opts.get(b'date')
1362 date = opts.get(b'date')
1364 if date:
1363 if date:
1365 date = dateutil.parsedate(date)
1364 date = dateutil.parsedate(date)
1366 diffopts = self.diffopts({b'git': opts.get(b'git')}, plain=True)
1365 diffopts = self.diffopts({b'git': opts.get(b'git')}, plain=True)
1367 if opts.get(b'checkname', True):
1366 if opts.get(b'checkname', True):
1368 self.checkpatchname(patchfn)
1367 self.checkpatchname(patchfn)
1369 inclsubs = checksubstate(repo)
1368 inclsubs = checksubstate(repo)
1370 if inclsubs:
1369 if inclsubs:
1371 substatestate = repo.dirstate.get_entry(b'.hgsubstate')
1370 substatestate = repo.dirstate.get_entry(b'.hgsubstate')
1372 if opts.get(b'include') or opts.get(b'exclude') or pats:
1371 if opts.get(b'include') or opts.get(b'exclude') or pats:
1373 # detect missing files in pats
1372 # detect missing files in pats
1374 def badfn(f, msg):
1373 def badfn(f, msg):
1375 if f != b'.hgsubstate': # .hgsubstate is auto-created
1374 if f != b'.hgsubstate': # .hgsubstate is auto-created
1376 raise error.Abort(b'%s: %s' % (f, msg))
1375 raise error.Abort(b'%s: %s' % (f, msg))
1377
1376
1378 match = scmutil.match(repo[None], pats, opts, badfn=badfn)
1377 match = scmutil.match(repo[None], pats, opts, badfn=badfn)
1379 changes = repo.status(match=match)
1378 changes = repo.status(match=match)
1380 else:
1379 else:
1381 changes = self.checklocalchanges(repo, force=True)
1380 changes = self.checklocalchanges(repo, force=True)
1382 commitfiles = list(inclsubs)
1381 commitfiles = list(inclsubs)
1383 commitfiles.extend(changes.modified)
1382 commitfiles.extend(changes.modified)
1384 commitfiles.extend(changes.added)
1383 commitfiles.extend(changes.added)
1385 commitfiles.extend(changes.removed)
1384 commitfiles.extend(changes.removed)
1386 match = scmutil.matchfiles(repo, commitfiles)
1385 match = scmutil.matchfiles(repo, commitfiles)
1387 if len(repo[None].parents()) > 1:
1386 if len(repo[None].parents()) > 1:
1388 raise error.Abort(_(b'cannot manage merge changesets'))
1387 raise error.Abort(_(b'cannot manage merge changesets'))
1389 self.checktoppatch(repo)
1388 self.checktoppatch(repo)
1390 insert = self.fullseriesend()
1389 insert = self.fullseriesend()
1391 with repo.wlock():
1390 with repo.wlock():
1392 try:
1391 try:
1393 # if patch file write fails, abort early
1392 # if patch file write fails, abort early
1394 p = self.opener(patchfn, b"w")
1393 p = self.opener(patchfn, b"w")
1395 except IOError as e:
1394 except IOError as e:
1396 raise error.Abort(
1395 raise error.Abort(
1397 _(b'cannot write patch "%s": %s')
1396 _(b'cannot write patch "%s": %s')
1398 % (patchfn, encoding.strtolocal(e.strerror))
1397 % (patchfn, encoding.strtolocal(e.strerror))
1399 )
1398 )
1400 try:
1399 try:
1401 defaultmsg = b"[mq]: %s" % patchfn
1400 defaultmsg = b"[mq]: %s" % patchfn
1402 editor = cmdutil.getcommiteditor(editform=editform)
1401 editor = cmdutil.getcommiteditor(editform=editform)
1403 if edit:
1402 if edit:
1404
1403
1405 def finishdesc(desc):
1404 def finishdesc(desc):
1406 if desc.rstrip():
1405 if desc.rstrip():
1407 return desc
1406 return desc
1408 else:
1407 else:
1409 return defaultmsg
1408 return defaultmsg
1410
1409
1411 # i18n: this message is shown in editor with "HG: " prefix
1410 # i18n: this message is shown in editor with "HG: " prefix
1412 extramsg = _(b'Leave message empty to use default message.')
1411 extramsg = _(b'Leave message empty to use default message.')
1413 editor = cmdutil.getcommiteditor(
1412 editor = cmdutil.getcommiteditor(
1414 finishdesc=finishdesc,
1413 finishdesc=finishdesc,
1415 extramsg=extramsg,
1414 extramsg=extramsg,
1416 editform=editform,
1415 editform=editform,
1417 )
1416 )
1418 commitmsg = msg
1417 commitmsg = msg
1419 else:
1418 else:
1420 commitmsg = msg or defaultmsg
1419 commitmsg = msg or defaultmsg
1421
1420
1422 n = newcommit(
1421 n = newcommit(
1423 repo,
1422 repo,
1424 None,
1423 None,
1425 commitmsg,
1424 commitmsg,
1426 user,
1425 user,
1427 date,
1426 date,
1428 match=match,
1427 match=match,
1429 force=True,
1428 force=True,
1430 editor=editor,
1429 editor=editor,
1431 )
1430 )
1432 if n is None:
1431 if n is None:
1433 raise error.Abort(_(b"repo commit failed"))
1432 raise error.Abort(_(b"repo commit failed"))
1434 try:
1433 try:
1435 self.fullseries[insert:insert] = [patchfn]
1434 self.fullseries[insert:insert] = [patchfn]
1436 self.applied.append(statusentry(n, patchfn))
1435 self.applied.append(statusentry(n, patchfn))
1437 self.parseseries()
1436 self.parseseries()
1438 self.seriesdirty = True
1437 self.seriesdirty = True
1439 self.applieddirty = True
1438 self.applieddirty = True
1440 nctx = repo[n]
1439 nctx = repo[n]
1441 ph = patchheader(self.join(patchfn), self.plainmode)
1440 ph = patchheader(self.join(patchfn), self.plainmode)
1442 if user:
1441 if user:
1443 ph.setuser(user)
1442 ph.setuser(user)
1444 if date:
1443 if date:
1445 ph.setdate(b'%d %d' % date)
1444 ph.setdate(b'%d %d' % date)
1446 ph.setparent(hex(nctx.p1().node()))
1445 ph.setparent(hex(nctx.p1().node()))
1447 msg = nctx.description().strip()
1446 msg = nctx.description().strip()
1448 if msg == defaultmsg.strip():
1447 if msg == defaultmsg.strip():
1449 msg = b''
1448 msg = b''
1450 ph.setmessage(msg)
1449 ph.setmessage(msg)
1451 p.write(bytes(ph))
1450 p.write(bytes(ph))
1452 if commitfiles:
1451 if commitfiles:
1453 parent = self.qparents(repo, n)
1452 parent = self.qparents(repo, n)
1454 if inclsubs:
1453 if inclsubs:
1455 self.putsubstate2changes(substatestate, changes)
1454 self.putsubstate2changes(substatestate, changes)
1456 chunks = patchmod.diff(
1455 chunks = patchmod.diff(
1457 repo,
1456 repo,
1458 node1=parent,
1457 node1=parent,
1459 node2=n,
1458 node2=n,
1460 changes=changes,
1459 changes=changes,
1461 opts=diffopts,
1460 opts=diffopts,
1462 )
1461 )
1463 for chunk in chunks:
1462 for chunk in chunks:
1464 p.write(chunk)
1463 p.write(chunk)
1465 p.close()
1464 p.close()
1466 r = self.qrepo()
1465 r = self.qrepo()
1467 if r:
1466 if r:
1468 r[None].add([patchfn])
1467 r[None].add([patchfn])
1469 except: # re-raises
1468 except: # re-raises
1470 repo.rollback()
1469 repo.rollback()
1471 raise
1470 raise
1472 except Exception:
1471 except Exception:
1473 patchpath = self.join(patchfn)
1472 patchpath = self.join(patchfn)
1474 try:
1473 try:
1475 os.unlink(patchpath)
1474 os.unlink(patchpath)
1476 except OSError:
1475 except OSError:
1477 self.ui.warn(_(b'error unlinking %s\n') % patchpath)
1476 self.ui.warn(_(b'error unlinking %s\n') % patchpath)
1478 raise
1477 raise
1479 self.removeundo(repo)
1478 self.removeundo(repo)
1480
1479
1481 def isapplied(self, patch):
1480 def isapplied(self, patch):
1482 """returns (index, rev, patch)"""
1481 """returns (index, rev, patch)"""
1483 for i, a in enumerate(self.applied):
1482 for i, a in enumerate(self.applied):
1484 if a.name == patch:
1483 if a.name == patch:
1485 return (i, a.node, a.name)
1484 return (i, a.node, a.name)
1486 return None
1485 return None
1487
1486
1488 # if the exact patch name does not exist, we try a few
1487 # if the exact patch name does not exist, we try a few
1489 # variations. If strict is passed, we try only #1
1488 # variations. If strict is passed, we try only #1
1490 #
1489 #
1491 # 1) a number (as string) to indicate an offset in the series file
1490 # 1) a number (as string) to indicate an offset in the series file
1492 # 2) a unique substring of the patch name was given
1491 # 2) a unique substring of the patch name was given
1493 # 3) patchname[-+]num to indicate an offset in the series file
1492 # 3) patchname[-+]num to indicate an offset in the series file
1494 def lookup(self, patch, strict=False):
1493 def lookup(self, patch, strict=False):
1495 def partialname(s):
1494 def partialname(s):
1496 if s in self.series:
1495 if s in self.series:
1497 return s
1496 return s
1498 matches = [x for x in self.series if s in x]
1497 matches = [x for x in self.series if s in x]
1499 if len(matches) > 1:
1498 if len(matches) > 1:
1500 self.ui.warn(_(b'patch name "%s" is ambiguous:\n') % s)
1499 self.ui.warn(_(b'patch name "%s" is ambiguous:\n') % s)
1501 for m in matches:
1500 for m in matches:
1502 self.ui.warn(b' %s\n' % m)
1501 self.ui.warn(b' %s\n' % m)
1503 return None
1502 return None
1504 if matches:
1503 if matches:
1505 return matches[0]
1504 return matches[0]
1506 if self.series and self.applied:
1505 if self.series and self.applied:
1507 if s == b'qtip':
1506 if s == b'qtip':
1508 return self.series[self.seriesend(True) - 1]
1507 return self.series[self.seriesend(True) - 1]
1509 if s == b'qbase':
1508 if s == b'qbase':
1510 return self.series[0]
1509 return self.series[0]
1511 return None
1510 return None
1512
1511
1513 if patch in self.series:
1512 if patch in self.series:
1514 return patch
1513 return patch
1515
1514
1516 if not os.path.isfile(self.join(patch)):
1515 if not os.path.isfile(self.join(patch)):
1517 try:
1516 try:
1518 sno = int(patch)
1517 sno = int(patch)
1519 except (ValueError, OverflowError):
1518 except (ValueError, OverflowError):
1520 pass
1519 pass
1521 else:
1520 else:
1522 if -len(self.series) <= sno < len(self.series):
1521 if -len(self.series) <= sno < len(self.series):
1523 return self.series[sno]
1522 return self.series[sno]
1524
1523
1525 if not strict:
1524 if not strict:
1526 res = partialname(patch)
1525 res = partialname(patch)
1527 if res:
1526 if res:
1528 return res
1527 return res
1529 minus = patch.rfind(b'-')
1528 minus = patch.rfind(b'-')
1530 if minus >= 0:
1529 if minus >= 0:
1531 res = partialname(patch[:minus])
1530 res = partialname(patch[:minus])
1532 if res:
1531 if res:
1533 i = self.series.index(res)
1532 i = self.series.index(res)
1534 try:
1533 try:
1535 off = int(patch[minus + 1 :] or 1)
1534 off = int(patch[minus + 1 :] or 1)
1536 except (ValueError, OverflowError):
1535 except (ValueError, OverflowError):
1537 pass
1536 pass
1538 else:
1537 else:
1539 if i - off >= 0:
1538 if i - off >= 0:
1540 return self.series[i - off]
1539 return self.series[i - off]
1541 plus = patch.rfind(b'+')
1540 plus = patch.rfind(b'+')
1542 if plus >= 0:
1541 if plus >= 0:
1543 res = partialname(patch[:plus])
1542 res = partialname(patch[:plus])
1544 if res:
1543 if res:
1545 i = self.series.index(res)
1544 i = self.series.index(res)
1546 try:
1545 try:
1547 off = int(patch[plus + 1 :] or 1)
1546 off = int(patch[plus + 1 :] or 1)
1548 except (ValueError, OverflowError):
1547 except (ValueError, OverflowError):
1549 pass
1548 pass
1550 else:
1549 else:
1551 if i + off < len(self.series):
1550 if i + off < len(self.series):
1552 return self.series[i + off]
1551 return self.series[i + off]
1553 raise error.Abort(_(b"patch %s not in series") % patch)
1552 raise error.Abort(_(b"patch %s not in series") % patch)
1554
1553
1555 def push(
1554 def push(
1556 self,
1555 self,
1557 repo,
1556 repo,
1558 patch=None,
1557 patch=None,
1559 force=False,
1558 force=False,
1560 list=False,
1559 list=False,
1561 mergeq=None,
1560 mergeq=None,
1562 all=False,
1561 all=False,
1563 move=False,
1562 move=False,
1564 exact=False,
1563 exact=False,
1565 nobackup=False,
1564 nobackup=False,
1566 keepchanges=False,
1565 keepchanges=False,
1567 ):
1566 ):
1568 self.checkkeepchanges(keepchanges, force)
1567 self.checkkeepchanges(keepchanges, force)
1569 diffopts = self.diffopts()
1568 diffopts = self.diffopts()
1570 with repo.wlock():
1569 with repo.wlock():
1571 heads = []
1570 heads = []
1572 for hs in repo.branchmap().iterheads():
1571 for hs in repo.branchmap().iterheads():
1573 heads.extend(hs)
1572 heads.extend(hs)
1574 if not heads:
1573 if not heads:
1575 heads = [repo.nullid]
1574 heads = [repo.nullid]
1576 if repo.dirstate.p1() not in heads and not exact:
1575 if repo.dirstate.p1() not in heads and not exact:
1577 self.ui.status(_(b"(working directory not at a head)\n"))
1576 self.ui.status(_(b"(working directory not at a head)\n"))
1578
1577
1579 if not self.series:
1578 if not self.series:
1580 self.ui.warn(_(b'no patches in series\n'))
1579 self.ui.warn(_(b'no patches in series\n'))
1581 return 0
1580 return 0
1582
1581
1583 # Suppose our series file is: A B C and the current 'top'
1582 # Suppose our series file is: A B C and the current 'top'
1584 # patch is B. qpush C should be performed (moving forward)
1583 # 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
1584 # qpush B is a NOP (no change) qpush A is an error (can't
1586 # go backwards with qpush)
1585 # go backwards with qpush)
1587 if patch:
1586 if patch:
1588 patch = self.lookup(patch)
1587 patch = self.lookup(patch)
1589 info = self.isapplied(patch)
1588 info = self.isapplied(patch)
1590 if info and info[0] >= len(self.applied) - 1:
1589 if info and info[0] >= len(self.applied) - 1:
1591 self.ui.warn(
1590 self.ui.warn(
1592 _(b'qpush: %s is already at the top\n') % patch
1591 _(b'qpush: %s is already at the top\n') % patch
1593 )
1592 )
1594 return 0
1593 return 0
1595
1594
1596 pushable, reason = self.pushable(patch)
1595 pushable, reason = self.pushable(patch)
1597 if pushable:
1596 if pushable:
1598 if self.series.index(patch) < self.seriesend():
1597 if self.series.index(patch) < self.seriesend():
1599 raise error.Abort(
1598 raise error.Abort(
1600 _(b"cannot push to a previous patch: %s") % patch
1599 _(b"cannot push to a previous patch: %s") % patch
1601 )
1600 )
1602 else:
1601 else:
1603 if reason:
1602 if reason:
1604 reason = _(b'guarded by %s') % reason
1603 reason = _(b'guarded by %s') % reason
1605 else:
1604 else:
1606 reason = _(b'no matching guards')
1605 reason = _(b'no matching guards')
1607 self.ui.warn(
1606 self.ui.warn(
1608 _(b"cannot push '%s' - %s\n") % (patch, reason)
1607 _(b"cannot push '%s' - %s\n") % (patch, reason)
1609 )
1608 )
1610 return 1
1609 return 1
1611 elif all:
1610 elif all:
1612 patch = self.series[-1]
1611 patch = self.series[-1]
1613 if self.isapplied(patch):
1612 if self.isapplied(patch):
1614 self.ui.warn(_(b'all patches are currently applied\n'))
1613 self.ui.warn(_(b'all patches are currently applied\n'))
1615 return 0
1614 return 0
1616
1615
1617 # Following the above example, starting at 'top' of B:
1616 # Following the above example, starting at 'top' of B:
1618 # qpush should be performed (pushes C), but a subsequent
1617 # qpush should be performed (pushes C), but a subsequent
1619 # qpush without an argument is an error (nothing to
1618 # qpush without an argument is an error (nothing to
1620 # apply). This allows a loop of "...while hg qpush..." to
1619 # apply). This allows a loop of "...while hg qpush..." to
1621 # work as it detects an error when done
1620 # work as it detects an error when done
1622 start = self.seriesend()
1621 start = self.seriesend()
1623 if start == len(self.series):
1622 if start == len(self.series):
1624 self.ui.warn(_(b'patch series already fully applied\n'))
1623 self.ui.warn(_(b'patch series already fully applied\n'))
1625 return 1
1624 return 1
1626 if not force and not keepchanges:
1625 if not force and not keepchanges:
1627 self.checklocalchanges(repo, refresh=self.applied)
1626 self.checklocalchanges(repo, refresh=self.applied)
1628
1627
1629 if exact:
1628 if exact:
1630 if keepchanges:
1629 if keepchanges:
1631 raise error.Abort(
1630 raise error.Abort(
1632 _(b"cannot use --exact and --keep-changes together")
1631 _(b"cannot use --exact and --keep-changes together")
1633 )
1632 )
1634 if move:
1633 if move:
1635 raise error.Abort(
1634 raise error.Abort(
1636 _(b'cannot use --exact and --move together')
1635 _(b'cannot use --exact and --move together')
1637 )
1636 )
1638 if self.applied:
1637 if self.applied:
1639 raise error.Abort(
1638 raise error.Abort(
1640 _(b'cannot push --exact with applied patches')
1639 _(b'cannot push --exact with applied patches')
1641 )
1640 )
1642 root = self.series[start]
1641 root = self.series[start]
1643 target = patchheader(self.join(root), self.plainmode).parent
1642 target = patchheader(self.join(root), self.plainmode).parent
1644 if not target:
1643 if not target:
1645 raise error.Abort(
1644 raise error.Abort(
1646 _(b"%s does not have a parent recorded") % root
1645 _(b"%s does not have a parent recorded") % root
1647 )
1646 )
1648 if not repo[target] == repo[b'.']:
1647 if not repo[target] == repo[b'.']:
1649 hg.update(repo, target)
1648 hg.update(repo, target)
1650
1649
1651 if move:
1650 if move:
1652 if not patch:
1651 if not patch:
1653 raise error.Abort(_(b"please specify the patch to move"))
1652 raise error.Abort(_(b"please specify the patch to move"))
1654 for fullstart, rpn in enumerate(self.fullseries):
1653 for fullstart, rpn in enumerate(self.fullseries):
1655 # strip markers for patch guards
1654 # strip markers for patch guards
1656 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1655 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1657 break
1656 break
1658 for i, rpn in enumerate(self.fullseries[fullstart:]):
1657 for i, rpn in enumerate(self.fullseries[fullstart:]):
1659 # strip markers for patch guards
1658 # strip markers for patch guards
1660 if self.guard_re.split(rpn, 1)[0] == patch:
1659 if self.guard_re.split(rpn, 1)[0] == patch:
1661 break
1660 break
1662 index = fullstart + i
1661 index = fullstart + i
1663 assert index < len(self.fullseries)
1662 assert index < len(self.fullseries)
1664 fullpatch = self.fullseries[index]
1663 fullpatch = self.fullseries[index]
1665 del self.fullseries[index]
1664 del self.fullseries[index]
1666 self.fullseries.insert(fullstart, fullpatch)
1665 self.fullseries.insert(fullstart, fullpatch)
1667 self.parseseries()
1666 self.parseseries()
1668 self.seriesdirty = True
1667 self.seriesdirty = True
1669
1668
1670 self.applieddirty = True
1669 self.applieddirty = True
1671 if start > 0:
1670 if start > 0:
1672 self.checktoppatch(repo)
1671 self.checktoppatch(repo)
1673 if not patch:
1672 if not patch:
1674 patch = self.series[start]
1673 patch = self.series[start]
1675 end = start + 1
1674 end = start + 1
1676 else:
1675 else:
1677 end = self.series.index(patch, start) + 1
1676 end = self.series.index(patch, start) + 1
1678
1677
1679 tobackup = set()
1678 tobackup = set()
1680 if (not nobackup and force) or keepchanges:
1679 if (not nobackup and force) or keepchanges:
1681 status = self.checklocalchanges(repo, force=True)
1680 status = self.checklocalchanges(repo, force=True)
1682 if keepchanges:
1681 if keepchanges:
1683 tobackup.update(
1682 tobackup.update(
1684 status.modified
1683 status.modified
1685 + status.added
1684 + status.added
1686 + status.removed
1685 + status.removed
1687 + status.deleted
1686 + status.deleted
1688 )
1687 )
1689 else:
1688 else:
1690 tobackup.update(status.modified + status.added)
1689 tobackup.update(status.modified + status.added)
1691
1690
1692 s = self.series[start:end]
1691 s = self.series[start:end]
1693 all_files = set()
1692 all_files = set()
1694 try:
1693 try:
1695 if mergeq:
1694 if mergeq:
1696 ret = self.mergepatch(repo, mergeq, s, diffopts)
1695 ret = self.mergepatch(repo, mergeq, s, diffopts)
1697 else:
1696 else:
1698 ret = self.apply(
1697 ret = self.apply(
1699 repo,
1698 repo,
1700 s,
1699 s,
1701 list,
1700 list,
1702 all_files=all_files,
1701 all_files=all_files,
1703 tobackup=tobackup,
1702 tobackup=tobackup,
1704 keepchanges=keepchanges,
1703 keepchanges=keepchanges,
1705 )
1704 )
1706 except AbortNoCleanup:
1705 except AbortNoCleanup:
1707 raise
1706 raise
1708 except: # re-raises
1707 except: # re-raises
1709 self.ui.warn(_(b'cleaning up working directory...\n'))
1708 self.ui.warn(_(b'cleaning up working directory...\n'))
1710 cmdutil.revert(
1709 cmdutil.revert(
1711 self.ui,
1710 self.ui,
1712 repo,
1711 repo,
1713 repo[b'.'],
1712 repo[b'.'],
1714 no_backup=True,
1713 no_backup=True,
1715 )
1714 )
1716 # only remove unknown files that we know we touched or
1715 # only remove unknown files that we know we touched or
1717 # created while patching
1716 # created while patching
1718 for f in all_files:
1717 for f in all_files:
1719 if f not in repo.dirstate:
1718 if f not in repo.dirstate:
1720 repo.wvfs.unlinkpath(f, ignoremissing=True)
1719 repo.wvfs.unlinkpath(f, ignoremissing=True)
1721 self.ui.warn(_(b'done\n'))
1720 self.ui.warn(_(b'done\n'))
1722 raise
1721 raise
1723
1722
1724 if not self.applied:
1723 if not self.applied:
1725 return ret[0]
1724 return ret[0]
1726 top = self.applied[-1].name
1725 top = self.applied[-1].name
1727 if ret[0] and ret[0] > 1:
1726 if ret[0] and ret[0] > 1:
1728 msg = _(b"errors during apply, please fix and qrefresh %s\n")
1727 msg = _(b"errors during apply, please fix and qrefresh %s\n")
1729 self.ui.write(msg % top)
1728 self.ui.write(msg % top)
1730 else:
1729 else:
1731 self.ui.write(_(b"now at: %s\n") % top)
1730 self.ui.write(_(b"now at: %s\n") % top)
1732 return ret[0]
1731 return ret[0]
1733
1732
1734 def pop(
1733 def pop(
1735 self,
1734 self,
1736 repo,
1735 repo,
1737 patch=None,
1736 patch=None,
1738 force=False,
1737 force=False,
1739 update=True,
1738 update=True,
1740 all=False,
1739 all=False,
1741 nobackup=False,
1740 nobackup=False,
1742 keepchanges=False,
1741 keepchanges=False,
1743 ):
1742 ):
1744 self.checkkeepchanges(keepchanges, force)
1743 self.checkkeepchanges(keepchanges, force)
1745 with repo.wlock():
1744 with repo.wlock():
1746 if patch:
1745 if patch:
1747 # index, rev, patch
1746 # index, rev, patch
1748 info = self.isapplied(patch)
1747 info = self.isapplied(patch)
1749 if not info:
1748 if not info:
1750 patch = self.lookup(patch)
1749 patch = self.lookup(patch)
1751 info = self.isapplied(patch)
1750 info = self.isapplied(patch)
1752 if not info:
1751 if not info:
1753 raise error.Abort(_(b"patch %s is not applied") % patch)
1752 raise error.Abort(_(b"patch %s is not applied") % patch)
1754
1753
1755 if not self.applied:
1754 if not self.applied:
1756 # Allow qpop -a to work repeatedly,
1755 # Allow qpop -a to work repeatedly,
1757 # but not qpop without an argument
1756 # but not qpop without an argument
1758 self.ui.warn(_(b"no patches applied\n"))
1757 self.ui.warn(_(b"no patches applied\n"))
1759 return not all
1758 return not all
1760
1759
1761 if all:
1760 if all:
1762 start = 0
1761 start = 0
1763 elif patch:
1762 elif patch:
1764 start = info[0] + 1
1763 start = info[0] + 1
1765 else:
1764 else:
1766 start = len(self.applied) - 1
1765 start = len(self.applied) - 1
1767
1766
1768 if start >= len(self.applied):
1767 if start >= len(self.applied):
1769 self.ui.warn(_(b"qpop: %s is already at the top\n") % patch)
1768 self.ui.warn(_(b"qpop: %s is already at the top\n") % patch)
1770 return
1769 return
1771
1770
1772 if not update:
1771 if not update:
1773 parents = repo.dirstate.parents()
1772 parents = repo.dirstate.parents()
1774 rr = [x.node for x in self.applied]
1773 rr = [x.node for x in self.applied]
1775 for p in parents:
1774 for p in parents:
1776 if p in rr:
1775 if p in rr:
1777 self.ui.warn(_(b"qpop: forcing dirstate update\n"))
1776 self.ui.warn(_(b"qpop: forcing dirstate update\n"))
1778 update = True
1777 update = True
1779 else:
1778 else:
1780 parents = [p.node() for p in repo[None].parents()]
1779 parents = [p.node() for p in repo[None].parents()]
1781 update = any(
1780 update = any(
1782 entry.node in parents for entry in self.applied[start:]
1781 entry.node in parents for entry in self.applied[start:]
1783 )
1782 )
1784
1783
1785 tobackup = set()
1784 tobackup = set()
1786 if update:
1785 if update:
1787 s = self.checklocalchanges(repo, force=force or keepchanges)
1786 s = self.checklocalchanges(repo, force=force or keepchanges)
1788 if force:
1787 if force:
1789 if not nobackup:
1788 if not nobackup:
1790 tobackup.update(s.modified + s.added)
1789 tobackup.update(s.modified + s.added)
1791 elif keepchanges:
1790 elif keepchanges:
1792 tobackup.update(
1791 tobackup.update(
1793 s.modified + s.added + s.removed + s.deleted
1792 s.modified + s.added + s.removed + s.deleted
1794 )
1793 )
1795
1794
1796 self.applieddirty = True
1795 self.applieddirty = True
1797 end = len(self.applied)
1796 end = len(self.applied)
1798 rev = self.applied[start].node
1797 rev = self.applied[start].node
1799
1798
1800 try:
1799 try:
1801 heads = repo.changelog.heads(rev)
1800 heads = repo.changelog.heads(rev)
1802 except error.LookupError:
1801 except error.LookupError:
1803 node = short(rev)
1802 node = short(rev)
1804 raise error.Abort(_(b'trying to pop unknown node %s') % node)
1803 raise error.Abort(_(b'trying to pop unknown node %s') % node)
1805
1804
1806 if heads != [self.applied[-1].node]:
1805 if heads != [self.applied[-1].node]:
1807 raise error.Abort(
1806 raise error.Abort(
1808 _(
1807 _(
1809 b"popping would remove a revision not "
1808 b"popping would remove a revision not "
1810 b"managed by this patch queue"
1809 b"managed by this patch queue"
1811 )
1810 )
1812 )
1811 )
1813 if not repo[self.applied[-1].node].mutable():
1812 if not repo[self.applied[-1].node].mutable():
1814 raise error.Abort(
1813 raise error.Abort(
1815 _(b"popping would remove a public revision"),
1814 _(b"popping would remove a public revision"),
1816 hint=_(b"see 'hg help phases' for details"),
1815 hint=_(b"see 'hg help phases' for details"),
1817 )
1816 )
1818
1817
1819 # we know there are no local changes, so we can make a simplified
1818 # we know there are no local changes, so we can make a simplified
1820 # form of hg.update.
1819 # form of hg.update.
1821 if update:
1820 if update:
1822 qp = self.qparents(repo, rev)
1821 qp = self.qparents(repo, rev)
1823 ctx = repo[qp]
1822 ctx = repo[qp]
1824 st = repo.status(qp, b'.')
1823 st = repo.status(qp, b'.')
1825 m, a, r, d = st.modified, st.added, st.removed, st.deleted
1824 m, a, r, d = st.modified, st.added, st.removed, st.deleted
1826 if d:
1825 if d:
1827 raise error.Abort(_(b"deletions found between repo revs"))
1826 raise error.Abort(_(b"deletions found between repo revs"))
1828
1827
1829 tobackup = set(a + m + r) & tobackup
1828 tobackup = set(a + m + r) & tobackup
1830 if keepchanges and tobackup:
1829 if keepchanges and tobackup:
1831 raise error.Abort(_(b"local changes found, qrefresh first"))
1830 raise error.Abort(_(b"local changes found, qrefresh first"))
1832 self.backup(repo, tobackup)
1831 self.backup(repo, tobackup)
1833 with repo.dirstate.changing_parents(repo):
1832 with repo.dirstate.changing_parents(repo):
1834 for f in a:
1833 for f in a:
1835 repo.wvfs.unlinkpath(f, ignoremissing=True)
1834 repo.wvfs.unlinkpath(f, ignoremissing=True)
1836 repo.dirstate.update_file(
1835 repo.dirstate.update_file(
1837 f, p1_tracked=False, wc_tracked=False
1836 f, p1_tracked=False, wc_tracked=False
1838 )
1837 )
1839 for f in m + r:
1838 for f in m + r:
1840 fctx = ctx[f]
1839 fctx = ctx[f]
1841 repo.wwrite(f, fctx.data(), fctx.flags())
1840 repo.wwrite(f, fctx.data(), fctx.flags())
1842 repo.dirstate.update_file(
1841 repo.dirstate.update_file(
1843 f, p1_tracked=True, wc_tracked=True
1842 f, p1_tracked=True, wc_tracked=True
1844 )
1843 )
1845 repo.setparents(qp, repo.nullid)
1844 repo.setparents(qp, repo.nullid)
1846 for patch in reversed(self.applied[start:end]):
1845 for patch in reversed(self.applied[start:end]):
1847 self.ui.status(_(b"popping %s\n") % patch.name)
1846 self.ui.status(_(b"popping %s\n") % patch.name)
1848 del self.applied[start:end]
1847 del self.applied[start:end]
1849 strip(self.ui, repo, [rev], update=False, backup=False)
1848 strip(self.ui, repo, [rev], update=False, backup=False)
1850 for s, state in repo[b'.'].substate.items():
1849 for s, state in repo[b'.'].substate.items():
1851 repo[b'.'].sub(s).get(state)
1850 repo[b'.'].sub(s).get(state)
1852 if self.applied:
1851 if self.applied:
1853 self.ui.write(_(b"now at: %s\n") % self.applied[-1].name)
1852 self.ui.write(_(b"now at: %s\n") % self.applied[-1].name)
1854 else:
1853 else:
1855 self.ui.write(_(b"patch queue now empty\n"))
1854 self.ui.write(_(b"patch queue now empty\n"))
1856
1855
1857 def diff(self, repo, pats, opts):
1856 def diff(self, repo, pats, opts):
1858 top, patch = self.checktoppatch(repo)
1857 top, patch = self.checktoppatch(repo)
1859 if not top:
1858 if not top:
1860 self.ui.write(_(b"no patches applied\n"))
1859 self.ui.write(_(b"no patches applied\n"))
1861 return
1860 return
1862 qp = self.qparents(repo, top)
1861 qp = self.qparents(repo, top)
1863 if opts.get(b'reverse'):
1862 if opts.get(b'reverse'):
1864 node1, node2 = None, qp
1863 node1, node2 = None, qp
1865 else:
1864 else:
1866 node1, node2 = qp, None
1865 node1, node2 = qp, None
1867 diffopts = self.diffopts(opts, patch)
1866 diffopts = self.diffopts(opts, patch)
1868 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1867 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1869
1868
1870 def refresh(self, repo, pats=None, **opts):
1869 def refresh(self, repo, pats=None, **opts):
1871 opts = pycompat.byteskwargs(opts)
1870 opts = pycompat.byteskwargs(opts)
1872 if not self.applied:
1871 if not self.applied:
1873 self.ui.write(_(b"no patches applied\n"))
1872 self.ui.write(_(b"no patches applied\n"))
1874 return 1
1873 return 1
1875 msg = opts.get(b'msg', b'').rstrip()
1874 msg = opts.get(b'msg', b'').rstrip()
1876 edit = opts.get(b'edit')
1875 edit = opts.get(b'edit')
1877 editform = opts.get(b'editform', b'mq.qrefresh')
1876 editform = opts.get(b'editform', b'mq.qrefresh')
1878 newuser = opts.get(b'user')
1877 newuser = opts.get(b'user')
1879 newdate = opts.get(b'date')
1878 newdate = opts.get(b'date')
1880 if newdate:
1879 if newdate:
1881 newdate = b'%d %d' % dateutil.parsedate(newdate)
1880 newdate = b'%d %d' % dateutil.parsedate(newdate)
1882 wlock = repo.wlock()
1881 wlock = repo.wlock()
1883
1882
1884 try:
1883 try:
1885 self.checktoppatch(repo)
1884 self.checktoppatch(repo)
1886 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1885 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1887 if repo.changelog.heads(top) != [top]:
1886 if repo.changelog.heads(top) != [top]:
1888 raise error.Abort(
1887 raise error.Abort(
1889 _(b"cannot qrefresh a revision with children")
1888 _(b"cannot qrefresh a revision with children")
1890 )
1889 )
1891 if not repo[top].mutable():
1890 if not repo[top].mutable():
1892 raise error.Abort(
1891 raise error.Abort(
1893 _(b"cannot qrefresh public revision"),
1892 _(b"cannot qrefresh public revision"),
1894 hint=_(b"see 'hg help phases' for details"),
1893 hint=_(b"see 'hg help phases' for details"),
1895 )
1894 )
1896
1895
1897 cparents = repo.changelog.parents(top)
1896 cparents = repo.changelog.parents(top)
1898 patchparent = self.qparents(repo, top)
1897 patchparent = self.qparents(repo, top)
1899
1898
1900 inclsubs = checksubstate(repo, patchparent)
1899 inclsubs = checksubstate(repo, patchparent)
1901 if inclsubs:
1900 if inclsubs:
1902 substatestate = repo.dirstate.get_entry(b'.hgsubstate')
1901 substatestate = repo.dirstate.get_entry(b'.hgsubstate')
1903
1902
1904 ph = patchheader(self.join(patchfn), self.plainmode)
1903 ph = patchheader(self.join(patchfn), self.plainmode)
1905 diffopts = self.diffopts(
1904 diffopts = self.diffopts(
1906 {b'git': opts.get(b'git')}, patchfn, plain=True
1905 {b'git': opts.get(b'git')}, patchfn, plain=True
1907 )
1906 )
1908 if newuser:
1907 if newuser:
1909 ph.setuser(newuser)
1908 ph.setuser(newuser)
1910 if newdate:
1909 if newdate:
1911 ph.setdate(newdate)
1910 ph.setdate(newdate)
1912 ph.setparent(hex(patchparent))
1911 ph.setparent(hex(patchparent))
1913
1912
1914 # only commit new patch when write is complete
1913 # only commit new patch when write is complete
1915 patchf = self.opener(patchfn, b'w', atomictemp=True)
1914 patchf = self.opener(patchfn, b'w', atomictemp=True)
1916
1915
1917 # update the dirstate in place, strip off the qtip commit
1916 # update the dirstate in place, strip off the qtip commit
1918 # and then commit.
1917 # and then commit.
1919 #
1918 #
1920 # this should really read:
1919 # this should really read:
1921 # st = repo.status(top, patchparent)
1920 # st = repo.status(top, patchparent)
1922 # but we do it backwards to take advantage of manifest/changelog
1921 # but we do it backwards to take advantage of manifest/changelog
1923 # caching against the next repo.status call
1922 # caching against the next repo.status call
1924 st = repo.status(patchparent, top)
1923 st = repo.status(patchparent, top)
1925 mm, aa, dd = st.modified, st.added, st.removed
1924 mm, aa, dd = st.modified, st.added, st.removed
1926 ctx = repo[top]
1925 ctx = repo[top]
1927 aaa = aa[:]
1926 aaa = aa[:]
1928 match1 = scmutil.match(repo[None], pats, opts)
1927 match1 = scmutil.match(repo[None], pats, opts)
1929 # in short mode, we only diff the files included in the
1928 # in short mode, we only diff the files included in the
1930 # patch already plus specified files
1929 # patch already plus specified files
1931 if opts.get(b'short'):
1930 if opts.get(b'short'):
1932 # if amending a patch, we start with existing
1931 # if amending a patch, we start with existing
1933 # files plus specified files - unfiltered
1932 # files plus specified files - unfiltered
1934 match = scmutil.matchfiles(repo, mm + aa + dd + match1.files())
1933 match = scmutil.matchfiles(repo, mm + aa + dd + match1.files())
1935 # filter with include/exclude options
1934 # filter with include/exclude options
1936 match1 = scmutil.match(repo[None], opts=opts)
1935 match1 = scmutil.match(repo[None], opts=opts)
1937 else:
1936 else:
1938 match = scmutil.matchall(repo)
1937 match = scmutil.matchall(repo)
1939 stb = repo.status(match=match)
1938 stb = repo.status(match=match)
1940 m, a, r, d = stb.modified, stb.added, stb.removed, stb.deleted
1939 m, a, r, d = stb.modified, stb.added, stb.removed, stb.deleted
1941 mm = set(mm)
1940 mm = set(mm)
1942 aa = set(aa)
1941 aa = set(aa)
1943 dd = set(dd)
1942 dd = set(dd)
1944
1943
1945 # we might end up with files that were added between
1944 # we might end up with files that were added between
1946 # qtip and the dirstate parent, but then changed in the
1945 # qtip and the dirstate parent, but then changed in the
1947 # local dirstate. in this case, we want them to only
1946 # local dirstate. in this case, we want them to only
1948 # show up in the added section
1947 # show up in the added section
1949 for x in m:
1948 for x in m:
1950 if x not in aa:
1949 if x not in aa:
1951 mm.add(x)
1950 mm.add(x)
1952 # we might end up with files added by the local dirstate that
1951 # we might end up with files added by the local dirstate that
1953 # were deleted by the patch. In this case, they should only
1952 # were deleted by the patch. In this case, they should only
1954 # show up in the changed section.
1953 # show up in the changed section.
1955 for x in a:
1954 for x in a:
1956 if x in dd:
1955 if x in dd:
1957 dd.remove(x)
1956 dd.remove(x)
1958 mm.add(x)
1957 mm.add(x)
1959 else:
1958 else:
1960 aa.add(x)
1959 aa.add(x)
1961 # make sure any files deleted in the local dirstate
1960 # make sure any files deleted in the local dirstate
1962 # are not in the add or change column of the patch
1961 # are not in the add or change column of the patch
1963 forget = []
1962 forget = []
1964 for x in d + r:
1963 for x in d + r:
1965 if x in aa:
1964 if x in aa:
1966 aa.remove(x)
1965 aa.remove(x)
1967 forget.append(x)
1966 forget.append(x)
1968 continue
1967 continue
1969 else:
1968 else:
1970 mm.discard(x)
1969 mm.discard(x)
1971 dd.add(x)
1970 dd.add(x)
1972
1971
1973 m = list(mm)
1972 m = list(mm)
1974 r = list(dd)
1973 r = list(dd)
1975 a = list(aa)
1974 a = list(aa)
1976
1975
1977 # create 'match' that includes the files to be recommitted.
1976 # create 'match' that includes the files to be recommitted.
1978 # apply match1 via repo.status to ensure correct case handling.
1977 # apply match1 via repo.status to ensure correct case handling.
1979 st = repo.status(patchparent, match=match1)
1978 st = repo.status(patchparent, match=match1)
1980 cm, ca, cr, cd = st.modified, st.added, st.removed, st.deleted
1979 cm, ca, cr, cd = st.modified, st.added, st.removed, st.deleted
1981 allmatches = set(cm + ca + cr + cd)
1980 allmatches = set(cm + ca + cr + cd)
1982 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1981 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1983
1982
1984 files = set(inclsubs)
1983 files = set(inclsubs)
1985 for x in refreshchanges:
1984 for x in refreshchanges:
1986 files.update(x)
1985 files.update(x)
1987 match = scmutil.matchfiles(repo, files)
1986 match = scmutil.matchfiles(repo, files)
1988
1987
1989 bmlist = repo[top].bookmarks()
1988 bmlist = repo[top].bookmarks()
1990
1989
1991 with repo.dirstate.changing_parents(repo):
1990 with repo.dirstate.changing_parents(repo):
1992 # XXX do we actually need the dirstateguard
1991 # XXX do we actually need the dirstateguard
1993 dsguard = None
1992 dsguard = None
1994 try:
1993 try:
1995 dsguard = dirstateguard.dirstateguard(repo, b'mq.refresh')
1994 dsguard = dirstateguard.dirstateguard(repo, b'mq.refresh')
1996 if diffopts.git or diffopts.upgrade:
1995 if diffopts.git or diffopts.upgrade:
1997 copies = {}
1996 copies = {}
1998 for dst in a:
1997 for dst in a:
1999 src = repo.dirstate.copied(dst)
1998 src = repo.dirstate.copied(dst)
2000 # during qfold, the source file for copies may
1999 # during qfold, the source file for copies may
2001 # be removed. Treat this as a simple add.
2000 # be removed. Treat this as a simple add.
2002 if src is not None and src in repo.dirstate:
2001 if src is not None and src in repo.dirstate:
2003 copies.setdefault(src, []).append(dst)
2002 copies.setdefault(src, []).append(dst)
2004 repo.dirstate.update_file(
2003 repo.dirstate.update_file(
2005 dst, p1_tracked=False, wc_tracked=True
2004 dst, p1_tracked=False, wc_tracked=True
2006 )
2005 )
2007 # remember the copies between patchparent and qtip
2006 # remember the copies between patchparent and qtip
2008 for dst in aaa:
2007 for dst in aaa:
2009 src = ctx[dst].copysource()
2008 src = ctx[dst].copysource()
2010 if src:
2009 if src:
2011 copies.setdefault(src, []).extend(
2010 copies.setdefault(src, []).extend(
2012 copies.get(dst, [])
2011 copies.get(dst, [])
2013 )
2012 )
2014 if dst in a:
2013 if dst in a:
2015 copies[src].append(dst)
2014 copies[src].append(dst)
2016 # we can't copy a file created by the patch itself
2015 # we can't copy a file created by the patch itself
2017 if dst in copies:
2016 if dst in copies:
2018 del copies[dst]
2017 del copies[dst]
2019 for src, dsts in copies.items():
2018 for src, dsts in copies.items():
2020 for dst in dsts:
2019 for dst in dsts:
2021 repo.dirstate.copy(src, dst)
2020 repo.dirstate.copy(src, dst)
2022 else:
2021 else:
2023 for dst in a:
2022 for dst in a:
2024 repo.dirstate.update_file(
2023 repo.dirstate.update_file(
2025 dst, p1_tracked=False, wc_tracked=True
2024 dst, p1_tracked=False, wc_tracked=True
2026 )
2025 )
2027 # Drop useless copy information
2026 # Drop useless copy information
2028 for f in list(repo.dirstate.copies()):
2027 for f in list(repo.dirstate.copies()):
2029 repo.dirstate.copy(None, f)
2028 repo.dirstate.copy(None, f)
2030 for f in r:
2029 for f in r:
2031 repo.dirstate.update_file_p1(f, p1_tracked=True)
2030 repo.dirstate.update_file_p1(f, p1_tracked=True)
2032 # if the patch excludes a modified file, mark that
2031 # if the patch excludes a modified file, mark that
2033 # file with mtime=0 so status can see it.
2032 # file with mtime=0 so status can see it.
2034 mm = []
2033 mm = []
2035 for i in range(len(m) - 1, -1, -1):
2034 for i in range(len(m) - 1, -1, -1):
2036 if not match1(m[i]):
2035 if not match1(m[i]):
2037 mm.append(m[i])
2036 mm.append(m[i])
2038 del m[i]
2037 del m[i]
2039 for f in m:
2038 for f in m:
2040 repo.dirstate.update_file_p1(f, p1_tracked=True)
2039 repo.dirstate.update_file_p1(f, p1_tracked=True)
2041 for f in mm:
2040 for f in mm:
2042 repo.dirstate.update_file_p1(f, p1_tracked=True)
2041 repo.dirstate.update_file_p1(f, p1_tracked=True)
2043 for f in forget:
2042 for f in forget:
2044 repo.dirstate.update_file_p1(f, p1_tracked=False)
2043 repo.dirstate.update_file_p1(f, p1_tracked=False)
2045
2044
2046 user = ph.user or ctx.user()
2045 user = ph.user or ctx.user()
2047
2046
2048 oldphase = repo[top].phase()
2047 oldphase = repo[top].phase()
2049
2048
2050 # assumes strip can roll itself back if interrupted
2049 # assumes strip can roll itself back if interrupted
2051 repo.setparents(*cparents)
2050 repo.setparents(*cparents)
2052 self.applied.pop()
2051 self.applied.pop()
2053 self.applieddirty = True
2052 self.applieddirty = True
2054 strip(self.ui, repo, [top], update=False, backup=False)
2053 strip(self.ui, repo, [top], update=False, backup=False)
2055 dsguard.close()
2054 dsguard.close()
2056 finally:
2055 finally:
2057 release(dsguard)
2056 release(dsguard)
2058
2057
2059 try:
2058 try:
2060 # might be nice to attempt to roll back strip after this
2059 # might be nice to attempt to roll back strip after this
2061
2060
2062 defaultmsg = b"[mq]: %s" % patchfn
2061 defaultmsg = b"[mq]: %s" % patchfn
2063 editor = cmdutil.getcommiteditor(editform=editform)
2062 editor = cmdutil.getcommiteditor(editform=editform)
2064 if edit:
2063 if edit:
2065
2064
2066 def finishdesc(desc):
2065 def finishdesc(desc):
2067 if desc.rstrip():
2066 if desc.rstrip():
2068 ph.setmessage(desc)
2067 ph.setmessage(desc)
2069 return desc
2068 return desc
2070 return defaultmsg
2069 return defaultmsg
2071
2070
2072 # i18n: this message is shown in editor with "HG: " prefix
2071 # i18n: this message is shown in editor with "HG: " prefix
2073 extramsg = _(b'Leave message empty to use default message.')
2072 extramsg = _(b'Leave message empty to use default message.')
2074 editor = cmdutil.getcommiteditor(
2073 editor = cmdutil.getcommiteditor(
2075 finishdesc=finishdesc,
2074 finishdesc=finishdesc,
2076 extramsg=extramsg,
2075 extramsg=extramsg,
2077 editform=editform,
2076 editform=editform,
2078 )
2077 )
2079 message = msg or b"\n".join(ph.message)
2078 message = msg or b"\n".join(ph.message)
2080 elif not msg:
2079 elif not msg:
2081 if not ph.message:
2080 if not ph.message:
2082 message = defaultmsg
2081 message = defaultmsg
2083 else:
2082 else:
2084 message = b"\n".join(ph.message)
2083 message = b"\n".join(ph.message)
2085 else:
2084 else:
2086 message = msg
2085 message = msg
2087 ph.setmessage(msg)
2086 ph.setmessage(msg)
2088
2087
2089 # Ensure we create a new changeset in the same phase than
2088 # Ensure we create a new changeset in the same phase than
2090 # the old one.
2089 # the old one.
2091 lock = tr = None
2090 lock = tr = None
2092 try:
2091 try:
2093 lock = repo.lock()
2092 lock = repo.lock()
2094 tr = repo.transaction(b'mq')
2093 tr = repo.transaction(b'mq')
2095 n = newcommit(
2094 n = newcommit(
2096 repo,
2095 repo,
2097 oldphase,
2096 oldphase,
2098 message,
2097 message,
2099 user,
2098 user,
2100 ph.date,
2099 ph.date,
2101 match=match,
2100 match=match,
2102 force=True,
2101 force=True,
2103 editor=editor,
2102 editor=editor,
2104 )
2103 )
2105 # only write patch after a successful commit
2104 # only write patch after a successful commit
2106 c = [list(x) for x in refreshchanges]
2105 c = [list(x) for x in refreshchanges]
2107 if inclsubs:
2106 if inclsubs:
2108 self.putsubstate2changes(substatestate, c)
2107 self.putsubstate2changes(substatestate, c)
2109 chunks = patchmod.diff(
2108 chunks = patchmod.diff(
2110 repo, patchparent, changes=c, opts=diffopts
2109 repo, patchparent, changes=c, opts=diffopts
2111 )
2110 )
2112 comments = bytes(ph)
2111 comments = bytes(ph)
2113 if comments:
2112 if comments:
2114 patchf.write(comments)
2113 patchf.write(comments)
2115 for chunk in chunks:
2114 for chunk in chunks:
2116 patchf.write(chunk)
2115 patchf.write(chunk)
2117 patchf.close()
2116 patchf.close()
2118
2117
2119 marks = repo._bookmarks
2118 marks = repo._bookmarks
2120 marks.applychanges(repo, tr, [(bm, n) for bm in bmlist])
2119 marks.applychanges(repo, tr, [(bm, n) for bm in bmlist])
2121 tr.close()
2120 tr.close()
2122
2121
2123 self.applied.append(statusentry(n, patchfn))
2122 self.applied.append(statusentry(n, patchfn))
2124 finally:
2123 finally:
2125 lockmod.release(tr, lock)
2124 lockmod.release(tr, lock)
2126 except: # re-raises
2125 except: # re-raises
2127 ctx = repo[cparents[0]]
2126 ctx = repo[cparents[0]]
2128 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2127 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2129 repo.dirstate.write(repo.currenttransaction())
2128 repo.dirstate.write(repo.currenttransaction())
2130 self.savedirty()
2129 self.savedirty()
2131 self.ui.warn(
2130 self.ui.warn(
2132 _(
2131 _(
2133 b'qrefresh interrupted while patch was popped! '
2132 b'qrefresh interrupted while patch was popped! '
2134 b'(revert --all, qpush to recover)\n'
2133 b'(revert --all, qpush to recover)\n'
2135 )
2134 )
2136 )
2135 )
2137 raise
2136 raise
2138 finally:
2137 finally:
2139 wlock.release()
2138 wlock.release()
2140 self.removeundo(repo)
2139 self.removeundo(repo)
2141
2140
2142 def init(self, repo, create=False):
2141 def init(self, repo, create=False):
2143 if not create and os.path.isdir(self.path):
2142 if not create and os.path.isdir(self.path):
2144 raise error.Abort(_(b"patch queue directory already exists"))
2143 raise error.Abort(_(b"patch queue directory already exists"))
2145 try:
2144 try:
2146 os.mkdir(self.path)
2145 os.mkdir(self.path)
2147 except FileExistsError:
2146 except FileExistsError:
2148 if not create:
2147 if not create:
2149 raise
2148 raise
2150 if create:
2149 if create:
2151 return self.qrepo(create=True)
2150 return self.qrepo(create=True)
2152
2151
2153 def unapplied(self, repo, patch=None):
2152 def unapplied(self, repo, patch=None):
2154 if patch and patch not in self.series:
2153 if patch and patch not in self.series:
2155 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)
2156 if not patch:
2155 if not patch:
2157 start = self.seriesend()
2156 start = self.seriesend()
2158 else:
2157 else:
2159 start = self.series.index(patch) + 1
2158 start = self.series.index(patch) + 1
2160 unapplied = []
2159 unapplied = []
2161 for i in range(start, len(self.series)):
2160 for i in range(start, len(self.series)):
2162 pushable, reason = self.pushable(i)
2161 pushable, reason = self.pushable(i)
2163 if pushable:
2162 if pushable:
2164 unapplied.append((i, self.series[i]))
2163 unapplied.append((i, self.series[i]))
2165 self.explainpushable(i)
2164 self.explainpushable(i)
2166 return unapplied
2165 return unapplied
2167
2166
2168 def qseries(
2167 def qseries(
2169 self,
2168 self,
2170 repo,
2169 repo,
2171 missing=None,
2170 missing=None,
2172 start=0,
2171 start=0,
2173 length=None,
2172 length=None,
2174 status=None,
2173 status=None,
2175 summary=False,
2174 summary=False,
2176 ):
2175 ):
2177 def displayname(pfx, patchname, state):
2176 def displayname(pfx, patchname, state):
2178 if pfx:
2177 if pfx:
2179 self.ui.write(pfx)
2178 self.ui.write(pfx)
2180 if summary:
2179 if summary:
2181 ph = patchheader(self.join(patchname), self.plainmode)
2180 ph = patchheader(self.join(patchname), self.plainmode)
2182 if ph.message:
2181 if ph.message:
2183 msg = ph.message[0]
2182 msg = ph.message[0]
2184 else:
2183 else:
2185 msg = b''
2184 msg = b''
2186
2185
2187 if self.ui.formatted():
2186 if self.ui.formatted():
2188 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
2187 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
2189 if width > 0:
2188 if width > 0:
2190 msg = stringutil.ellipsis(msg, width)
2189 msg = stringutil.ellipsis(msg, width)
2191 else:
2190 else:
2192 msg = b''
2191 msg = b''
2193 self.ui.write(patchname, label=b'qseries.' + state)
2192 self.ui.write(patchname, label=b'qseries.' + state)
2194 self.ui.write(b': ')
2193 self.ui.write(b': ')
2195 self.ui.write(msg, label=b'qseries.message.' + state)
2194 self.ui.write(msg, label=b'qseries.message.' + state)
2196 else:
2195 else:
2197 self.ui.write(patchname, label=b'qseries.' + state)
2196 self.ui.write(patchname, label=b'qseries.' + state)
2198 self.ui.write(b'\n')
2197 self.ui.write(b'\n')
2199
2198
2200 applied = {p.name for p in self.applied}
2199 applied = {p.name for p in self.applied}
2201 if length is None:
2200 if length is None:
2202 length = len(self.series) - start
2201 length = len(self.series) - start
2203 if not missing:
2202 if not missing:
2204 if self.ui.verbose:
2203 if self.ui.verbose:
2205 idxwidth = len(b"%d" % (start + length - 1))
2204 idxwidth = len(b"%d" % (start + length - 1))
2206 for i in range(start, start + length):
2205 for i in range(start, start + length):
2207 patch = self.series[i]
2206 patch = self.series[i]
2208 if patch in applied:
2207 if patch in applied:
2209 char, state = b'A', b'applied'
2208 char, state = b'A', b'applied'
2210 elif self.pushable(i)[0]:
2209 elif self.pushable(i)[0]:
2211 char, state = b'U', b'unapplied'
2210 char, state = b'U', b'unapplied'
2212 else:
2211 else:
2213 char, state = b'G', b'guarded'
2212 char, state = b'G', b'guarded'
2214 pfx = b''
2213 pfx = b''
2215 if self.ui.verbose:
2214 if self.ui.verbose:
2216 pfx = b'%*d %s ' % (idxwidth, i, char)
2215 pfx = b'%*d %s ' % (idxwidth, i, char)
2217 elif status and status != char:
2216 elif status and status != char:
2218 continue
2217 continue
2219 displayname(pfx, patch, state)
2218 displayname(pfx, patch, state)
2220 else:
2219 else:
2221 msng_list = []
2220 msng_list = []
2222 for root, dirs, files in os.walk(self.path):
2221 for root, dirs, files in os.walk(self.path):
2223 d = root[len(self.path) + 1 :]
2222 d = root[len(self.path) + 1 :]
2224 for f in files:
2223 for f in files:
2225 fl = os.path.join(d, f)
2224 fl = os.path.join(d, f)
2226 if (
2225 if (
2227 fl not in self.series
2226 fl not in self.series
2228 and fl
2227 and fl
2229 not in (
2228 not in (
2230 self.statuspath,
2229 self.statuspath,
2231 self.seriespath,
2230 self.seriespath,
2232 self.guardspath,
2231 self.guardspath,
2233 )
2232 )
2234 and not fl.startswith(b'.')
2233 and not fl.startswith(b'.')
2235 ):
2234 ):
2236 msng_list.append(fl)
2235 msng_list.append(fl)
2237 for x in sorted(msng_list):
2236 for x in sorted(msng_list):
2238 pfx = self.ui.verbose and b'D ' or b''
2237 pfx = self.ui.verbose and b'D ' or b''
2239 displayname(pfx, x, b'missing')
2238 displayname(pfx, x, b'missing')
2240
2239
2241 def issaveline(self, l):
2240 def issaveline(self, l):
2242 if l.name == b'.hg.patches.save.line':
2241 if l.name == b'.hg.patches.save.line':
2243 return True
2242 return True
2244
2243
2245 def qrepo(self, create=False):
2244 def qrepo(self, create=False):
2246 ui = self.baseui.copy()
2245 ui = self.baseui.copy()
2247 # copy back attributes set by ui.pager()
2246 # copy back attributes set by ui.pager()
2248 if self.ui.pageractive and not ui.pageractive:
2247 if self.ui.pageractive and not ui.pageractive:
2249 ui.pageractive = self.ui.pageractive
2248 ui.pageractive = self.ui.pageractive
2250 # internal config: ui.formatted
2249 # internal config: ui.formatted
2251 ui.setconfig(
2250 ui.setconfig(
2252 b'ui',
2251 b'ui',
2253 b'formatted',
2252 b'formatted',
2254 self.ui.config(b'ui', b'formatted'),
2253 self.ui.config(b'ui', b'formatted'),
2255 b'mqpager',
2254 b'mqpager',
2256 )
2255 )
2257 ui.setconfig(
2256 ui.setconfig(
2258 b'ui',
2257 b'ui',
2259 b'interactive',
2258 b'interactive',
2260 self.ui.config(b'ui', b'interactive'),
2259 self.ui.config(b'ui', b'interactive'),
2261 b'mqpager',
2260 b'mqpager',
2262 )
2261 )
2263 if create or os.path.isdir(self.join(b".hg")):
2262 if create or os.path.isdir(self.join(b".hg")):
2264 return hg.repository(ui, path=self.path, create=create)
2263 return hg.repository(ui, path=self.path, create=create)
2265
2264
2266 def restore(self, repo, rev, delete=None, qupdate=None):
2265 def restore(self, repo, rev, delete=None, qupdate=None):
2267 desc = repo[rev].description().strip()
2266 desc = repo[rev].description().strip()
2268 lines = desc.splitlines()
2267 lines = desc.splitlines()
2269 datastart = None
2268 datastart = None
2270 series = []
2269 series = []
2271 applied = []
2270 applied = []
2272 qpp = None
2271 qpp = None
2273 for i, line in enumerate(lines):
2272 for i, line in enumerate(lines):
2274 if line == b'Patch Data:':
2273 if line == b'Patch Data:':
2275 datastart = i + 1
2274 datastart = i + 1
2276 elif line.startswith(b'Dirstate:'):
2275 elif line.startswith(b'Dirstate:'):
2277 l = line.rstrip()
2276 l = line.rstrip()
2278 l = l[10:].split(b' ')
2277 l = l[10:].split(b' ')
2279 qpp = [bin(x) for x in l]
2278 qpp = [bin(x) for x in l]
2280 elif datastart is not None:
2279 elif datastart is not None:
2281 l = line.rstrip()
2280 l = line.rstrip()
2282 n, name = l.split(b':', 1)
2281 n, name = l.split(b':', 1)
2283 if n:
2282 if n:
2284 applied.append(statusentry(bin(n), name))
2283 applied.append(statusentry(bin(n), name))
2285 else:
2284 else:
2286 series.append(l)
2285 series.append(l)
2287 if datastart is None:
2286 if datastart is None:
2288 self.ui.warn(_(b"no saved patch data found\n"))
2287 self.ui.warn(_(b"no saved patch data found\n"))
2289 return 1
2288 return 1
2290 self.ui.warn(_(b"restoring status: %s\n") % lines[0])
2289 self.ui.warn(_(b"restoring status: %s\n") % lines[0])
2291 self.fullseries = series
2290 self.fullseries = series
2292 self.applied = applied
2291 self.applied = applied
2293 self.parseseries()
2292 self.parseseries()
2294 self.seriesdirty = True
2293 self.seriesdirty = True
2295 self.applieddirty = True
2294 self.applieddirty = True
2296 heads = repo.changelog.heads()
2295 heads = repo.changelog.heads()
2297 if delete:
2296 if delete:
2298 if rev not in heads:
2297 if rev not in heads:
2299 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"))
2300 else:
2299 else:
2301 self.ui.warn(_(b"removing save entry %s\n") % short(rev))
2300 self.ui.warn(_(b"removing save entry %s\n") % short(rev))
2302 pp = repo.dirstate.parents()
2301 pp = repo.dirstate.parents()
2303 if rev in pp:
2302 if rev in pp:
2304 update = True
2303 update = True
2305 else:
2304 else:
2306 update = False
2305 update = False
2307 strip(self.ui, repo, [rev], update=update, backup=False)
2306 strip(self.ui, repo, [rev], update=update, backup=False)
2308 if qpp:
2307 if qpp:
2309 self.ui.warn(
2308 self.ui.warn(
2310 _(b"saved queue repository parents: %s %s\n")
2309 _(b"saved queue repository parents: %s %s\n")
2311 % (short(qpp[0]), short(qpp[1]))
2310 % (short(qpp[0]), short(qpp[1]))
2312 )
2311 )
2313 if qupdate:
2312 if qupdate:
2314 self.ui.status(_(b"updating queue directory\n"))
2313 self.ui.status(_(b"updating queue directory\n"))
2315 r = self.qrepo()
2314 r = self.qrepo()
2316 if not r:
2315 if not r:
2317 self.ui.warn(_(b"unable to load queue repository\n"))
2316 self.ui.warn(_(b"unable to load queue repository\n"))
2318 return 1
2317 return 1
2319 hg.clean(r, qpp[0])
2318 hg.clean(r, qpp[0])
2320
2319
2321 def save(self, repo, msg=None):
2320 def save(self, repo, msg=None):
2322 if not self.applied:
2321 if not self.applied:
2323 self.ui.warn(_(b"save: no patches applied, exiting\n"))
2322 self.ui.warn(_(b"save: no patches applied, exiting\n"))
2324 return 1
2323 return 1
2325 if self.issaveline(self.applied[-1]):
2324 if self.issaveline(self.applied[-1]):
2326 self.ui.warn(_(b"status is already saved\n"))
2325 self.ui.warn(_(b"status is already saved\n"))
2327 return 1
2326 return 1
2328
2327
2329 if not msg:
2328 if not msg:
2330 msg = _(b"hg patches saved state")
2329 msg = _(b"hg patches saved state")
2331 else:
2330 else:
2332 msg = b"hg patches: " + msg.rstrip(b'\r\n')
2331 msg = b"hg patches: " + msg.rstrip(b'\r\n')
2333 r = self.qrepo()
2332 r = self.qrepo()
2334 if r:
2333 if r:
2335 pp = r.dirstate.parents()
2334 pp = r.dirstate.parents()
2336 msg += b"\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
2335 msg += b"\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
2337 msg += b"\n\nPatch Data:\n"
2336 msg += b"\n\nPatch Data:\n"
2338 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)
2339 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)
2340 n = repo.commit(msg, force=True)
2339 n = repo.commit(msg, force=True)
2341 if not n:
2340 if not n:
2342 self.ui.warn(_(b"repo commit failed\n"))
2341 self.ui.warn(_(b"repo commit failed\n"))
2343 return 1
2342 return 1
2344 self.applied.append(statusentry(n, b'.hg.patches.save.line'))
2343 self.applied.append(statusentry(n, b'.hg.patches.save.line'))
2345 self.applieddirty = True
2344 self.applieddirty = True
2346 self.removeundo(repo)
2345 self.removeundo(repo)
2347
2346
2348 def fullseriesend(self):
2347 def fullseriesend(self):
2349 if self.applied:
2348 if self.applied:
2350 p = self.applied[-1].name
2349 p = self.applied[-1].name
2351 end = self.findseries(p)
2350 end = self.findseries(p)
2352 if end is None:
2351 if end is None:
2353 return len(self.fullseries)
2352 return len(self.fullseries)
2354 return end + 1
2353 return end + 1
2355 return 0
2354 return 0
2356
2355
2357 def seriesend(self, all_patches=False):
2356 def seriesend(self, all_patches=False):
2358 """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
2359 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
2360 index of the first patch past the last applied one.
2359 index of the first patch past the last applied one.
2361 """
2360 """
2362 end = 0
2361 end = 0
2363
2362
2364 def nextpatch(start):
2363 def nextpatch(start):
2365 if all_patches or start >= len(self.series):
2364 if all_patches or start >= len(self.series):
2366 return start
2365 return start
2367 for i in range(start, len(self.series)):
2366 for i in range(start, len(self.series)):
2368 p, reason = self.pushable(i)
2367 p, reason = self.pushable(i)
2369 if p:
2368 if p:
2370 return i
2369 return i
2371 self.explainpushable(i)
2370 self.explainpushable(i)
2372 return len(self.series)
2371 return len(self.series)
2373
2372
2374 if self.applied:
2373 if self.applied:
2375 p = self.applied[-1].name
2374 p = self.applied[-1].name
2376 try:
2375 try:
2377 end = self.series.index(p)
2376 end = self.series.index(p)
2378 except ValueError:
2377 except ValueError:
2379 return 0
2378 return 0
2380 return nextpatch(end + 1)
2379 return nextpatch(end + 1)
2381 return nextpatch(end)
2380 return nextpatch(end)
2382
2381
2383 def appliedname(self, index):
2382 def appliedname(self, index):
2384 pname = self.applied[index].name
2383 pname = self.applied[index].name
2385 if not self.ui.verbose:
2384 if not self.ui.verbose:
2386 p = pname
2385 p = pname
2387 else:
2386 else:
2388 p = (b"%d" % self.series.index(pname)) + b" " + pname
2387 p = (b"%d" % self.series.index(pname)) + b" " + pname
2389 return p
2388 return p
2390
2389
2391 def qimport(
2390 def qimport(
2392 self,
2391 self,
2393 repo,
2392 repo,
2394 files,
2393 files,
2395 patchname=None,
2394 patchname=None,
2396 rev=None,
2395 rev=None,
2397 existing=None,
2396 existing=None,
2398 force=None,
2397 force=None,
2399 git=False,
2398 git=False,
2400 ):
2399 ):
2401 def checkseries(patchname):
2400 def checkseries(patchname):
2402 if patchname in self.series:
2401 if patchname in self.series:
2403 raise error.Abort(
2402 raise error.Abort(
2404 _(b'patch %s is already in the series file') % patchname
2403 _(b'patch %s is already in the series file') % patchname
2405 )
2404 )
2406
2405
2407 if rev:
2406 if rev:
2408 if files:
2407 if files:
2409 raise error.Abort(
2408 raise error.Abort(
2410 _(b'option "-r" not valid when importing files')
2409 _(b'option "-r" not valid when importing files')
2411 )
2410 )
2412 rev = logcmdutil.revrange(repo, rev)
2411 rev = logcmdutil.revrange(repo, rev)
2413 rev.sort(reverse=True)
2412 rev.sort(reverse=True)
2414 elif not files:
2413 elif not files:
2415 raise error.Abort(_(b'no files or revisions specified'))
2414 raise error.Abort(_(b'no files or revisions specified'))
2416 if (len(files) > 1 or len(rev) > 1) and patchname:
2415 if (len(files) > 1 or len(rev) > 1) and patchname:
2417 raise error.Abort(
2416 raise error.Abort(
2418 _(b'option "-n" not valid when importing multiple patches')
2417 _(b'option "-n" not valid when importing multiple patches')
2419 )
2418 )
2420 imported = []
2419 imported = []
2421 if rev:
2420 if rev:
2422 # If mq patches are applied, we can only import revisions
2421 # If mq patches are applied, we can only import revisions
2423 # that form a linear path to qbase.
2422 # that form a linear path to qbase.
2424 # Otherwise, they should form a linear path to a head.
2423 # Otherwise, they should form a linear path to a head.
2425 heads = repo.changelog.heads(repo.changelog.node(rev.first()))
2424 heads = repo.changelog.heads(repo.changelog.node(rev.first()))
2426 if len(heads) > 1:
2425 if len(heads) > 1:
2427 raise error.Abort(
2426 raise error.Abort(
2428 _(b'revision %d is the root of more than one branch')
2427 _(b'revision %d is the root of more than one branch')
2429 % rev.last()
2428 % rev.last()
2430 )
2429 )
2431 if self.applied:
2430 if self.applied:
2432 base = repo.changelog.node(rev.first())
2431 base = repo.changelog.node(rev.first())
2433 if base in [n.node for n in self.applied]:
2432 if base in [n.node for n in self.applied]:
2434 raise error.Abort(
2433 raise error.Abort(
2435 _(b'revision %d is already managed') % rev.first()
2434 _(b'revision %d is already managed') % rev.first()
2436 )
2435 )
2437 if heads != [self.applied[-1].node]:
2436 if heads != [self.applied[-1].node]:
2438 raise error.Abort(
2437 raise error.Abort(
2439 _(b'revision %d is not the parent of the queue')
2438 _(b'revision %d is not the parent of the queue')
2440 % rev.first()
2439 % rev.first()
2441 )
2440 )
2442 base = repo.changelog.rev(self.applied[0].node)
2441 base = repo.changelog.rev(self.applied[0].node)
2443 lastparent = repo.changelog.parentrevs(base)[0]
2442 lastparent = repo.changelog.parentrevs(base)[0]
2444 else:
2443 else:
2445 if heads != [repo.changelog.node(rev.first())]:
2444 if heads != [repo.changelog.node(rev.first())]:
2446 raise error.Abort(
2445 raise error.Abort(
2447 _(b'revision %d has unmanaged children') % rev.first()
2446 _(b'revision %d has unmanaged children') % rev.first()
2448 )
2447 )
2449 lastparent = None
2448 lastparent = None
2450
2449
2451 diffopts = self.diffopts({b'git': git})
2450 diffopts = self.diffopts({b'git': git})
2452 with repo.transaction(b'qimport') as tr:
2451 with repo.transaction(b'qimport') as tr:
2453 for r in rev:
2452 for r in rev:
2454 if not repo[r].mutable():
2453 if not repo[r].mutable():
2455 raise error.Abort(
2454 raise error.Abort(
2456 _(b'revision %d is not mutable') % r,
2455 _(b'revision %d is not mutable') % r,
2457 hint=_(b"see 'hg help phases' " b'for details'),
2456 hint=_(b"see 'hg help phases' " b'for details'),
2458 )
2457 )
2459 p1, p2 = repo.changelog.parentrevs(r)
2458 p1, p2 = repo.changelog.parentrevs(r)
2460 n = repo.changelog.node(r)
2459 n = repo.changelog.node(r)
2461 if p2 != nullrev:
2460 if p2 != nullrev:
2462 raise error.Abort(
2461 raise error.Abort(
2463 _(b'cannot import merge revision %d') % r
2462 _(b'cannot import merge revision %d') % r
2464 )
2463 )
2465 if lastparent and lastparent != r:
2464 if lastparent and lastparent != r:
2466 raise error.Abort(
2465 raise error.Abort(
2467 _(b'revision %d is not the parent of %d')
2466 _(b'revision %d is not the parent of %d')
2468 % (r, lastparent)
2467 % (r, lastparent)
2469 )
2468 )
2470 lastparent = p1
2469 lastparent = p1
2471
2470
2472 if not patchname:
2471 if not patchname:
2473 patchname = self.makepatchname(
2472 patchname = self.makepatchname(
2474 repo[r].description().split(b'\n', 1)[0],
2473 repo[r].description().split(b'\n', 1)[0],
2475 b'%d.diff' % r,
2474 b'%d.diff' % r,
2476 )
2475 )
2477 checkseries(patchname)
2476 checkseries(patchname)
2478 self.checkpatchname(patchname, force)
2477 self.checkpatchname(patchname, force)
2479 self.fullseries.insert(0, patchname)
2478 self.fullseries.insert(0, patchname)
2480
2479
2481 with self.opener(patchname, b"w") as fp:
2480 with self.opener(patchname, b"w") as fp:
2482 cmdutil.exportfile(repo, [n], fp, opts=diffopts)
2481 cmdutil.exportfile(repo, [n], fp, opts=diffopts)
2483
2482
2484 se = statusentry(n, patchname)
2483 se = statusentry(n, patchname)
2485 self.applied.insert(0, se)
2484 self.applied.insert(0, se)
2486
2485
2487 self.added.append(patchname)
2486 self.added.append(patchname)
2488 imported.append(patchname)
2487 imported.append(patchname)
2489 patchname = None
2488 patchname = None
2490 if rev and repo.ui.configbool(b'mq', b'secret'):
2489 if rev and repo.ui.configbool(b'mq', b'secret'):
2491 # if we added anything with --rev, move the secret root
2490 # if we added anything with --rev, move the secret root
2492 phases.retractboundary(repo, tr, phases.secret, [n])
2491 phases.retractboundary(repo, tr, phases.secret, [n])
2493 self.parseseries()
2492 self.parseseries()
2494 self.applieddirty = True
2493 self.applieddirty = True
2495 self.seriesdirty = True
2494 self.seriesdirty = True
2496
2495
2497 for i, filename in enumerate(files):
2496 for i, filename in enumerate(files):
2498 if existing:
2497 if existing:
2499 if filename == b'-':
2498 if filename == b'-':
2500 raise error.Abort(
2499 raise error.Abort(
2501 _(b'-e is incompatible with import from -')
2500 _(b'-e is incompatible with import from -')
2502 )
2501 )
2503 filename = normname(filename)
2502 filename = normname(filename)
2504 self.checkreservedname(filename)
2503 self.checkreservedname(filename)
2505 if urlutil.url(filename).islocal():
2504 if urlutil.url(filename).islocal():
2506 originpath = self.join(filename)
2505 originpath = self.join(filename)
2507 if not os.path.isfile(originpath):
2506 if not os.path.isfile(originpath):
2508 raise error.Abort(
2507 raise error.Abort(
2509 _(b"patch %s does not exist") % filename
2508 _(b"patch %s does not exist") % filename
2510 )
2509 )
2511
2510
2512 if patchname:
2511 if patchname:
2513 self.checkpatchname(patchname, force)
2512 self.checkpatchname(patchname, force)
2514
2513
2515 self.ui.write(
2514 self.ui.write(
2516 _(b'renaming %s to %s\n') % (filename, patchname)
2515 _(b'renaming %s to %s\n') % (filename, patchname)
2517 )
2516 )
2518 util.rename(originpath, self.join(patchname))
2517 util.rename(originpath, self.join(patchname))
2519 else:
2518 else:
2520 patchname = filename
2519 patchname = filename
2521
2520
2522 else:
2521 else:
2523 if filename == b'-' and not patchname:
2522 if filename == b'-' and not patchname:
2524 raise error.Abort(
2523 raise error.Abort(
2525 _(b'need --name to import a patch from -')
2524 _(b'need --name to import a patch from -')
2526 )
2525 )
2527 elif not patchname:
2526 elif not patchname:
2528 patchname = normname(
2527 patchname = normname(
2529 os.path.basename(filename.rstrip(b'/'))
2528 os.path.basename(filename.rstrip(b'/'))
2530 )
2529 )
2531 self.checkpatchname(patchname, force)
2530 self.checkpatchname(patchname, force)
2532 try:
2531 try:
2533 if filename == b'-':
2532 if filename == b'-':
2534 text = self.ui.fin.read()
2533 text = self.ui.fin.read()
2535 else:
2534 else:
2536 fp = hg.openpath(self.ui, filename)
2535 fp = hg.openpath(self.ui, filename)
2537 text = fp.read()
2536 text = fp.read()
2538 fp.close()
2537 fp.close()
2539 except (OSError, IOError):
2538 except (OSError, IOError):
2540 raise error.Abort(_(b"unable to read file %s") % filename)
2539 raise error.Abort(_(b"unable to read file %s") % filename)
2541 patchf = self.opener(patchname, b"w")
2540 patchf = self.opener(patchname, b"w")
2542 patchf.write(text)
2541 patchf.write(text)
2543 patchf.close()
2542 patchf.close()
2544 if not force:
2543 if not force:
2545 checkseries(patchname)
2544 checkseries(patchname)
2546 if patchname not in self.series:
2545 if patchname not in self.series:
2547 index = self.fullseriesend() + i
2546 index = self.fullseriesend() + i
2548 self.fullseries[index:index] = [patchname]
2547 self.fullseries[index:index] = [patchname]
2549 self.parseseries()
2548 self.parseseries()
2550 self.seriesdirty = True
2549 self.seriesdirty = True
2551 self.ui.warn(_(b"adding %s to series file\n") % patchname)
2550 self.ui.warn(_(b"adding %s to series file\n") % patchname)
2552 self.added.append(patchname)
2551 self.added.append(patchname)
2553 imported.append(patchname)
2552 imported.append(patchname)
2554 patchname = None
2553 patchname = None
2555
2554
2556 self.removeundo(repo)
2555 self.removeundo(repo)
2557 return imported
2556 return imported
2558
2557
2559
2558
2560 def fixkeepchangesopts(ui, opts):
2559 def fixkeepchangesopts(ui, opts):
2561 if (
2560 if (
2562 not ui.configbool(b'mq', b'keepchanges')
2561 not ui.configbool(b'mq', b'keepchanges')
2563 or opts.get(b'force')
2562 or opts.get(b'force')
2564 or opts.get(b'exact')
2563 or opts.get(b'exact')
2565 ):
2564 ):
2566 return opts
2565 return opts
2567 opts = dict(opts)
2566 opts = dict(opts)
2568 opts[b'keep_changes'] = True
2567 opts[b'keep_changes'] = True
2569 return opts
2568 return opts
2570
2569
2571
2570
2572 @command(
2571 @command(
2573 b"qdelete|qremove|qrm",
2572 b"qdelete|qremove|qrm",
2574 [
2573 [
2575 (b'k', b'keep', None, _(b'keep patch file')),
2574 (b'k', b'keep', None, _(b'keep patch file')),
2576 (
2575 (
2577 b'r',
2576 b'r',
2578 b'rev',
2577 b'rev',
2579 [],
2578 [],
2580 _(b'stop managing a revision (DEPRECATED)'),
2579 _(b'stop managing a revision (DEPRECATED)'),
2581 _(b'REV'),
2580 _(b'REV'),
2582 ),
2581 ),
2583 ],
2582 ],
2584 _(b'hg qdelete [-k] [PATCH]...'),
2583 _(b'hg qdelete [-k] [PATCH]...'),
2585 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2584 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2586 )
2585 )
2587 def delete(ui, repo, *patches, **opts):
2586 def delete(ui, repo, *patches, **opts):
2588 """remove patches from queue
2587 """remove patches from queue
2589
2588
2590 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
2591 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
2592 preserved in the patch directory.
2591 preserved in the patch directory.
2593
2592
2594 To stop managing a patch and move it into permanent history,
2593 To stop managing a patch and move it into permanent history,
2595 use the :hg:`qfinish` command."""
2594 use the :hg:`qfinish` command."""
2596 q = repo.mq
2595 q = repo.mq
2597 q.delete(repo, patches, pycompat.byteskwargs(opts))
2596 q.delete(repo, patches, pycompat.byteskwargs(opts))
2598 q.savedirty()
2597 q.savedirty()
2599 return 0
2598 return 0
2600
2599
2601
2600
2602 @command(
2601 @command(
2603 b"qapplied",
2602 b"qapplied",
2604 [(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'))]
2605 + seriesopts,
2604 + seriesopts,
2606 _(b'hg qapplied [-1] [-s] [PATCH]'),
2605 _(b'hg qapplied [-1] [-s] [PATCH]'),
2607 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2606 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2608 )
2607 )
2609 def applied(ui, repo, patch=None, **opts):
2608 def applied(ui, repo, patch=None, **opts):
2610 """print the patches already applied
2609 """print the patches already applied
2611
2610
2612 Returns 0 on success."""
2611 Returns 0 on success."""
2613
2612
2614 q = repo.mq
2613 q = repo.mq
2615 opts = pycompat.byteskwargs(opts)
2614 opts = pycompat.byteskwargs(opts)
2616
2615
2617 if patch:
2616 if patch:
2618 if patch not in q.series:
2617 if patch not in q.series:
2619 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)
2620 end = q.series.index(patch) + 1
2619 end = q.series.index(patch) + 1
2621 else:
2620 else:
2622 end = q.seriesend(True)
2621 end = q.seriesend(True)
2623
2622
2624 if opts.get(b'last') and not end:
2623 if opts.get(b'last') and not end:
2625 ui.write(_(b"no patches applied\n"))
2624 ui.write(_(b"no patches applied\n"))
2626 return 1
2625 return 1
2627 elif opts.get(b'last') and end == 1:
2626 elif opts.get(b'last') and end == 1:
2628 ui.write(_(b"only one patch applied\n"))
2627 ui.write(_(b"only one patch applied\n"))
2629 return 1
2628 return 1
2630 elif opts.get(b'last'):
2629 elif opts.get(b'last'):
2631 start = end - 2
2630 start = end - 2
2632 end = 1
2631 end = 1
2633 else:
2632 else:
2634 start = 0
2633 start = 0
2635
2634
2636 q.qseries(
2635 q.qseries(
2637 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')
2638 )
2637 )
2639
2638
2640
2639
2641 @command(
2640 @command(
2642 b"qunapplied",
2641 b"qunapplied",
2643 [(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,
2644 _(b'hg qunapplied [-1] [-s] [PATCH]'),
2643 _(b'hg qunapplied [-1] [-s] [PATCH]'),
2645 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2644 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2646 )
2645 )
2647 def unapplied(ui, repo, patch=None, **opts):
2646 def unapplied(ui, repo, patch=None, **opts):
2648 """print the patches not yet applied
2647 """print the patches not yet applied
2649
2648
2650 Returns 0 on success."""
2649 Returns 0 on success."""
2651
2650
2652 q = repo.mq
2651 q = repo.mq
2653 opts = pycompat.byteskwargs(opts)
2652 opts = pycompat.byteskwargs(opts)
2654 if patch:
2653 if patch:
2655 if patch not in q.series:
2654 if patch not in q.series:
2656 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)
2657 start = q.series.index(patch) + 1
2656 start = q.series.index(patch) + 1
2658 else:
2657 else:
2659 start = q.seriesend(True)
2658 start = q.seriesend(True)
2660
2659
2661 if start == len(q.series) and opts.get(b'first'):
2660 if start == len(q.series) and opts.get(b'first'):
2662 ui.write(_(b"all patches applied\n"))
2661 ui.write(_(b"all patches applied\n"))
2663 return 1
2662 return 1
2664
2663
2665 if opts.get(b'first'):
2664 if opts.get(b'first'):
2666 length = 1
2665 length = 1
2667 else:
2666 else:
2668 length = None
2667 length = None
2669 q.qseries(
2668 q.qseries(
2670 repo,
2669 repo,
2671 start=start,
2670 start=start,
2672 length=length,
2671 length=length,
2673 status=b'U',
2672 status=b'U',
2674 summary=opts.get(b'summary'),
2673 summary=opts.get(b'summary'),
2675 )
2674 )
2676
2675
2677
2676
2678 @command(
2677 @command(
2679 b"qimport",
2678 b"qimport",
2680 [
2679 [
2681 (b'e', b'existing', None, _(b'import file in patch directory')),
2680 (b'e', b'existing', None, _(b'import file in patch directory')),
2682 (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')),
2683 (b'f', b'force', None, _(b'overwrite existing files')),
2682 (b'f', b'force', None, _(b'overwrite existing files')),
2684 (
2683 (
2685 b'r',
2684 b'r',
2686 b'rev',
2685 b'rev',
2687 [],
2686 [],
2688 _(b'place existing revisions under mq control'),
2687 _(b'place existing revisions under mq control'),
2689 _(b'REV'),
2688 _(b'REV'),
2690 ),
2689 ),
2691 (b'g', b'git', None, _(b'use git extended diff format')),
2690 (b'g', b'git', None, _(b'use git extended diff format')),
2692 (b'P', b'push', None, _(b'qpush after importing')),
2691 (b'P', b'push', None, _(b'qpush after importing')),
2693 ],
2692 ],
2694 _(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]...'),
2695 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2694 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2696 )
2695 )
2697 def qimport(ui, repo, *filename, **opts):
2696 def qimport(ui, repo, *filename, **opts):
2698 """import a patch or existing changeset
2697 """import a patch or existing changeset
2699
2698
2700 The patch is inserted into the series after the last applied
2699 The patch is inserted into the series after the last applied
2701 patch. If no patches have been applied, qimport prepends the patch
2700 patch. If no patches have been applied, qimport prepends the patch
2702 to the series.
2701 to the series.
2703
2702
2704 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
2705 give it a new one with -n/--name.
2704 give it a new one with -n/--name.
2706
2705
2707 You can register an existing patch inside the patch directory with
2706 You can register an existing patch inside the patch directory with
2708 the -e/--existing flag.
2707 the -e/--existing flag.
2709
2708
2710 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
2711 overwritten.
2710 overwritten.
2712
2711
2713 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
2714 (e.g. qimport --rev . -n patch will place the current revision
2713 (e.g. qimport --rev . -n patch will place the current revision
2715 under mq control). With -g/--git, patches imported with --rev will
2714 under mq control). With -g/--git, patches imported with --rev will
2716 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
2717 on why this is important for preserving rename/copy information
2716 on why this is important for preserving rename/copy information
2718 and permission changes. Use :hg:`qfinish` to remove changesets
2717 and permission changes. Use :hg:`qfinish` to remove changesets
2719 from mq control.
2718 from mq control.
2720
2719
2721 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.
2722 When importing from standard input, a patch name must be specified
2721 When importing from standard input, a patch name must be specified
2723 using the --name flag.
2722 using the --name flag.
2724
2723
2725 To import an existing patch while renaming it::
2724 To import an existing patch while renaming it::
2726
2725
2727 hg qimport -e existing-patch -n new-name
2726 hg qimport -e existing-patch -n new-name
2728
2727
2729 Returns 0 if import succeeded.
2728 Returns 0 if import succeeded.
2730 """
2729 """
2731 opts = pycompat.byteskwargs(opts)
2730 opts = pycompat.byteskwargs(opts)
2732 with repo.lock(): # cause this may move phase
2731 with repo.lock(): # cause this may move phase
2733 q = repo.mq
2732 q = repo.mq
2734 try:
2733 try:
2735 imported = q.qimport(
2734 imported = q.qimport(
2736 repo,
2735 repo,
2737 filename,
2736 filename,
2738 patchname=opts.get(b'name'),
2737 patchname=opts.get(b'name'),
2739 existing=opts.get(b'existing'),
2738 existing=opts.get(b'existing'),
2740 force=opts.get(b'force'),
2739 force=opts.get(b'force'),
2741 rev=opts.get(b'rev'),
2740 rev=opts.get(b'rev'),
2742 git=opts.get(b'git'),
2741 git=opts.get(b'git'),
2743 )
2742 )
2744 finally:
2743 finally:
2745 q.savedirty()
2744 q.savedirty()
2746
2745
2747 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'):
2748 return q.push(repo, imported[-1])
2747 return q.push(repo, imported[-1])
2749 return 0
2748 return 0
2750
2749
2751
2750
2752 def qinit(ui, repo, create):
2751 def qinit(ui, repo, create):
2753 """initialize a new queue repository
2752 """initialize a new queue repository
2754
2753
2755 This command also creates a series file for ordering patches, and
2754 This command also creates a series file for ordering patches, and
2756 an mq-specific .hgignore file in the queue repository, to exclude
2755 an mq-specific .hgignore file in the queue repository, to exclude
2757 the status and guards files (these contain mostly transient state).
2756 the status and guards files (these contain mostly transient state).
2758
2757
2759 Returns 0 if initialization succeeded."""
2758 Returns 0 if initialization succeeded."""
2760 q = repo.mq
2759 q = repo.mq
2761 r = q.init(repo, create)
2760 r = q.init(repo, create)
2762 q.savedirty()
2761 q.savedirty()
2763 if r:
2762 if r:
2764 if not os.path.exists(r.wjoin(b'.hgignore')):
2763 if not os.path.exists(r.wjoin(b'.hgignore')):
2765 fp = r.wvfs(b'.hgignore', b'w')
2764 fp = r.wvfs(b'.hgignore', b'w')
2766 fp.write(b'^\\.hg\n')
2765 fp.write(b'^\\.hg\n')
2767 fp.write(b'^\\.mq\n')
2766 fp.write(b'^\\.mq\n')
2768 fp.write(b'syntax: glob\n')
2767 fp.write(b'syntax: glob\n')
2769 fp.write(b'status\n')
2768 fp.write(b'status\n')
2770 fp.write(b'guards\n')
2769 fp.write(b'guards\n')
2771 fp.close()
2770 fp.close()
2772 if not os.path.exists(r.wjoin(b'series')):
2771 if not os.path.exists(r.wjoin(b'series')):
2773 r.wvfs(b'series', b'w').close()
2772 r.wvfs(b'series', b'w').close()
2774 r[None].add([b'.hgignore', b'series'])
2773 r[None].add([b'.hgignore', b'series'])
2775 commands.add(ui, r)
2774 commands.add(ui, r)
2776 return 0
2775 return 0
2777
2776
2778
2777
2779 @command(
2778 @command(
2780 b"qinit",
2779 b"qinit",
2781 [(b'c', b'create-repo', None, _(b'create queue repository'))],
2780 [(b'c', b'create-repo', None, _(b'create queue repository'))],
2782 _(b'hg qinit [-c]'),
2781 _(b'hg qinit [-c]'),
2783 helpcategory=command.CATEGORY_REPO_CREATION,
2782 helpcategory=command.CATEGORY_REPO_CREATION,
2784 helpbasic=True,
2783 helpbasic=True,
2785 )
2784 )
2786 def init(ui, repo, **opts):
2785 def init(ui, repo, **opts):
2787 """init a new queue repository (DEPRECATED)
2786 """init a new queue repository (DEPRECATED)
2788
2787
2789 The queue repository is unversioned by default. If
2788 The queue repository is unversioned by default. If
2790 -c/--create-repo is specified, qinit will create a separate nested
2789 -c/--create-repo is specified, qinit will create a separate nested
2791 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
2792 an unversioned patch repository into a versioned one). You can use
2791 an unversioned patch repository into a versioned one). You can use
2793 qcommit to commit changes to this queue repository.
2792 qcommit to commit changes to this queue repository.
2794
2793
2795 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
2796 commands. With -c, use :hg:`init --mq` instead."""
2795 commands. With -c, use :hg:`init --mq` instead."""
2797 return qinit(ui, repo, create=opts.get('create_repo'))
2796 return qinit(ui, repo, create=opts.get('create_repo'))
2798
2797
2799
2798
2800 @command(
2799 @command(
2801 b"qclone",
2800 b"qclone",
2802 [
2801 [
2803 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
2802 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
2804 (
2803 (
2805 b'U',
2804 b'U',
2806 b'noupdate',
2805 b'noupdate',
2807 None,
2806 None,
2808 _(b'do not update the new working directories'),
2807 _(b'do not update the new working directories'),
2809 ),
2808 ),
2810 (
2809 (
2811 b'',
2810 b'',
2812 b'uncompressed',
2811 b'uncompressed',
2813 None,
2812 None,
2814 _(b'use uncompressed transfer (fast over LAN)'),
2813 _(b'use uncompressed transfer (fast over LAN)'),
2815 ),
2814 ),
2816 (
2815 (
2817 b'p',
2816 b'p',
2818 b'patches',
2817 b'patches',
2819 b'',
2818 b'',
2820 _(b'location of source patch repository'),
2819 _(b'location of source patch repository'),
2821 _(b'REPO'),
2820 _(b'REPO'),
2822 ),
2821 ),
2823 ]
2822 ]
2824 + cmdutil.remoteopts,
2823 + cmdutil.remoteopts,
2825 _(b'hg qclone [OPTION]... SOURCE [DEST]'),
2824 _(b'hg qclone [OPTION]... SOURCE [DEST]'),
2826 helpcategory=command.CATEGORY_REPO_CREATION,
2825 helpcategory=command.CATEGORY_REPO_CREATION,
2827 norepo=True,
2826 norepo=True,
2828 )
2827 )
2829 def clone(ui, source, dest=None, **opts):
2828 def clone(ui, source, dest=None, **opts):
2830 """clone main and patch repository at same time
2829 """clone main and patch repository at same time
2831
2830
2832 If source is local, destination will have no patches applied. If
2831 If source is local, destination will have no patches applied. If
2833 source is remote, this command can not check if patches are
2832 source is remote, this command can not check if patches are
2834 applied in source, so cannot guarantee that patches are not
2833 applied in source, so cannot guarantee that patches are not
2835 applied in destination. If you clone remote repository, be sure
2834 applied in destination. If you clone remote repository, be sure
2836 before that it has no patches applied.
2835 before that it has no patches applied.
2837
2836
2838 Source patch repository is looked for in <src>/.hg/patches by
2837 Source patch repository is looked for in <src>/.hg/patches by
2839 default. Use -p <url> to change.
2838 default. Use -p <url> to change.
2840
2839
2841 The patch directory must be a nested Mercurial repository, as
2840 The patch directory must be a nested Mercurial repository, as
2842 would be created by :hg:`init --mq`.
2841 would be created by :hg:`init --mq`.
2843
2842
2844 Return 0 on success.
2843 Return 0 on success.
2845 """
2844 """
2846 opts = pycompat.byteskwargs(opts)
2845 opts = pycompat.byteskwargs(opts)
2847
2846
2848 def patchdir(repo):
2847 def patchdir(repo):
2849 """compute a patch repo url from a repo object"""
2848 """compute a patch repo url from a repo object"""
2850 url = repo.url()
2849 url = repo.url()
2851 if url.endswith(b'/'):
2850 if url.endswith(b'/'):
2852 url = url[:-1]
2851 url = url[:-1]
2853 return url + b'/.hg/patches'
2852 return url + b'/.hg/patches'
2854
2853
2855 # main repo (destination and sources)
2854 # main repo (destination and sources)
2856 if dest is None:
2855 if dest is None:
2857 dest = hg.defaultdest(source)
2856 dest = hg.defaultdest(source)
2858 source_path = urlutil.get_clone_path_obj(ui, source)
2857 source_path = urlutil.get_clone_path_obj(ui, source)
2859 sr = hg.peer(ui, opts, source_path)
2858 sr = hg.peer(ui, opts, source_path)
2860
2859
2861 # patches repo (source only)
2860 # patches repo (source only)
2862 if opts.get(b'patches'):
2861 if opts.get(b'patches'):
2863 patches_path = urlutil.get_clone_path_obj(ui, opts.get(b'patches'))
2862 patches_path = urlutil.get_clone_path_obj(ui, opts.get(b'patches'))
2864 else:
2863 else:
2865 # XXX path: we should turn this into a path object
2864 # XXX path: we should turn this into a path object
2866 patches_path = patchdir(sr)
2865 patches_path = patchdir(sr)
2867 try:
2866 try:
2868 hg.peer(ui, opts, patches_path)
2867 hg.peer(ui, opts, patches_path)
2869 except error.RepoError:
2868 except error.RepoError:
2870 raise error.Abort(
2869 raise error.Abort(
2871 _(b'versioned patch repository not found (see init --mq)')
2870 _(b'versioned patch repository not found (see init --mq)')
2872 )
2871 )
2873 qbase, destrev = None, None
2872 qbase, destrev = None, None
2874 if sr.local():
2873 if sr.local():
2875 repo = sr.local()
2874 repo = sr.local()
2876 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2875 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2877 qbase = repo.mq.applied[0].node
2876 qbase = repo.mq.applied[0].node
2878 if not hg.islocal(dest):
2877 if not hg.islocal(dest):
2879 heads = set(repo.heads())
2878 heads = set(repo.heads())
2880 destrev = list(heads.difference(repo.heads(qbase)))
2879 destrev = list(heads.difference(repo.heads(qbase)))
2881 destrev.append(repo.changelog.parents(qbase)[0])
2880 destrev.append(repo.changelog.parents(qbase)[0])
2882 elif sr.capable(b'lookup'):
2881 elif sr.capable(b'lookup'):
2883 try:
2882 try:
2884 qbase = sr.lookup(b'qbase')
2883 qbase = sr.lookup(b'qbase')
2885 except error.RepoError:
2884 except error.RepoError:
2886 pass
2885 pass
2887
2886
2888 ui.note(_(b'cloning main repository\n'))
2887 ui.note(_(b'cloning main repository\n'))
2889 sr, dr = hg.clone(
2888 sr, dr = hg.clone(
2890 ui,
2889 ui,
2891 opts,
2890 opts,
2892 sr.url(),
2891 sr.url(),
2893 dest,
2892 dest,
2894 pull=opts.get(b'pull'),
2893 pull=opts.get(b'pull'),
2895 revs=destrev,
2894 revs=destrev,
2896 update=False,
2895 update=False,
2897 stream=opts.get(b'uncompressed'),
2896 stream=opts.get(b'uncompressed'),
2898 )
2897 )
2899
2898
2900 ui.note(_(b'cloning patch repository\n'))
2899 ui.note(_(b'cloning patch repository\n'))
2901 hg.clone(
2900 hg.clone(
2902 ui,
2901 ui,
2903 opts,
2902 opts,
2904 opts.get(b'patches') or patchdir(sr),
2903 opts.get(b'patches') or patchdir(sr),
2905 patchdir(dr),
2904 patchdir(dr),
2906 pull=opts.get(b'pull'),
2905 pull=opts.get(b'pull'),
2907 update=not opts.get(b'noupdate'),
2906 update=not opts.get(b'noupdate'),
2908 stream=opts.get(b'uncompressed'),
2907 stream=opts.get(b'uncompressed'),
2909 )
2908 )
2910
2909
2911 if dr.local():
2910 if dr.local():
2912 repo = dr.local()
2911 repo = dr.local()
2913 if qbase:
2912 if qbase:
2914 ui.note(
2913 ui.note(
2915 _(
2914 _(
2916 b'stripping applied patches from destination '
2915 b'stripping applied patches from destination '
2917 b'repository\n'
2916 b'repository\n'
2918 )
2917 )
2919 )
2918 )
2920 strip(ui, repo, [qbase], update=False, backup=None)
2919 strip(ui, repo, [qbase], update=False, backup=None)
2921 if not opts.get(b'noupdate'):
2920 if not opts.get(b'noupdate'):
2922 ui.note(_(b'updating destination repository\n'))
2921 ui.note(_(b'updating destination repository\n'))
2923 hg.update(repo, repo.changelog.tip())
2922 hg.update(repo, repo.changelog.tip())
2924
2923
2925
2924
2926 @command(
2925 @command(
2927 b"qcommit|qci",
2926 b"qcommit|qci",
2928 commands.table[b"commit|ci"][1],
2927 commands.table[b"commit|ci"][1],
2929 _(b'hg qcommit [OPTION]... [FILE]...'),
2928 _(b'hg qcommit [OPTION]... [FILE]...'),
2930 helpcategory=command.CATEGORY_COMMITTING,
2929 helpcategory=command.CATEGORY_COMMITTING,
2931 inferrepo=True,
2930 inferrepo=True,
2932 )
2931 )
2933 def commit(ui, repo, *pats, **opts):
2932 def commit(ui, repo, *pats, **opts):
2934 """commit changes in the queue repository (DEPRECATED)
2933 """commit changes in the queue repository (DEPRECATED)
2935
2934
2936 This command is deprecated; use :hg:`commit --mq` instead."""
2935 This command is deprecated; use :hg:`commit --mq` instead."""
2937 q = repo.mq
2936 q = repo.mq
2938 r = q.qrepo()
2937 r = q.qrepo()
2939 if not r:
2938 if not r:
2940 raise error.Abort(b'no queue repository')
2939 raise error.Abort(b'no queue repository')
2941 commands.commit(r.ui, r, *pats, **opts)
2940 commands.commit(r.ui, r, *pats, **opts)
2942
2941
2943
2942
2944 @command(
2943 @command(
2945 b"qseries",
2944 b"qseries",
2946 [
2945 [
2947 (b'm', b'missing', None, _(b'print patches not in series')),
2946 (b'm', b'missing', None, _(b'print patches not in series')),
2948 ]
2947 ]
2949 + seriesopts,
2948 + seriesopts,
2950 _(b'hg qseries [-ms]'),
2949 _(b'hg qseries [-ms]'),
2951 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2950 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2952 )
2951 )
2953 def series(ui, repo, **opts):
2952 def series(ui, repo, **opts):
2954 """print the entire series file
2953 """print the entire series file
2955
2954
2956 Returns 0 on success."""
2955 Returns 0 on success."""
2957 repo.mq.qseries(
2956 repo.mq.qseries(
2958 repo, missing=opts.get('missing'), summary=opts.get('summary')
2957 repo, missing=opts.get('missing'), summary=opts.get('summary')
2959 )
2958 )
2960 return 0
2959 return 0
2961
2960
2962
2961
2963 @command(
2962 @command(
2964 b"qtop",
2963 b"qtop",
2965 seriesopts,
2964 seriesopts,
2966 _(b'hg qtop [-s]'),
2965 _(b'hg qtop [-s]'),
2967 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2966 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2968 )
2967 )
2969 def top(ui, repo, **opts):
2968 def top(ui, repo, **opts):
2970 """print the name of the current patch
2969 """print the name of the current patch
2971
2970
2972 Returns 0 on success."""
2971 Returns 0 on success."""
2973 q = repo.mq
2972 q = repo.mq
2974 if q.applied:
2973 if q.applied:
2975 t = q.seriesend(True)
2974 t = q.seriesend(True)
2976 else:
2975 else:
2977 t = 0
2976 t = 0
2978
2977
2979 if t:
2978 if t:
2980 q.qseries(
2979 q.qseries(
2981 repo,
2980 repo,
2982 start=t - 1,
2981 start=t - 1,
2983 length=1,
2982 length=1,
2984 status=b'A',
2983 status=b'A',
2985 summary=opts.get('summary'),
2984 summary=opts.get('summary'),
2986 )
2985 )
2987 else:
2986 else:
2988 ui.write(_(b"no patches applied\n"))
2987 ui.write(_(b"no patches applied\n"))
2989 return 1
2988 return 1
2990
2989
2991
2990
2992 @command(
2991 @command(
2993 b"qnext",
2992 b"qnext",
2994 seriesopts,
2993 seriesopts,
2995 _(b'hg qnext [-s]'),
2994 _(b'hg qnext [-s]'),
2996 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2995 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2997 )
2996 )
2998 def next(ui, repo, **opts):
2997 def next(ui, repo, **opts):
2999 """print the name of the next pushable patch
2998 """print the name of the next pushable patch
3000
2999
3001 Returns 0 on success."""
3000 Returns 0 on success."""
3002 q = repo.mq
3001 q = repo.mq
3003 end = q.seriesend()
3002 end = q.seriesend()
3004 if end == len(q.series):
3003 if end == len(q.series):
3005 ui.write(_(b"all patches applied\n"))
3004 ui.write(_(b"all patches applied\n"))
3006 return 1
3005 return 1
3007 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
3006 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
3008
3007
3009
3008
3010 @command(
3009 @command(
3011 b"qprev",
3010 b"qprev",
3012 seriesopts,
3011 seriesopts,
3013 _(b'hg qprev [-s]'),
3012 _(b'hg qprev [-s]'),
3014 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3013 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3015 )
3014 )
3016 def prev(ui, repo, **opts):
3015 def prev(ui, repo, **opts):
3017 """print the name of the preceding applied patch
3016 """print the name of the preceding applied patch
3018
3017
3019 Returns 0 on success."""
3018 Returns 0 on success."""
3020 q = repo.mq
3019 q = repo.mq
3021 l = len(q.applied)
3020 l = len(q.applied)
3022 if l == 1:
3021 if l == 1:
3023 ui.write(_(b"only one patch applied\n"))
3022 ui.write(_(b"only one patch applied\n"))
3024 return 1
3023 return 1
3025 if not l:
3024 if not l:
3026 ui.write(_(b"no patches applied\n"))
3025 ui.write(_(b"no patches applied\n"))
3027 return 1
3026 return 1
3028 idx = q.series.index(q.applied[-2].name)
3027 idx = q.series.index(q.applied[-2].name)
3029 q.qseries(
3028 q.qseries(
3030 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')
3031 )
3030 )
3032
3031
3033
3032
3034 def setupheaderopts(ui, opts):
3033 def setupheaderopts(ui, opts):
3035 if not opts.get(b'user') and opts.get(b'currentuser'):
3034 if not opts.get(b'user') and opts.get(b'currentuser'):
3036 opts[b'user'] = ui.username()
3035 opts[b'user'] = ui.username()
3037 if not opts.get(b'date') and opts.get(b'currentdate'):
3036 if not opts.get(b'date') and opts.get(b'currentdate'):
3038 opts[b'date'] = b"%d %d" % dateutil.makedate()
3037 opts[b'date'] = b"%d %d" % dateutil.makedate()
3039
3038
3040
3039
3041 @command(
3040 @command(
3042 b"qnew",
3041 b"qnew",
3043 [
3042 [
3044 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
3043 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
3045 (b'f', b'force', None, _(b'import uncommitted changes (DEPRECATED)')),
3044 (b'f', b'force', None, _(b'import uncommitted changes (DEPRECATED)')),
3046 (b'g', b'git', None, _(b'use git extended diff format')),
3045 (b'g', b'git', None, _(b'use git extended diff format')),
3047 (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')),
3048 (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')),
3049 (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')),
3050 (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')),
3051 ]
3050 ]
3052 + cmdutil.walkopts
3051 + cmdutil.walkopts
3053 + cmdutil.commitopts,
3052 + cmdutil.commitopts,
3054 _(b'hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'),
3053 _(b'hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'),
3055 helpcategory=command.CATEGORY_COMMITTING,
3054 helpcategory=command.CATEGORY_COMMITTING,
3056 helpbasic=True,
3055 helpbasic=True,
3057 inferrepo=True,
3056 inferrepo=True,
3058 )
3057 )
3059 def new(ui, repo, patch, *args, **opts):
3058 def new(ui, repo, patch, *args, **opts):
3060 """create a new patch
3059 """create a new patch
3061
3060
3062 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
3063 any). The patch will be initialized with any outstanding changes
3062 any). The patch will be initialized with any outstanding changes
3064 in the working directory. You may also use -I/--include,
3063 in the working directory. You may also use -I/--include,
3065 -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
3066 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
3067 as uncommitted modifications.
3066 as uncommitted modifications.
3068
3067
3069 -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
3070 date, respectively. -U/--currentuser and -D/--currentdate set user
3069 date, respectively. -U/--currentuser and -D/--currentdate set user
3071 to current user and date to current date.
3070 to current user and date to current date.
3072
3071
3073 -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
3074 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
3075 empty and the commit message is '[mq]: PATCH'.
3074 empty and the commit message is '[mq]: PATCH'.
3076
3075
3077 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
3078 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
3079 is important for preserving permission changes and copy/rename
3078 is important for preserving permission changes and copy/rename
3080 information.
3079 information.
3081
3080
3082 Returns 0 on successful creation of a new patch.
3081 Returns 0 on successful creation of a new patch.
3083 """
3082 """
3084 opts = pycompat.byteskwargs(opts)
3083 opts = pycompat.byteskwargs(opts)
3085 msg = cmdutil.logmessage(ui, opts)
3084 msg = cmdutil.logmessage(ui, opts)
3086 q = repo.mq
3085 q = repo.mq
3087 opts[b'msg'] = msg
3086 opts[b'msg'] = msg
3088 setupheaderopts(ui, opts)
3087 setupheaderopts(ui, opts)
3089 q.new(repo, patch, *args, **pycompat.strkwargs(opts))
3088 q.new(repo, patch, *args, **pycompat.strkwargs(opts))
3090 q.savedirty()
3089 q.savedirty()
3091 return 0
3090 return 0
3092
3091
3093
3092
3094 @command(
3093 @command(
3095 b"qrefresh",
3094 b"qrefresh",
3096 [
3095 [
3097 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
3096 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
3098 (b'g', b'git', None, _(b'use git extended diff format')),
3097 (b'g', b'git', None, _(b'use git extended diff format')),
3099 (
3098 (
3100 b's',
3099 b's',
3101 b'short',
3100 b'short',
3102 None,
3101 None,
3103 _(b'refresh only files already in the patch and specified files'),
3102 _(b'refresh only files already in the patch and specified files'),
3104 ),
3103 ),
3105 (
3104 (
3106 b'U',
3105 b'U',
3107 b'currentuser',
3106 b'currentuser',
3108 None,
3107 None,
3109 _(b'add/update author field in patch with current user'),
3108 _(b'add/update author field in patch with current user'),
3110 ),
3109 ),
3111 (
3110 (
3112 b'u',
3111 b'u',
3113 b'user',
3112 b'user',
3114 b'',
3113 b'',
3115 _(b'add/update author field in patch with given user'),
3114 _(b'add/update author field in patch with given user'),
3116 _(b'USER'),
3115 _(b'USER'),
3117 ),
3116 ),
3118 (
3117 (
3119 b'D',
3118 b'D',
3120 b'currentdate',
3119 b'currentdate',
3121 None,
3120 None,
3122 _(b'add/update date field in patch with current date'),
3121 _(b'add/update date field in patch with current date'),
3123 ),
3122 ),
3124 (
3123 (
3125 b'd',
3124 b'd',
3126 b'date',
3125 b'date',
3127 b'',
3126 b'',
3128 _(b'add/update date field in patch with given date'),
3127 _(b'add/update date field in patch with given date'),
3129 _(b'DATE'),
3128 _(b'DATE'),
3130 ),
3129 ),
3131 ]
3130 ]
3132 + cmdutil.walkopts
3131 + cmdutil.walkopts
3133 + cmdutil.commitopts,
3132 + cmdutil.commitopts,
3134 _(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]...'),
3135 helpcategory=command.CATEGORY_COMMITTING,
3134 helpcategory=command.CATEGORY_COMMITTING,
3136 helpbasic=True,
3135 helpbasic=True,
3137 inferrepo=True,
3136 inferrepo=True,
3138 )
3137 )
3139 def refresh(ui, repo, *pats, **opts):
3138 def refresh(ui, repo, *pats, **opts):
3140 """update the current patch
3139 """update the current patch
3141
3140
3142 If any file patterns are provided, the refreshed patch will
3141 If any file patterns are provided, the refreshed patch will
3143 contain only the modifications that match those patterns; the
3142 contain only the modifications that match those patterns; the
3144 remaining modifications will remain in the working directory.
3143 remaining modifications will remain in the working directory.
3145
3144
3146 If -s/--short is specified, files currently included in the patch
3145 If -s/--short is specified, files currently included in the patch
3147 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.
3148
3147
3149 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
3150 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
3151 your message in ``.hg/last-message.txt``.
3150 your message in ``.hg/last-message.txt``.
3152
3151
3153 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
3154 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
3155 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
3156 git diff format.
3155 git diff format.
3157
3156
3158 Returns 0 on success.
3157 Returns 0 on success.
3159 """
3158 """
3160 opts = pycompat.byteskwargs(opts)
3159 opts = pycompat.byteskwargs(opts)
3161 q = repo.mq
3160 q = repo.mq
3162 message = cmdutil.logmessage(ui, opts)
3161 message = cmdutil.logmessage(ui, opts)
3163 setupheaderopts(ui, opts)
3162 setupheaderopts(ui, opts)
3164 with repo.wlock():
3163 with repo.wlock():
3165 ret = q.refresh(repo, pats, msg=message, **pycompat.strkwargs(opts))
3164 ret = q.refresh(repo, pats, msg=message, **pycompat.strkwargs(opts))
3166 q.savedirty()
3165 q.savedirty()
3167 return ret
3166 return ret
3168
3167
3169
3168
3170 @command(
3169 @command(
3171 b"qdiff",
3170 b"qdiff",
3172 cmdutil.diffopts + cmdutil.diffopts2 + cmdutil.walkopts,
3171 cmdutil.diffopts + cmdutil.diffopts2 + cmdutil.walkopts,
3173 _(b'hg qdiff [OPTION]... [FILE]...'),
3172 _(b'hg qdiff [OPTION]... [FILE]...'),
3174 helpcategory=command.CATEGORY_FILE_CONTENTS,
3173 helpcategory=command.CATEGORY_FILE_CONTENTS,
3175 helpbasic=True,
3174 helpbasic=True,
3176 inferrepo=True,
3175 inferrepo=True,
3177 )
3176 )
3178 def diff(ui, repo, *pats, **opts):
3177 def diff(ui, repo, *pats, **opts):
3179 """diff of the current patch and subsequent modifications
3178 """diff of the current patch and subsequent modifications
3180
3179
3181 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
3182 changes which have been made in the working directory since the
3181 changes which have been made in the working directory since the
3183 last refresh (thus showing what the current patch would become
3182 last refresh (thus showing what the current patch would become
3184 after a qrefresh).
3183 after a qrefresh).
3185
3184
3186 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
3187 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
3188 made by the current patch without including changes made since the
3187 made by the current patch without including changes made since the
3189 qrefresh.
3188 qrefresh.
3190
3189
3191 Returns 0 on success.
3190 Returns 0 on success.
3192 """
3191 """
3193 ui.pager(b'qdiff')
3192 ui.pager(b'qdiff')
3194 repo.mq.diff(repo, pats, pycompat.byteskwargs(opts))
3193 repo.mq.diff(repo, pats, pycompat.byteskwargs(opts))
3195 return 0
3194 return 0
3196
3195
3197
3196
3198 @command(
3197 @command(
3199 b'qfold',
3198 b'qfold',
3200 [
3199 [
3201 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
3200 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
3202 (b'k', b'keep', None, _(b'keep folded patch files')),
3201 (b'k', b'keep', None, _(b'keep folded patch files')),
3203 ]
3202 ]
3204 + cmdutil.commitopts,
3203 + cmdutil.commitopts,
3205 _(b'hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'),
3204 _(b'hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'),
3206 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
3205 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
3207 )
3206 )
3208 def fold(ui, repo, *files, **opts):
3207 def fold(ui, repo, *files, **opts):
3209 """fold the named patches into the current patch
3208 """fold the named patches into the current patch
3210
3209
3211 Patches must not yet be applied. Each patch will be successively
3210 Patches must not yet be applied. Each patch will be successively
3212 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
3213 patches apply successfully, the current patch will be refreshed
3212 patches apply successfully, the current patch will be refreshed
3214 with the new cumulative patch, and the folded patches will be
3213 with the new cumulative patch, and the folded patches will be
3215 deleted. With -k/--keep, the folded patch files will not be
3214 deleted. With -k/--keep, the folded patch files will not be
3216 removed afterwards.
3215 removed afterwards.
3217
3216
3218 The header for each folded patch will be concatenated with the
3217 The header for each folded patch will be concatenated with the
3219 current patch header, separated by a line of ``* * *``.
3218 current patch header, separated by a line of ``* * *``.
3220
3219
3221 Returns 0 on success."""
3220 Returns 0 on success."""
3222 opts = pycompat.byteskwargs(opts)
3221 opts = pycompat.byteskwargs(opts)
3223 q = repo.mq
3222 q = repo.mq
3224 if not files:
3223 if not files:
3225 raise error.Abort(_(b'qfold requires at least one patch name'))
3224 raise error.Abort(_(b'qfold requires at least one patch name'))
3226 if not q.checktoppatch(repo)[0]:
3225 if not q.checktoppatch(repo)[0]:
3227 raise error.Abort(_(b'no patches applied'))
3226 raise error.Abort(_(b'no patches applied'))
3228 q.checklocalchanges(repo)
3227
3229
3228 with repo.wlock():
3230 message = cmdutil.logmessage(ui, opts)
3229 q.checklocalchanges(repo)
3231
3230
3232 parent = q.lookup(b'qtip')
3231 message = cmdutil.logmessage(ui, opts)
3233 patches = []
3232
3234 messages = []
3233 parent = q.lookup(b'qtip')
3235 for f in files:
3234 patches = []
3236 p = q.lookup(f)
3235 messages = []
3237 if p in patches or p == parent:
3236 for f in files:
3238 ui.warn(_(b'skipping already folded patch %s\n') % p)
3237 p = q.lookup(f)
3239 if q.isapplied(p):
3238 if p in patches or p == parent:
3240 raise error.Abort(
3239 ui.warn(_(b'skipping already folded patch %s\n') % p)
3241 _(b'qfold cannot fold already applied patch %s') % p
3240 if q.isapplied(p):
3242 )
3241 raise error.Abort(
3243 patches.append(p)
3242 _(b'qfold cannot fold already applied patch %s') % p
3244
3243 )
3245 for p in patches:
3244 patches.append(p)
3245
3246 for p in patches:
3247 if not message:
3248 ph = patchheader(q.join(p), q.plainmode)
3249 if ph.message:
3250 messages.append(ph.message)
3251 pf = q.join(p)
3252 (patchsuccess, files, fuzz) = q.patch(repo, pf)
3253 if not patchsuccess:
3254 raise error.Abort(_(b'error folding patch %s') % p)
3255
3246 if not message:
3256 if not message:
3247 ph = patchheader(q.join(p), q.plainmode)
3257 ph = patchheader(q.join(parent), q.plainmode)
3248 if ph.message:
3258 message = ph.message
3249 messages.append(ph.message)
3259 for msg in messages:
3250 pf = q.join(p)
3260 if msg:
3251 (patchsuccess, files, fuzz) = q.patch(repo, pf)
3261 if message:
3252 if not patchsuccess:
3262 message.append(b'* * *')
3253 raise error.Abort(_(b'error folding patch %s') % p)
3263 message.extend(msg)
3254
3264 message = b'\n'.join(message)
3255 if not message:
3265
3256 ph = patchheader(q.join(parent), q.plainmode)
3266 diffopts = q.patchopts(q.diffopts(), *patches)
3257 message = ph.message
3258 for msg in messages:
3259 if msg:
3260 if message:
3261 message.append(b'* * *')
3262 message.extend(msg)
3263 message = b'\n'.join(message)
3264
3265 diffopts = q.patchopts(q.diffopts(), *patches)
3266 with repo.wlock():
3267 q.refresh(
3267 q.refresh(
3268 repo,
3268 repo,
3269 msg=message,
3269 msg=message,
3270 git=diffopts.git,
3270 git=diffopts.git,
3271 edit=opts.get(b'edit'),
3271 edit=opts.get(b'edit'),
3272 editform=b'mq.qfold',
3272 editform=b'mq.qfold',
3273 )
3273 )
3274 q.delete(repo, patches, opts)
3274 q.delete(repo, patches, opts)
3275 q.savedirty()
3275 q.savedirty()
3276
3276
3277
3277
3278 @command(
3278 @command(
3279 b"qgoto",
3279 b"qgoto",
3280 [
3280 [
3281 (
3281 (
3282 b'',
3282 b'',
3283 b'keep-changes',
3283 b'keep-changes',
3284 None,
3284 None,
3285 _(b'tolerate non-conflicting local changes'),
3285 _(b'tolerate non-conflicting local changes'),
3286 ),
3286 ),
3287 (b'f', b'force', None, _(b'overwrite any local changes')),
3287 (b'f', b'force', None, _(b'overwrite any local changes')),
3288 (b'', b'no-backup', None, _(b'do not save backup copies of files')),
3288 (b'', b'no-backup', None, _(b'do not save backup copies of files')),
3289 ],
3289 ],
3290 _(b'hg qgoto [OPTION]... PATCH'),
3290 _(b'hg qgoto [OPTION]... PATCH'),
3291 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3291 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3292 )
3292 )
3293 def goto(ui, repo, patch, **opts):
3293 def goto(ui, repo, patch, **opts):
3294 """push or pop patches until named patch is at top of stack
3294 """push or pop patches until named patch is at top of stack
3295
3295
3296 Returns 0 on success."""
3296 Returns 0 on success."""
3297 opts = pycompat.byteskwargs(opts)
3297 opts = pycompat.byteskwargs(opts)
3298 opts = fixkeepchangesopts(ui, opts)
3298 opts = fixkeepchangesopts(ui, opts)
3299 q = repo.mq
3299 q = repo.mq
3300 patch = q.lookup(patch)
3300 patch = q.lookup(patch)
3301 nobackup = opts.get(b'no_backup')
3301 nobackup = opts.get(b'no_backup')
3302 keepchanges = opts.get(b'keep_changes')
3302 keepchanges = opts.get(b'keep_changes')
3303 if q.isapplied(patch):
3303 if q.isapplied(patch):
3304 ret = q.pop(
3304 ret = q.pop(
3305 repo,
3305 repo,
3306 patch,
3306 patch,
3307 force=opts.get(b'force'),
3307 force=opts.get(b'force'),
3308 nobackup=nobackup,
3308 nobackup=nobackup,
3309 keepchanges=keepchanges,
3309 keepchanges=keepchanges,
3310 )
3310 )
3311 else:
3311 else:
3312 ret = q.push(
3312 ret = q.push(
3313 repo,
3313 repo,
3314 patch,
3314 patch,
3315 force=opts.get(b'force'),
3315 force=opts.get(b'force'),
3316 nobackup=nobackup,
3316 nobackup=nobackup,
3317 keepchanges=keepchanges,
3317 keepchanges=keepchanges,
3318 )
3318 )
3319 q.savedirty()
3319 q.savedirty()
3320 return ret
3320 return ret
3321
3321
3322
3322
3323 @command(
3323 @command(
3324 b"qguard",
3324 b"qguard",
3325 [
3325 [
3326 (b'l', b'list', None, _(b'list all patches and guards')),
3326 (b'l', b'list', None, _(b'list all patches and guards')),
3327 (b'n', b'none', None, _(b'drop all guards')),
3327 (b'n', b'none', None, _(b'drop all guards')),
3328 ],
3328 ],
3329 _(b'hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'),
3329 _(b'hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'),
3330 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3330 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3331 )
3331 )
3332 def guard(ui, repo, *args, **opts):
3332 def guard(ui, repo, *args, **opts):
3333 """set or print guards for a patch
3333 """set or print guards for a patch
3334
3334
3335 Guards control whether a patch can be pushed. A patch with no
3335 Guards control whether a patch can be pushed. A patch with no
3336 guards is always pushed. A patch with a positive guard ("+foo") is
3336 guards is always pushed. A patch with a positive guard ("+foo") is
3337 pushed only if the :hg:`qselect` command has activated it. A patch with
3337 pushed only if the :hg:`qselect` command has activated it. A patch with
3338 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
3338 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
3339 has activated it.
3339 has activated it.
3340
3340
3341 With no arguments, print the currently active guards.
3341 With no arguments, print the currently active guards.
3342 With arguments, set guards for the named patch.
3342 With arguments, set guards for the named patch.
3343
3343
3344 .. note::
3344 .. note::
3345
3345
3346 Specifying negative guards now requires '--'.
3346 Specifying negative guards now requires '--'.
3347
3347
3348 To set guards on another patch::
3348 To set guards on another patch::
3349
3349
3350 hg qguard other.patch -- +2.6.17 -stable
3350 hg qguard other.patch -- +2.6.17 -stable
3351
3351
3352 Returns 0 on success.
3352 Returns 0 on success.
3353 """
3353 """
3354
3354
3355 def status(idx):
3355 def status(idx):
3356 guards = q.seriesguards[idx] or [b'unguarded']
3356 guards = q.seriesguards[idx] or [b'unguarded']
3357 if q.series[idx] in applied:
3357 if q.series[idx] in applied:
3358 state = b'applied'
3358 state = b'applied'
3359 elif q.pushable(idx)[0]:
3359 elif q.pushable(idx)[0]:
3360 state = b'unapplied'
3360 state = b'unapplied'
3361 else:
3361 else:
3362 state = b'guarded'
3362 state = b'guarded'
3363 label = b'qguard.patch qguard.%s qseries.%s' % (state, state)
3363 label = b'qguard.patch qguard.%s qseries.%s' % (state, state)
3364 ui.write(b'%s: ' % ui.label(q.series[idx], label))
3364 ui.write(b'%s: ' % ui.label(q.series[idx], label))
3365
3365
3366 for i, guard in enumerate(guards):
3366 for i, guard in enumerate(guards):
3367 if guard.startswith(b'+'):
3367 if guard.startswith(b'+'):
3368 ui.write(guard, label=b'qguard.positive')
3368 ui.write(guard, label=b'qguard.positive')
3369 elif guard.startswith(b'-'):
3369 elif guard.startswith(b'-'):
3370 ui.write(guard, label=b'qguard.negative')
3370 ui.write(guard, label=b'qguard.negative')
3371 else:
3371 else:
3372 ui.write(guard, label=b'qguard.unguarded')
3372 ui.write(guard, label=b'qguard.unguarded')
3373 if i != len(guards) - 1:
3373 if i != len(guards) - 1:
3374 ui.write(b' ')
3374 ui.write(b' ')
3375 ui.write(b'\n')
3375 ui.write(b'\n')
3376
3376
3377 q = repo.mq
3377 q = repo.mq
3378 applied = {p.name for p in q.applied}
3378 applied = {p.name for p in q.applied}
3379 patch = None
3379 patch = None
3380 args = list(args)
3380 args = list(args)
3381 if opts.get('list'):
3381 if opts.get('list'):
3382 if args or opts.get('none'):
3382 if args or opts.get('none'):
3383 raise error.Abort(
3383 raise error.Abort(
3384 _(b'cannot mix -l/--list with options or arguments')
3384 _(b'cannot mix -l/--list with options or arguments')
3385 )
3385 )
3386 for i in range(len(q.series)):
3386 for i in range(len(q.series)):
3387 status(i)
3387 status(i)
3388 return
3388 return
3389 if not args or args[0][0:1] in b'-+':
3389 if not args or args[0][0:1] in b'-+':
3390 if not q.applied:
3390 if not q.applied:
3391 raise error.Abort(_(b'no patches applied'))
3391 raise error.Abort(_(b'no patches applied'))
3392 patch = q.applied[-1].name
3392 patch = q.applied[-1].name
3393 if patch is None and args[0][0:1] not in b'-+':
3393 if patch is None and args[0][0:1] not in b'-+':
3394 patch = args.pop(0)
3394 patch = args.pop(0)
3395 if patch is None:
3395 if patch is None:
3396 raise error.Abort(_(b'no patch to work with'))
3396 raise error.Abort(_(b'no patch to work with'))
3397 if args or opts.get('none'):
3397 if args or opts.get('none'):
3398 idx = q.findseries(patch)
3398 idx = q.findseries(patch)
3399 if idx is None:
3399 if idx is None:
3400 raise error.Abort(_(b'no patch named %s') % patch)
3400 raise error.Abort(_(b'no patch named %s') % patch)
3401 q.setguards(idx, args)
3401 q.setguards(idx, args)
3402 q.savedirty()
3402 q.savedirty()
3403 else:
3403 else:
3404 status(q.series.index(q.lookup(patch)))
3404 status(q.series.index(q.lookup(patch)))
3405
3405
3406
3406
3407 @command(
3407 @command(
3408 b"qheader",
3408 b"qheader",
3409 [],
3409 [],
3410 _(b'hg qheader [PATCH]'),
3410 _(b'hg qheader [PATCH]'),
3411 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3411 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3412 )
3412 )
3413 def header(ui, repo, patch=None):
3413 def header(ui, repo, patch=None):
3414 """print the header of the topmost or specified patch
3414 """print the header of the topmost or specified patch
3415
3415
3416 Returns 0 on success."""
3416 Returns 0 on success."""
3417 q = repo.mq
3417 q = repo.mq
3418
3418
3419 if patch:
3419 if patch:
3420 patch = q.lookup(patch)
3420 patch = q.lookup(patch)
3421 else:
3421 else:
3422 if not q.applied:
3422 if not q.applied:
3423 ui.write(_(b'no patches applied\n'))
3423 ui.write(_(b'no patches applied\n'))
3424 return 1
3424 return 1
3425 patch = q.lookup(b'qtip')
3425 patch = q.lookup(b'qtip')
3426 ph = patchheader(q.join(patch), q.plainmode)
3426 ph = patchheader(q.join(patch), q.plainmode)
3427
3427
3428 ui.write(b'\n'.join(ph.message) + b'\n')
3428 ui.write(b'\n'.join(ph.message) + b'\n')
3429
3429
3430
3430
3431 def lastsavename(path):
3431 def lastsavename(path):
3432 (directory, base) = os.path.split(path)
3432 (directory, base) = os.path.split(path)
3433 names = os.listdir(directory)
3433 names = os.listdir(directory)
3434 namere = re.compile(b"%s.([0-9]+)" % base)
3434 namere = re.compile(b"%s.([0-9]+)" % base)
3435 maxindex = None
3435 maxindex = None
3436 maxname = None
3436 maxname = None
3437 for f in names:
3437 for f in names:
3438 m = namere.match(f)
3438 m = namere.match(f)
3439 if m:
3439 if m:
3440 index = int(m.group(1))
3440 index = int(m.group(1))
3441 if maxindex is None or index > maxindex:
3441 if maxindex is None or index > maxindex:
3442 maxindex = index
3442 maxindex = index
3443 maxname = f
3443 maxname = f
3444 if maxname:
3444 if maxname:
3445 return (os.path.join(directory, maxname), maxindex)
3445 return (os.path.join(directory, maxname), maxindex)
3446 return (None, None)
3446 return (None, None)
3447
3447
3448
3448
3449 def savename(path):
3449 def savename(path):
3450 (last, index) = lastsavename(path)
3450 (last, index) = lastsavename(path)
3451 if last is None:
3451 if last is None:
3452 index = 0
3452 index = 0
3453 newpath = path + b".%d" % (index + 1)
3453 newpath = path + b".%d" % (index + 1)
3454 return newpath
3454 return newpath
3455
3455
3456
3456
3457 @command(
3457 @command(
3458 b"qpush",
3458 b"qpush",
3459 [
3459 [
3460 (
3460 (
3461 b'',
3461 b'',
3462 b'keep-changes',
3462 b'keep-changes',
3463 None,
3463 None,
3464 _(b'tolerate non-conflicting local changes'),
3464 _(b'tolerate non-conflicting local changes'),
3465 ),
3465 ),
3466 (b'f', b'force', None, _(b'apply on top of local changes')),
3466 (b'f', b'force', None, _(b'apply on top of local changes')),
3467 (
3467 (
3468 b'e',
3468 b'e',
3469 b'exact',
3469 b'exact',
3470 None,
3470 None,
3471 _(b'apply the target patch to its recorded parent'),
3471 _(b'apply the target patch to its recorded parent'),
3472 ),
3472 ),
3473 (b'l', b'list', None, _(b'list patch name in commit text')),
3473 (b'l', b'list', None, _(b'list patch name in commit text')),
3474 (b'a', b'all', None, _(b'apply all patches')),
3474 (b'a', b'all', None, _(b'apply all patches')),
3475 (b'm', b'merge', None, _(b'merge from another queue (DEPRECATED)')),
3475 (b'm', b'merge', None, _(b'merge from another queue (DEPRECATED)')),
3476 (b'n', b'name', b'', _(b'merge queue name (DEPRECATED)'), _(b'NAME')),
3476 (b'n', b'name', b'', _(b'merge queue name (DEPRECATED)'), _(b'NAME')),
3477 (
3477 (
3478 b'',
3478 b'',
3479 b'move',
3479 b'move',
3480 None,
3480 None,
3481 _(b'reorder patch series and apply only the patch'),
3481 _(b'reorder patch series and apply only the patch'),
3482 ),
3482 ),
3483 (b'', b'no-backup', None, _(b'do not save backup copies of files')),
3483 (b'', b'no-backup', None, _(b'do not save backup copies of files')),
3484 ],
3484 ],
3485 _(b'hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'),
3485 _(b'hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'),
3486 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3486 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3487 helpbasic=True,
3487 helpbasic=True,
3488 )
3488 )
3489 def push(ui, repo, patch=None, **opts):
3489 def push(ui, repo, patch=None, **opts):
3490 """push the next patch onto the stack
3490 """push the next patch onto the stack
3491
3491
3492 By default, abort if the working directory contains uncommitted
3492 By default, abort if the working directory contains uncommitted
3493 changes. With --keep-changes, abort only if the uncommitted files
3493 changes. With --keep-changes, abort only if the uncommitted files
3494 overlap with patched files. With -f/--force, backup and patch over
3494 overlap with patched files. With -f/--force, backup and patch over
3495 uncommitted changes.
3495 uncommitted changes.
3496
3496
3497 Return 0 on success.
3497 Return 0 on success.
3498 """
3498 """
3499 q = repo.mq
3499 q = repo.mq
3500 mergeq = None
3500 mergeq = None
3501
3501
3502 opts = pycompat.byteskwargs(opts)
3502 opts = pycompat.byteskwargs(opts)
3503 opts = fixkeepchangesopts(ui, opts)
3503 opts = fixkeepchangesopts(ui, opts)
3504 if opts.get(b'merge'):
3504 if opts.get(b'merge'):
3505 if opts.get(b'name'):
3505 if opts.get(b'name'):
3506 newpath = repo.vfs.join(opts.get(b'name'))
3506 newpath = repo.vfs.join(opts.get(b'name'))
3507 else:
3507 else:
3508 newpath, i = lastsavename(q.path)
3508 newpath, i = lastsavename(q.path)
3509 if not newpath:
3509 if not newpath:
3510 ui.warn(_(b"no saved queues found, please use -n\n"))
3510 ui.warn(_(b"no saved queues found, please use -n\n"))
3511 return 1
3511 return 1
3512 mergeq = queue(ui, repo.baseui, repo.path, newpath)
3512 mergeq = queue(ui, repo.baseui, repo.path, newpath)
3513 ui.warn(_(b"merging with queue at: %s\n") % mergeq.path)
3513 ui.warn(_(b"merging with queue at: %s\n") % mergeq.path)
3514 ret = q.push(
3514 ret = q.push(
3515 repo,
3515 repo,
3516 patch,
3516 patch,
3517 force=opts.get(b'force'),
3517 force=opts.get(b'force'),
3518 list=opts.get(b'list'),
3518 list=opts.get(b'list'),
3519 mergeq=mergeq,
3519 mergeq=mergeq,
3520 all=opts.get(b'all'),
3520 all=opts.get(b'all'),
3521 move=opts.get(b'move'),
3521 move=opts.get(b'move'),
3522 exact=opts.get(b'exact'),
3522 exact=opts.get(b'exact'),
3523 nobackup=opts.get(b'no_backup'),
3523 nobackup=opts.get(b'no_backup'),
3524 keepchanges=opts.get(b'keep_changes'),
3524 keepchanges=opts.get(b'keep_changes'),
3525 )
3525 )
3526 return ret
3526 return ret
3527
3527
3528
3528
3529 @command(
3529 @command(
3530 b"qpop",
3530 b"qpop",
3531 [
3531 [
3532 (b'a', b'all', None, _(b'pop all patches')),
3532 (b'a', b'all', None, _(b'pop all patches')),
3533 (b'n', b'name', b'', _(b'queue name to pop (DEPRECATED)'), _(b'NAME')),
3533 (b'n', b'name', b'', _(b'queue name to pop (DEPRECATED)'), _(b'NAME')),
3534 (
3534 (
3535 b'',
3535 b'',
3536 b'keep-changes',
3536 b'keep-changes',
3537 None,
3537 None,
3538 _(b'tolerate non-conflicting local changes'),
3538 _(b'tolerate non-conflicting local changes'),
3539 ),
3539 ),
3540 (b'f', b'force', None, _(b'forget any local changes to patched files')),
3540 (b'f', b'force', None, _(b'forget any local changes to patched files')),
3541 (b'', b'no-backup', None, _(b'do not save backup copies of files')),
3541 (b'', b'no-backup', None, _(b'do not save backup copies of files')),
3542 ],
3542 ],
3543 _(b'hg qpop [-a] [-f] [PATCH | INDEX]'),
3543 _(b'hg qpop [-a] [-f] [PATCH | INDEX]'),
3544 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3544 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3545 helpbasic=True,
3545 helpbasic=True,
3546 )
3546 )
3547 def pop(ui, repo, patch=None, **opts):
3547 def pop(ui, repo, patch=None, **opts):
3548 """pop the current patch off the stack
3548 """pop the current patch off the stack
3549
3549
3550 Without argument, pops off the top of the patch stack. If given a
3550 Without argument, pops off the top of the patch stack. If given a
3551 patch name, keeps popping off patches until the named patch is at
3551 patch name, keeps popping off patches until the named patch is at
3552 the top of the stack.
3552 the top of the stack.
3553
3553
3554 By default, abort if the working directory contains uncommitted
3554 By default, abort if the working directory contains uncommitted
3555 changes. With --keep-changes, abort only if the uncommitted files
3555 changes. With --keep-changes, abort only if the uncommitted files
3556 overlap with patched files. With -f/--force, backup and discard
3556 overlap with patched files. With -f/--force, backup and discard
3557 changes made to such files.
3557 changes made to such files.
3558
3558
3559 Return 0 on success.
3559 Return 0 on success.
3560 """
3560 """
3561 opts = pycompat.byteskwargs(opts)
3561 opts = pycompat.byteskwargs(opts)
3562 opts = fixkeepchangesopts(ui, opts)
3562 opts = fixkeepchangesopts(ui, opts)
3563 localupdate = True
3563 localupdate = True
3564 if opts.get(b'name'):
3564 if opts.get(b'name'):
3565 q = queue(ui, repo.baseui, repo.path, repo.vfs.join(opts.get(b'name')))
3565 q = queue(ui, repo.baseui, repo.path, repo.vfs.join(opts.get(b'name')))
3566 ui.warn(_(b'using patch queue: %s\n') % q.path)
3566 ui.warn(_(b'using patch queue: %s\n') % q.path)
3567 localupdate = False
3567 localupdate = False
3568 else:
3568 else:
3569 q = repo.mq
3569 q = repo.mq
3570 ret = q.pop(
3570 ret = q.pop(
3571 repo,
3571 repo,
3572 patch,
3572 patch,
3573 force=opts.get(b'force'),
3573 force=opts.get(b'force'),
3574 update=localupdate,
3574 update=localupdate,
3575 all=opts.get(b'all'),
3575 all=opts.get(b'all'),
3576 nobackup=opts.get(b'no_backup'),
3576 nobackup=opts.get(b'no_backup'),
3577 keepchanges=opts.get(b'keep_changes'),
3577 keepchanges=opts.get(b'keep_changes'),
3578 )
3578 )
3579 q.savedirty()
3579 q.savedirty()
3580 return ret
3580 return ret
3581
3581
3582
3582
3583 @command(
3583 @command(
3584 b"qrename|qmv",
3584 b"qrename|qmv",
3585 [],
3585 [],
3586 _(b'hg qrename PATCH1 [PATCH2]'),
3586 _(b'hg qrename PATCH1 [PATCH2]'),
3587 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3587 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3588 )
3588 )
3589 def rename(ui, repo, patch, name=None, **opts):
3589 def rename(ui, repo, patch, name=None, **opts):
3590 """rename a patch
3590 """rename a patch
3591
3591
3592 With one argument, renames the current patch to PATCH1.
3592 With one argument, renames the current patch to PATCH1.
3593 With two arguments, renames PATCH1 to PATCH2.
3593 With two arguments, renames PATCH1 to PATCH2.
3594
3594
3595 Returns 0 on success."""
3595 Returns 0 on success."""
3596 q = repo.mq
3596 q = repo.mq
3597 if not name:
3597 if not name:
3598 name = patch
3598 name = patch
3599 patch = None
3599 patch = None
3600
3600
3601 if patch:
3601 if patch:
3602 patch = q.lookup(patch)
3602 patch = q.lookup(patch)
3603 else:
3603 else:
3604 if not q.applied:
3604 if not q.applied:
3605 ui.write(_(b'no patches applied\n'))
3605 ui.write(_(b'no patches applied\n'))
3606 return
3606 return
3607 patch = q.lookup(b'qtip')
3607 patch = q.lookup(b'qtip')
3608 absdest = q.join(name)
3608 absdest = q.join(name)
3609 if os.path.isdir(absdest):
3609 if os.path.isdir(absdest):
3610 name = normname(os.path.join(name, os.path.basename(patch)))
3610 name = normname(os.path.join(name, os.path.basename(patch)))
3611 absdest = q.join(name)
3611 absdest = q.join(name)
3612 q.checkpatchname(name)
3612 q.checkpatchname(name)
3613
3613
3614 ui.note(_(b'renaming %s to %s\n') % (patch, name))
3614 ui.note(_(b'renaming %s to %s\n') % (patch, name))
3615 i = q.findseries(patch)
3615 i = q.findseries(patch)
3616 guards = q.guard_re.findall(q.fullseries[i])
3616 guards = q.guard_re.findall(q.fullseries[i])
3617 q.fullseries[i] = name + b''.join([b' #' + g for g in guards])
3617 q.fullseries[i] = name + b''.join([b' #' + g for g in guards])
3618 q.parseseries()
3618 q.parseseries()
3619 q.seriesdirty = True
3619 q.seriesdirty = True
3620
3620
3621 info = q.isapplied(patch)
3621 info = q.isapplied(patch)
3622 if info:
3622 if info:
3623 q.applied[info[0]] = statusentry(info[1], name)
3623 q.applied[info[0]] = statusentry(info[1], name)
3624 q.applieddirty = True
3624 q.applieddirty = True
3625
3625
3626 destdir = os.path.dirname(absdest)
3626 destdir = os.path.dirname(absdest)
3627 if not os.path.isdir(destdir):
3627 if not os.path.isdir(destdir):
3628 os.makedirs(destdir)
3628 os.makedirs(destdir)
3629 util.rename(q.join(patch), absdest)
3629 util.rename(q.join(patch), absdest)
3630 r = q.qrepo()
3630 r = q.qrepo()
3631 if r and patch in r.dirstate:
3631 if r and patch in r.dirstate:
3632 wctx = r[None]
3632 wctx = r[None]
3633 with r.wlock():
3633 with r.wlock():
3634 if r.dirstate.get_entry(patch).added:
3634 if r.dirstate.get_entry(patch).added:
3635 r.dirstate.set_untracked(patch)
3635 r.dirstate.set_untracked(patch)
3636 r.dirstate.set_tracked(name)
3636 r.dirstate.set_tracked(name)
3637 else:
3637 else:
3638 wctx.copy(patch, name)
3638 wctx.copy(patch, name)
3639 wctx.forget([patch])
3639 wctx.forget([patch])
3640
3640
3641 q.savedirty()
3641 q.savedirty()
3642
3642
3643
3643
3644 @command(
3644 @command(
3645 b"qrestore",
3645 b"qrestore",
3646 [
3646 [
3647 (b'd', b'delete', None, _(b'delete save entry')),
3647 (b'd', b'delete', None, _(b'delete save entry')),
3648 (b'u', b'update', None, _(b'update queue working directory')),
3648 (b'u', b'update', None, _(b'update queue working directory')),
3649 ],
3649 ],
3650 _(b'hg qrestore [-d] [-u] REV'),
3650 _(b'hg qrestore [-d] [-u] REV'),
3651 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3651 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3652 )
3652 )
3653 def restore(ui, repo, rev, **opts):
3653 def restore(ui, repo, rev, **opts):
3654 """restore the queue state saved by a revision (DEPRECATED)
3654 """restore the queue state saved by a revision (DEPRECATED)
3655
3655
3656 This command is deprecated, use :hg:`rebase` instead."""
3656 This command is deprecated, use :hg:`rebase` instead."""
3657 rev = repo.lookup(rev)
3657 rev = repo.lookup(rev)
3658 q = repo.mq
3658 q = repo.mq
3659 q.restore(repo, rev, delete=opts.get('delete'), qupdate=opts.get('update'))
3659 q.restore(repo, rev, delete=opts.get('delete'), qupdate=opts.get('update'))
3660 q.savedirty()
3660 q.savedirty()
3661 return 0
3661 return 0
3662
3662
3663
3663
3664 @command(
3664 @command(
3665 b"qsave",
3665 b"qsave",
3666 [
3666 [
3667 (b'c', b'copy', None, _(b'copy patch directory')),
3667 (b'c', b'copy', None, _(b'copy patch directory')),
3668 (b'n', b'name', b'', _(b'copy directory name'), _(b'NAME')),
3668 (b'n', b'name', b'', _(b'copy directory name'), _(b'NAME')),
3669 (b'e', b'empty', None, _(b'clear queue status file')),
3669 (b'e', b'empty', None, _(b'clear queue status file')),
3670 (b'f', b'force', None, _(b'force copy')),
3670 (b'f', b'force', None, _(b'force copy')),
3671 ]
3671 ]
3672 + cmdutil.commitopts,
3672 + cmdutil.commitopts,
3673 _(b'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
3673 _(b'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
3674 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3674 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3675 )
3675 )
3676 def save(ui, repo, **opts):
3676 def save(ui, repo, **opts):
3677 """save current queue state (DEPRECATED)
3677 """save current queue state (DEPRECATED)
3678
3678
3679 This command is deprecated, use :hg:`rebase` instead."""
3679 This command is deprecated, use :hg:`rebase` instead."""
3680 q = repo.mq
3680 q = repo.mq
3681 opts = pycompat.byteskwargs(opts)
3681 opts = pycompat.byteskwargs(opts)
3682 message = cmdutil.logmessage(ui, opts)
3682 message = cmdutil.logmessage(ui, opts)
3683 ret = q.save(repo, msg=message)
3683 ret = q.save(repo, msg=message)
3684 if ret:
3684 if ret:
3685 return ret
3685 return ret
3686 q.savedirty() # save to .hg/patches before copying
3686 q.savedirty() # save to .hg/patches before copying
3687 if opts.get(b'copy'):
3687 if opts.get(b'copy'):
3688 path = q.path
3688 path = q.path
3689 if opts.get(b'name'):
3689 if opts.get(b'name'):
3690 newpath = os.path.join(q.basepath, opts.get(b'name'))
3690 newpath = os.path.join(q.basepath, opts.get(b'name'))
3691 if os.path.exists(newpath):
3691 if os.path.exists(newpath):
3692 if not os.path.isdir(newpath):
3692 if not os.path.isdir(newpath):
3693 raise error.Abort(
3693 raise error.Abort(
3694 _(b'destination %s exists and is not a directory')
3694 _(b'destination %s exists and is not a directory')
3695 % newpath
3695 % newpath
3696 )
3696 )
3697 if not opts.get(b'force'):
3697 if not opts.get(b'force'):
3698 raise error.Abort(
3698 raise error.Abort(
3699 _(b'destination %s exists, use -f to force') % newpath
3699 _(b'destination %s exists, use -f to force') % newpath
3700 )
3700 )
3701 else:
3701 else:
3702 newpath = savename(path)
3702 newpath = savename(path)
3703 ui.warn(_(b"copy %s to %s\n") % (path, newpath))
3703 ui.warn(_(b"copy %s to %s\n") % (path, newpath))
3704 util.copyfiles(path, newpath)
3704 util.copyfiles(path, newpath)
3705 if opts.get(b'empty'):
3705 if opts.get(b'empty'):
3706 del q.applied[:]
3706 del q.applied[:]
3707 q.applieddirty = True
3707 q.applieddirty = True
3708 q.savedirty()
3708 q.savedirty()
3709 return 0
3709 return 0
3710
3710
3711
3711
3712 @command(
3712 @command(
3713 b"qselect",
3713 b"qselect",
3714 [
3714 [
3715 (b'n', b'none', None, _(b'disable all guards')),
3715 (b'n', b'none', None, _(b'disable all guards')),
3716 (b's', b'series', None, _(b'list all guards in series file')),
3716 (b's', b'series', None, _(b'list all guards in series file')),
3717 (b'', b'pop', None, _(b'pop to before first guarded applied patch')),
3717 (b'', b'pop', None, _(b'pop to before first guarded applied patch')),
3718 (b'', b'reapply', None, _(b'pop, then reapply patches')),
3718 (b'', b'reapply', None, _(b'pop, then reapply patches')),
3719 ],
3719 ],
3720 _(b'hg qselect [OPTION]... [GUARD]...'),
3720 _(b'hg qselect [OPTION]... [GUARD]...'),
3721 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3721 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3722 )
3722 )
3723 def select(ui, repo, *args, **opts):
3723 def select(ui, repo, *args, **opts):
3724 """set or print guarded patches to push
3724 """set or print guarded patches to push
3725
3725
3726 Use the :hg:`qguard` command to set or print guards on patch, then use
3726 Use the :hg:`qguard` command to set or print guards on patch, then use
3727 qselect to tell mq which guards to use. A patch will be pushed if
3727 qselect to tell mq which guards to use. A patch will be pushed if
3728 it has no guards or any positive guards match the currently
3728 it has no guards or any positive guards match the currently
3729 selected guard, but will not be pushed if any negative guards
3729 selected guard, but will not be pushed if any negative guards
3730 match the current guard. For example::
3730 match the current guard. For example::
3731
3731
3732 qguard foo.patch -- -stable (negative guard)
3732 qguard foo.patch -- -stable (negative guard)
3733 qguard bar.patch +stable (positive guard)
3733 qguard bar.patch +stable (positive guard)
3734 qselect stable
3734 qselect stable
3735
3735
3736 This activates the "stable" guard. mq will skip foo.patch (because
3736 This activates the "stable" guard. mq will skip foo.patch (because
3737 it has a negative match) but push bar.patch (because it has a
3737 it has a negative match) but push bar.patch (because it has a
3738 positive match).
3738 positive match).
3739
3739
3740 With no arguments, prints the currently active guards.
3740 With no arguments, prints the currently active guards.
3741 With one argument, sets the active guard.
3741 With one argument, sets the active guard.
3742
3742
3743 Use -n/--none to deactivate guards (no other arguments needed).
3743 Use -n/--none to deactivate guards (no other arguments needed).
3744 When no guards are active, patches with positive guards are
3744 When no guards are active, patches with positive guards are
3745 skipped and patches with negative guards are pushed.
3745 skipped and patches with negative guards are pushed.
3746
3746
3747 qselect can change the guards on applied patches. It does not pop
3747 qselect can change the guards on applied patches. It does not pop
3748 guarded patches by default. Use --pop to pop back to the last
3748 guarded patches by default. Use --pop to pop back to the last
3749 applied patch that is not guarded. Use --reapply (which implies
3749 applied patch that is not guarded. Use --reapply (which implies
3750 --pop) to push back to the current patch afterwards, but skip
3750 --pop) to push back to the current patch afterwards, but skip
3751 guarded patches.
3751 guarded patches.
3752
3752
3753 Use -s/--series to print a list of all guards in the series file
3753 Use -s/--series to print a list of all guards in the series file
3754 (no other arguments needed). Use -v for more information.
3754 (no other arguments needed). Use -v for more information.
3755
3755
3756 Returns 0 on success."""
3756 Returns 0 on success."""
3757
3757
3758 q = repo.mq
3758 q = repo.mq
3759 opts = pycompat.byteskwargs(opts)
3759 opts = pycompat.byteskwargs(opts)
3760 guards = q.active()
3760 guards = q.active()
3761 pushable = lambda i: q.pushable(q.applied[i].name)[0]
3761 pushable = lambda i: q.pushable(q.applied[i].name)[0]
3762 if args or opts.get(b'none'):
3762 if args or opts.get(b'none'):
3763 old_unapplied = q.unapplied(repo)
3763 old_unapplied = q.unapplied(repo)
3764 old_guarded = [i for i in range(len(q.applied)) if not pushable(i)]
3764 old_guarded = [i for i in range(len(q.applied)) if not pushable(i)]
3765 q.setactive(args)
3765 q.setactive(args)
3766 q.savedirty()
3766 q.savedirty()
3767 if not args:
3767 if not args:
3768 ui.status(_(b'guards deactivated\n'))
3768 ui.status(_(b'guards deactivated\n'))
3769 if not opts.get(b'pop') and not opts.get(b'reapply'):
3769 if not opts.get(b'pop') and not opts.get(b'reapply'):
3770 unapplied = q.unapplied(repo)
3770 unapplied = q.unapplied(repo)
3771 guarded = [i for i in range(len(q.applied)) if not pushable(i)]
3771 guarded = [i for i in range(len(q.applied)) if not pushable(i)]
3772 if len(unapplied) != len(old_unapplied):
3772 if len(unapplied) != len(old_unapplied):
3773 ui.status(
3773 ui.status(
3774 _(
3774 _(
3775 b'number of unguarded, unapplied patches has '
3775 b'number of unguarded, unapplied patches has '
3776 b'changed from %d to %d\n'
3776 b'changed from %d to %d\n'
3777 )
3777 )
3778 % (len(old_unapplied), len(unapplied))
3778 % (len(old_unapplied), len(unapplied))
3779 )
3779 )
3780 if len(guarded) != len(old_guarded):
3780 if len(guarded) != len(old_guarded):
3781 ui.status(
3781 ui.status(
3782 _(
3782 _(
3783 b'number of guarded, applied patches has changed '
3783 b'number of guarded, applied patches has changed '
3784 b'from %d to %d\n'
3784 b'from %d to %d\n'
3785 )
3785 )
3786 % (len(old_guarded), len(guarded))
3786 % (len(old_guarded), len(guarded))
3787 )
3787 )
3788 elif opts.get(b'series'):
3788 elif opts.get(b'series'):
3789 guards = {}
3789 guards = {}
3790 noguards = 0
3790 noguards = 0
3791 for gs in q.seriesguards:
3791 for gs in q.seriesguards:
3792 if not gs:
3792 if not gs:
3793 noguards += 1
3793 noguards += 1
3794 for g in gs:
3794 for g in gs:
3795 guards.setdefault(g, 0)
3795 guards.setdefault(g, 0)
3796 guards[g] += 1
3796 guards[g] += 1
3797 if ui.verbose:
3797 if ui.verbose:
3798 guards[b'NONE'] = noguards
3798 guards[b'NONE'] = noguards
3799 guards = list(guards.items())
3799 guards = list(guards.items())
3800 guards.sort(key=lambda x: x[0][1:])
3800 guards.sort(key=lambda x: x[0][1:])
3801 if guards:
3801 if guards:
3802 ui.note(_(b'guards in series file:\n'))
3802 ui.note(_(b'guards in series file:\n'))
3803 for guard, count in guards:
3803 for guard, count in guards:
3804 ui.note(b'%2d ' % count)
3804 ui.note(b'%2d ' % count)
3805 ui.write(guard, b'\n')
3805 ui.write(guard, b'\n')
3806 else:
3806 else:
3807 ui.note(_(b'no guards in series file\n'))
3807 ui.note(_(b'no guards in series file\n'))
3808 else:
3808 else:
3809 if guards:
3809 if guards:
3810 ui.note(_(b'active guards:\n'))
3810 ui.note(_(b'active guards:\n'))
3811 for g in guards:
3811 for g in guards:
3812 ui.write(g, b'\n')
3812 ui.write(g, b'\n')
3813 else:
3813 else:
3814 ui.write(_(b'no active guards\n'))
3814 ui.write(_(b'no active guards\n'))
3815 reapply = opts.get(b'reapply') and q.applied and q.applied[-1].name
3815 reapply = opts.get(b'reapply') and q.applied and q.applied[-1].name
3816 popped = False
3816 popped = False
3817 if opts.get(b'pop') or opts.get(b'reapply'):
3817 if opts.get(b'pop') or opts.get(b'reapply'):
3818 for i in range(len(q.applied)):
3818 for i in range(len(q.applied)):
3819 if not pushable(i):
3819 if not pushable(i):
3820 ui.status(_(b'popping guarded patches\n'))
3820 ui.status(_(b'popping guarded patches\n'))
3821 popped = True
3821 popped = True
3822 if i == 0:
3822 if i == 0:
3823 q.pop(repo, all=True)
3823 q.pop(repo, all=True)
3824 else:
3824 else:
3825 q.pop(repo, q.applied[i - 1].name)
3825 q.pop(repo, q.applied[i - 1].name)
3826 break
3826 break
3827 if popped:
3827 if popped:
3828 try:
3828 try:
3829 if reapply:
3829 if reapply:
3830 ui.status(_(b'reapplying unguarded patches\n'))
3830 ui.status(_(b'reapplying unguarded patches\n'))
3831 q.push(repo, reapply)
3831 q.push(repo, reapply)
3832 finally:
3832 finally:
3833 q.savedirty()
3833 q.savedirty()
3834
3834
3835
3835
3836 @command(
3836 @command(
3837 b"qfinish",
3837 b"qfinish",
3838 [(b'a', b'applied', None, _(b'finish all applied changesets'))],
3838 [(b'a', b'applied', None, _(b'finish all applied changesets'))],
3839 _(b'hg qfinish [-a] [REV]...'),
3839 _(b'hg qfinish [-a] [REV]...'),
3840 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3840 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3841 )
3841 )
3842 def finish(ui, repo, *revrange, **opts):
3842 def finish(ui, repo, *revrange, **opts):
3843 """move applied patches into repository history
3843 """move applied patches into repository history
3844
3844
3845 Finishes the specified revisions (corresponding to applied
3845 Finishes the specified revisions (corresponding to applied
3846 patches) by moving them out of mq control into regular repository
3846 patches) by moving them out of mq control into regular repository
3847 history.
3847 history.
3848
3848
3849 Accepts a revision range or the -a/--applied option. If --applied
3849 Accepts a revision range or the -a/--applied option. If --applied
3850 is specified, all applied mq revisions are removed from mq
3850 is specified, all applied mq revisions are removed from mq
3851 control. Otherwise, the given revisions must be at the base of the
3851 control. Otherwise, the given revisions must be at the base of the
3852 stack of applied patches.
3852 stack of applied patches.
3853
3853
3854 This can be especially useful if your changes have been applied to
3854 This can be especially useful if your changes have been applied to
3855 an upstream repository, or if you are about to push your changes
3855 an upstream repository, or if you are about to push your changes
3856 to upstream.
3856 to upstream.
3857
3857
3858 Returns 0 on success.
3858 Returns 0 on success.
3859 """
3859 """
3860 if not opts.get('applied') and not revrange:
3860 if not opts.get('applied') and not revrange:
3861 raise error.Abort(_(b'no revisions specified'))
3861 raise error.Abort(_(b'no revisions specified'))
3862 elif opts.get('applied'):
3862 elif opts.get('applied'):
3863 revrange = (b'qbase::qtip',) + revrange
3863 revrange = (b'qbase::qtip',) + revrange
3864
3864
3865 q = repo.mq
3865 q = repo.mq
3866 if not q.applied:
3866 if not q.applied:
3867 ui.status(_(b'no patches applied\n'))
3867 ui.status(_(b'no patches applied\n'))
3868 return 0
3868 return 0
3869
3869
3870 revs = logcmdutil.revrange(repo, revrange)
3870 revs = logcmdutil.revrange(repo, revrange)
3871 if repo[b'.'].rev() in revs and repo[None].files():
3871 if repo[b'.'].rev() in revs and repo[None].files():
3872 ui.warn(_(b'warning: uncommitted changes in the working directory\n'))
3872 ui.warn(_(b'warning: uncommitted changes in the working directory\n'))
3873 # queue.finish may changes phases but leave the responsibility to lock the
3873 # queue.finish may changes phases but leave the responsibility to lock the
3874 # repo to the caller to avoid deadlock with wlock. This command code is
3874 # repo to the caller to avoid deadlock with wlock. This command code is
3875 # responsibility for this locking.
3875 # responsibility for this locking.
3876 with repo.lock():
3876 with repo.lock():
3877 q.finish(repo, revs)
3877 q.finish(repo, revs)
3878 q.savedirty()
3878 q.savedirty()
3879 return 0
3879 return 0
3880
3880
3881
3881
3882 @command(
3882 @command(
3883 b"qqueue",
3883 b"qqueue",
3884 [
3884 [
3885 (b'l', b'list', False, _(b'list all available queues')),
3885 (b'l', b'list', False, _(b'list all available queues')),
3886 (b'', b'active', False, _(b'print name of active queue')),
3886 (b'', b'active', False, _(b'print name of active queue')),
3887 (b'c', b'create', False, _(b'create new queue')),
3887 (b'c', b'create', False, _(b'create new queue')),
3888 (b'', b'rename', False, _(b'rename active queue')),
3888 (b'', b'rename', False, _(b'rename active queue')),
3889 (b'', b'delete', False, _(b'delete reference to queue')),
3889 (b'', b'delete', False, _(b'delete reference to queue')),
3890 (b'', b'purge', False, _(b'delete queue, and remove patch dir')),
3890 (b'', b'purge', False, _(b'delete queue, and remove patch dir')),
3891 ],
3891 ],
3892 _(b'[OPTION] [QUEUE]'),
3892 _(b'[OPTION] [QUEUE]'),
3893 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3893 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3894 )
3894 )
3895 def qqueue(ui, repo, name=None, **opts):
3895 def qqueue(ui, repo, name=None, **opts):
3896 """manage multiple patch queues
3896 """manage multiple patch queues
3897
3897
3898 Supports switching between different patch queues, as well as creating
3898 Supports switching between different patch queues, as well as creating
3899 new patch queues and deleting existing ones.
3899 new patch queues and deleting existing ones.
3900
3900
3901 Omitting a queue name or specifying -l/--list will show you the registered
3901 Omitting a queue name or specifying -l/--list will show you the registered
3902 queues - by default the "normal" patches queue is registered. The currently
3902 queues - by default the "normal" patches queue is registered. The currently
3903 active queue will be marked with "(active)". Specifying --active will print
3903 active queue will be marked with "(active)". Specifying --active will print
3904 only the name of the active queue.
3904 only the name of the active queue.
3905
3905
3906 To create a new queue, use -c/--create. The queue is automatically made
3906 To create a new queue, use -c/--create. The queue is automatically made
3907 active, except in the case where there are applied patches from the
3907 active, except in the case where there are applied patches from the
3908 currently active queue in the repository. Then the queue will only be
3908 currently active queue in the repository. Then the queue will only be
3909 created and switching will fail.
3909 created and switching will fail.
3910
3910
3911 To delete an existing queue, use --delete. You cannot delete the currently
3911 To delete an existing queue, use --delete. You cannot delete the currently
3912 active queue.
3912 active queue.
3913
3913
3914 Returns 0 on success.
3914 Returns 0 on success.
3915 """
3915 """
3916 q = repo.mq
3916 q = repo.mq
3917 _defaultqueue = b'patches'
3917 _defaultqueue = b'patches'
3918 _allqueues = b'patches.queues'
3918 _allqueues = b'patches.queues'
3919 _activequeue = b'patches.queue'
3919 _activequeue = b'patches.queue'
3920
3920
3921 def _getcurrent():
3921 def _getcurrent():
3922 cur = os.path.basename(q.path)
3922 cur = os.path.basename(q.path)
3923 if cur.startswith(b'patches-'):
3923 if cur.startswith(b'patches-'):
3924 cur = cur[8:]
3924 cur = cur[8:]
3925 return cur
3925 return cur
3926
3926
3927 def _noqueues():
3927 def _noqueues():
3928 try:
3928 try:
3929 fh = repo.vfs(_allqueues, b'r')
3929 fh = repo.vfs(_allqueues, b'r')
3930 fh.close()
3930 fh.close()
3931 except IOError:
3931 except IOError:
3932 return True
3932 return True
3933
3933
3934 return False
3934 return False
3935
3935
3936 def _getqueues():
3936 def _getqueues():
3937 current = _getcurrent()
3937 current = _getcurrent()
3938
3938
3939 try:
3939 try:
3940 fh = repo.vfs(_allqueues, b'r')
3940 fh = repo.vfs(_allqueues, b'r')
3941 queues = [queue.strip() for queue in fh if queue.strip()]
3941 queues = [queue.strip() for queue in fh if queue.strip()]
3942 fh.close()
3942 fh.close()
3943 if current not in queues:
3943 if current not in queues:
3944 queues.append(current)
3944 queues.append(current)
3945 except IOError:
3945 except IOError:
3946 queues = [_defaultqueue]
3946 queues = [_defaultqueue]
3947
3947
3948 return sorted(queues)
3948 return sorted(queues)
3949
3949
3950 def _setactive(name):
3950 def _setactive(name):
3951 if q.applied:
3951 if q.applied:
3952 raise error.Abort(
3952 raise error.Abort(
3953 _(
3953 _(
3954 b'new queue created, but cannot make active '
3954 b'new queue created, but cannot make active '
3955 b'as patches are applied'
3955 b'as patches are applied'
3956 )
3956 )
3957 )
3957 )
3958 _setactivenocheck(name)
3958 _setactivenocheck(name)
3959
3959
3960 def _setactivenocheck(name):
3960 def _setactivenocheck(name):
3961 fh = repo.vfs(_activequeue, b'w')
3961 fh = repo.vfs(_activequeue, b'w')
3962 if name != b'patches':
3962 if name != b'patches':
3963 fh.write(name)
3963 fh.write(name)
3964 fh.close()
3964 fh.close()
3965
3965
3966 def _addqueue(name):
3966 def _addqueue(name):
3967 fh = repo.vfs(_allqueues, b'a')
3967 fh = repo.vfs(_allqueues, b'a')
3968 fh.write(b'%s\n' % (name,))
3968 fh.write(b'%s\n' % (name,))
3969 fh.close()
3969 fh.close()
3970
3970
3971 def _queuedir(name):
3971 def _queuedir(name):
3972 if name == b'patches':
3972 if name == b'patches':
3973 return repo.vfs.join(b'patches')
3973 return repo.vfs.join(b'patches')
3974 else:
3974 else:
3975 return repo.vfs.join(b'patches-' + name)
3975 return repo.vfs.join(b'patches-' + name)
3976
3976
3977 def _validname(name):
3977 def _validname(name):
3978 for n in name:
3978 for n in name:
3979 if n in b':\\/.':
3979 if n in b':\\/.':
3980 return False
3980 return False
3981 return True
3981 return True
3982
3982
3983 def _delete(name):
3983 def _delete(name):
3984 if name not in existing:
3984 if name not in existing:
3985 raise error.Abort(_(b'cannot delete queue that does not exist'))
3985 raise error.Abort(_(b'cannot delete queue that does not exist'))
3986
3986
3987 current = _getcurrent()
3987 current = _getcurrent()
3988
3988
3989 if name == current:
3989 if name == current:
3990 raise error.Abort(_(b'cannot delete currently active queue'))
3990 raise error.Abort(_(b'cannot delete currently active queue'))
3991
3991
3992 fh = repo.vfs(b'patches.queues.new', b'w')
3992 fh = repo.vfs(b'patches.queues.new', b'w')
3993 for queue in existing:
3993 for queue in existing:
3994 if queue == name:
3994 if queue == name:
3995 continue
3995 continue
3996 fh.write(b'%s\n' % (queue,))
3996 fh.write(b'%s\n' % (queue,))
3997 fh.close()
3997 fh.close()
3998 repo.vfs.rename(b'patches.queues.new', _allqueues)
3998 repo.vfs.rename(b'patches.queues.new', _allqueues)
3999
3999
4000 opts = pycompat.byteskwargs(opts)
4000 opts = pycompat.byteskwargs(opts)
4001 if not name or opts.get(b'list') or opts.get(b'active'):
4001 if not name or opts.get(b'list') or opts.get(b'active'):
4002 current = _getcurrent()
4002 current = _getcurrent()
4003 if opts.get(b'active'):
4003 if opts.get(b'active'):
4004 ui.write(b'%s\n' % (current,))
4004 ui.write(b'%s\n' % (current,))
4005 return
4005 return
4006 for queue in _getqueues():
4006 for queue in _getqueues():
4007 ui.write(b'%s' % (queue,))
4007 ui.write(b'%s' % (queue,))
4008 if queue == current and not ui.quiet:
4008 if queue == current and not ui.quiet:
4009 ui.write(_(b' (active)\n'))
4009 ui.write(_(b' (active)\n'))
4010 else:
4010 else:
4011 ui.write(b'\n')
4011 ui.write(b'\n')
4012 return
4012 return
4013
4013
4014 if not _validname(name):
4014 if not _validname(name):
4015 raise error.Abort(
4015 raise error.Abort(
4016 _(b'invalid queue name, may not contain the characters ":\\/."')
4016 _(b'invalid queue name, may not contain the characters ":\\/."')
4017 )
4017 )
4018
4018
4019 with repo.wlock():
4019 with repo.wlock():
4020 existing = _getqueues()
4020 existing = _getqueues()
4021
4021
4022 if opts.get(b'create'):
4022 if opts.get(b'create'):
4023 if name in existing:
4023 if name in existing:
4024 raise error.Abort(_(b'queue "%s" already exists') % name)
4024 raise error.Abort(_(b'queue "%s" already exists') % name)
4025 if _noqueues():
4025 if _noqueues():
4026 _addqueue(_defaultqueue)
4026 _addqueue(_defaultqueue)
4027 _addqueue(name)
4027 _addqueue(name)
4028 _setactive(name)
4028 _setactive(name)
4029 elif opts.get(b'rename'):
4029 elif opts.get(b'rename'):
4030 current = _getcurrent()
4030 current = _getcurrent()
4031 if name == current:
4031 if name == current:
4032 raise error.Abort(
4032 raise error.Abort(
4033 _(b'can\'t rename "%s" to its current name') % name
4033 _(b'can\'t rename "%s" to its current name') % name
4034 )
4034 )
4035 if name in existing:
4035 if name in existing:
4036 raise error.Abort(_(b'queue "%s" already exists') % name)
4036 raise error.Abort(_(b'queue "%s" already exists') % name)
4037
4037
4038 olddir = _queuedir(current)
4038 olddir = _queuedir(current)
4039 newdir = _queuedir(name)
4039 newdir = _queuedir(name)
4040
4040
4041 if os.path.exists(newdir):
4041 if os.path.exists(newdir):
4042 raise error.Abort(
4042 raise error.Abort(
4043 _(b'non-queue directory "%s" already exists') % newdir
4043 _(b'non-queue directory "%s" already exists') % newdir
4044 )
4044 )
4045
4045
4046 fh = repo.vfs(b'patches.queues.new', b'w')
4046 fh = repo.vfs(b'patches.queues.new', b'w')
4047 for queue in existing:
4047 for queue in existing:
4048 if queue == current:
4048 if queue == current:
4049 fh.write(b'%s\n' % (name,))
4049 fh.write(b'%s\n' % (name,))
4050 if os.path.exists(olddir):
4050 if os.path.exists(olddir):
4051 util.rename(olddir, newdir)
4051 util.rename(olddir, newdir)
4052 else:
4052 else:
4053 fh.write(b'%s\n' % (queue,))
4053 fh.write(b'%s\n' % (queue,))
4054 fh.close()
4054 fh.close()
4055 repo.vfs.rename(b'patches.queues.new', _allqueues)
4055 repo.vfs.rename(b'patches.queues.new', _allqueues)
4056 _setactivenocheck(name)
4056 _setactivenocheck(name)
4057 elif opts.get(b'delete'):
4057 elif opts.get(b'delete'):
4058 _delete(name)
4058 _delete(name)
4059 elif opts.get(b'purge'):
4059 elif opts.get(b'purge'):
4060 if name in existing:
4060 if name in existing:
4061 _delete(name)
4061 _delete(name)
4062 qdir = _queuedir(name)
4062 qdir = _queuedir(name)
4063 if os.path.exists(qdir):
4063 if os.path.exists(qdir):
4064 shutil.rmtree(qdir)
4064 shutil.rmtree(qdir)
4065 else:
4065 else:
4066 if name not in existing:
4066 if name not in existing:
4067 raise error.Abort(_(b'use --create to create a new queue'))
4067 raise error.Abort(_(b'use --create to create a new queue'))
4068 _setactive(name)
4068 _setactive(name)
4069
4069
4070
4070
4071 def mqphasedefaults(repo, roots):
4071 def mqphasedefaults(repo, roots):
4072 """callback used to set mq changeset as secret when no phase data exists"""
4072 """callback used to set mq changeset as secret when no phase data exists"""
4073 if repo.mq.applied:
4073 if repo.mq.applied:
4074 if repo.ui.configbool(b'mq', b'secret'):
4074 if repo.ui.configbool(b'mq', b'secret'):
4075 mqphase = phases.secret
4075 mqphase = phases.secret
4076 else:
4076 else:
4077 mqphase = phases.draft
4077 mqphase = phases.draft
4078 qbase = repo[repo.mq.applied[0].node]
4078 qbase = repo[repo.mq.applied[0].node]
4079 roots[mqphase].add(qbase.node())
4079 roots[mqphase].add(qbase.node())
4080 return roots
4080 return roots
4081
4081
4082
4082
4083 def reposetup(ui, repo):
4083 def reposetup(ui, repo):
4084 class mqrepo(repo.__class__):
4084 class mqrepo(repo.__class__):
4085 @localrepo.unfilteredpropertycache
4085 @localrepo.unfilteredpropertycache
4086 def mq(self):
4086 def mq(self):
4087 return queue(self.ui, self.baseui, self.path)
4087 return queue(self.ui, self.baseui, self.path)
4088
4088
4089 def invalidateall(self):
4089 def invalidateall(self):
4090 super(mqrepo, self).invalidateall()
4090 super(mqrepo, self).invalidateall()
4091 if localrepo.hasunfilteredcache(self, 'mq'):
4091 if localrepo.hasunfilteredcache(self, 'mq'):
4092 # recreate mq in case queue path was changed
4092 # recreate mq in case queue path was changed
4093 delattr(self.unfiltered(), 'mq')
4093 delattr(self.unfiltered(), 'mq')
4094
4094
4095 def abortifwdirpatched(self, errmsg, force=False):
4095 def abortifwdirpatched(self, errmsg, force=False):
4096 if self.mq.applied and self.mq.checkapplied and not force:
4096 if self.mq.applied and self.mq.checkapplied and not force:
4097 parents = self.dirstate.parents()
4097 parents = self.dirstate.parents()
4098 patches = [s.node for s in self.mq.applied]
4098 patches = [s.node for s in self.mq.applied]
4099 if any(p in patches for p in parents):
4099 if any(p in patches for p in parents):
4100 raise error.Abort(errmsg)
4100 raise error.Abort(errmsg)
4101
4101
4102 def commit(
4102 def commit(
4103 self,
4103 self,
4104 text=b"",
4104 text=b"",
4105 user=None,
4105 user=None,
4106 date=None,
4106 date=None,
4107 match=None,
4107 match=None,
4108 force=False,
4108 force=False,
4109 editor=False,
4109 editor=False,
4110 extra=None,
4110 extra=None,
4111 ):
4111 ):
4112 if extra is None:
4112 if extra is None:
4113 extra = {}
4113 extra = {}
4114 self.abortifwdirpatched(
4114 self.abortifwdirpatched(
4115 _(b'cannot commit over an applied mq patch'), force
4115 _(b'cannot commit over an applied mq patch'), force
4116 )
4116 )
4117
4117
4118 return super(mqrepo, self).commit(
4118 return super(mqrepo, self).commit(
4119 text, user, date, match, force, editor, extra
4119 text, user, date, match, force, editor, extra
4120 )
4120 )
4121
4121
4122 def checkpush(self, pushop):
4122 def checkpush(self, pushop):
4123 if self.mq.applied and self.mq.checkapplied and not pushop.force:
4123 if self.mq.applied and self.mq.checkapplied and not pushop.force:
4124 outapplied = [e.node for e in self.mq.applied]
4124 outapplied = [e.node for e in self.mq.applied]
4125 if pushop.revs:
4125 if pushop.revs:
4126 # Assume applied patches have no non-patch descendants and
4126 # Assume applied patches have no non-patch descendants and
4127 # are not on remote already. Filtering any changeset not
4127 # are not on remote already. Filtering any changeset not
4128 # pushed.
4128 # pushed.
4129 heads = set(pushop.revs)
4129 heads = set(pushop.revs)
4130 for node in reversed(outapplied):
4130 for node in reversed(outapplied):
4131 if node in heads:
4131 if node in heads:
4132 break
4132 break
4133 else:
4133 else:
4134 outapplied.pop()
4134 outapplied.pop()
4135 # looking for pushed and shared changeset
4135 # looking for pushed and shared changeset
4136 for node in outapplied:
4136 for node in outapplied:
4137 if self[node].phase() < phases.secret:
4137 if self[node].phase() < phases.secret:
4138 raise error.Abort(_(b'source has mq patches applied'))
4138 raise error.Abort(_(b'source has mq patches applied'))
4139 # no non-secret patches pushed
4139 # no non-secret patches pushed
4140 super(mqrepo, self).checkpush(pushop)
4140 super(mqrepo, self).checkpush(pushop)
4141
4141
4142 def _findtags(self):
4142 def _findtags(self):
4143 '''augment tags from base class with patch tags'''
4143 '''augment tags from base class with patch tags'''
4144 result = super(mqrepo, self)._findtags()
4144 result = super(mqrepo, self)._findtags()
4145
4145
4146 q = self.mq
4146 q = self.mq
4147 if not q.applied:
4147 if not q.applied:
4148 return result
4148 return result
4149
4149
4150 mqtags = [(patch.node, patch.name) for patch in q.applied]
4150 mqtags = [(patch.node, patch.name) for patch in q.applied]
4151
4151
4152 try:
4152 try:
4153 # for now ignore filtering business
4153 # for now ignore filtering business
4154 self.unfiltered().changelog.rev(mqtags[-1][0])
4154 self.unfiltered().changelog.rev(mqtags[-1][0])
4155 except error.LookupError:
4155 except error.LookupError:
4156 self.ui.warn(
4156 self.ui.warn(
4157 _(b'mq status file refers to unknown node %s\n')
4157 _(b'mq status file refers to unknown node %s\n')
4158 % short(mqtags[-1][0])
4158 % short(mqtags[-1][0])
4159 )
4159 )
4160 return result
4160 return result
4161
4161
4162 # do not add fake tags for filtered revisions
4162 # do not add fake tags for filtered revisions
4163 included = self.changelog.hasnode
4163 included = self.changelog.hasnode
4164 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
4164 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
4165 if not mqtags:
4165 if not mqtags:
4166 return result
4166 return result
4167
4167
4168 mqtags.append((mqtags[-1][0], b'qtip'))
4168 mqtags.append((mqtags[-1][0], b'qtip'))
4169 mqtags.append((mqtags[0][0], b'qbase'))
4169 mqtags.append((mqtags[0][0], b'qbase'))
4170 mqtags.append((self.changelog.parents(mqtags[0][0])[0], b'qparent'))
4170 mqtags.append((self.changelog.parents(mqtags[0][0])[0], b'qparent'))
4171 tags = result[0]
4171 tags = result[0]
4172 for patch in mqtags:
4172 for patch in mqtags:
4173 if patch[1] in tags:
4173 if patch[1] in tags:
4174 self.ui.warn(
4174 self.ui.warn(
4175 _(b'tag %s overrides mq patch of the same name\n')
4175 _(b'tag %s overrides mq patch of the same name\n')
4176 % patch[1]
4176 % patch[1]
4177 )
4177 )
4178 else:
4178 else:
4179 tags[patch[1]] = patch[0]
4179 tags[patch[1]] = patch[0]
4180
4180
4181 return result
4181 return result
4182
4182
4183 if repo.local():
4183 if repo.local():
4184 repo.__class__ = mqrepo
4184 repo.__class__ = mqrepo
4185
4185
4186 repo._phasedefaults.append(mqphasedefaults)
4186 repo._phasedefaults.append(mqphasedefaults)
4187
4187
4188
4188
4189 def mqimport(orig, ui, repo, *args, **kwargs):
4189 def mqimport(orig, ui, repo, *args, **kwargs):
4190 if util.safehasattr(repo, b'abortifwdirpatched') and not kwargs.get(
4190 if util.safehasattr(repo, b'abortifwdirpatched') and not kwargs.get(
4191 'no_commit', False
4191 'no_commit', False
4192 ):
4192 ):
4193 repo.abortifwdirpatched(
4193 repo.abortifwdirpatched(
4194 _(b'cannot import over an applied patch'), kwargs.get('force')
4194 _(b'cannot import over an applied patch'), kwargs.get('force')
4195 )
4195 )
4196 return orig(ui, repo, *args, **kwargs)
4196 return orig(ui, repo, *args, **kwargs)
4197
4197
4198
4198
4199 def mqinit(orig, ui, *args, **kwargs):
4199 def mqinit(orig, ui, *args, **kwargs):
4200 mq = kwargs.pop('mq', None)
4200 mq = kwargs.pop('mq', None)
4201
4201
4202 if not mq:
4202 if not mq:
4203 return orig(ui, *args, **kwargs)
4203 return orig(ui, *args, **kwargs)
4204
4204
4205 if args:
4205 if args:
4206 repopath = args[0]
4206 repopath = args[0]
4207 if not hg.islocal(repopath):
4207 if not hg.islocal(repopath):
4208 raise error.Abort(
4208 raise error.Abort(
4209 _(b'only a local queue repository may be initialized')
4209 _(b'only a local queue repository may be initialized')
4210 )
4210 )
4211 else:
4211 else:
4212 repopath = cmdutil.findrepo(encoding.getcwd())
4212 repopath = cmdutil.findrepo(encoding.getcwd())
4213 if not repopath:
4213 if not repopath:
4214 raise error.Abort(
4214 raise error.Abort(
4215 _(b'there is no Mercurial repository here (.hg not found)')
4215 _(b'there is no Mercurial repository here (.hg not found)')
4216 )
4216 )
4217 repo = hg.repository(ui, repopath)
4217 repo = hg.repository(ui, repopath)
4218 return qinit(ui, repo, True)
4218 return qinit(ui, repo, True)
4219
4219
4220
4220
4221 def mqcommand(orig, ui, repo, *args, **kwargs):
4221 def mqcommand(orig, ui, repo, *args, **kwargs):
4222 """Add --mq option to operate on patch repository instead of main"""
4222 """Add --mq option to operate on patch repository instead of main"""
4223
4223
4224 # some commands do not like getting unknown options
4224 # some commands do not like getting unknown options
4225 mq = kwargs.pop('mq', None)
4225 mq = kwargs.pop('mq', None)
4226
4226
4227 if not mq:
4227 if not mq:
4228 return orig(ui, repo, *args, **kwargs)
4228 return orig(ui, repo, *args, **kwargs)
4229
4229
4230 q = repo.mq
4230 q = repo.mq
4231 r = q.qrepo()
4231 r = q.qrepo()
4232 if not r:
4232 if not r:
4233 raise error.Abort(_(b'no queue repository'))
4233 raise error.Abort(_(b'no queue repository'))
4234 return orig(r.ui, r, *args, **kwargs)
4234 return orig(r.ui, r, *args, **kwargs)
4235
4235
4236
4236
4237 def summaryhook(ui, repo):
4237 def summaryhook(ui, repo):
4238 q = repo.mq
4238 q = repo.mq
4239 m = []
4239 m = []
4240 a, u = len(q.applied), len(q.unapplied(repo))
4240 a, u = len(q.applied), len(q.unapplied(repo))
4241 if a:
4241 if a:
4242 m.append(ui.label(_(b"%d applied"), b'qseries.applied') % a)
4242 m.append(ui.label(_(b"%d applied"), b'qseries.applied') % a)
4243 if u:
4243 if u:
4244 m.append(ui.label(_(b"%d unapplied"), b'qseries.unapplied') % u)
4244 m.append(ui.label(_(b"%d unapplied"), b'qseries.unapplied') % u)
4245 if m:
4245 if m:
4246 # i18n: column positioning for "hg summary"
4246 # i18n: column positioning for "hg summary"
4247 ui.write(_(b"mq: %s\n") % b', '.join(m))
4247 ui.write(_(b"mq: %s\n") % b', '.join(m))
4248 else:
4248 else:
4249 # i18n: column positioning for "hg summary"
4249 # i18n: column positioning for "hg summary"
4250 ui.note(_(b"mq: (empty queue)\n"))
4250 ui.note(_(b"mq: (empty queue)\n"))
4251
4251
4252
4252
4253 revsetpredicate = registrar.revsetpredicate()
4253 revsetpredicate = registrar.revsetpredicate()
4254
4254
4255
4255
4256 @revsetpredicate(b'mq()')
4256 @revsetpredicate(b'mq()')
4257 def revsetmq(repo, subset, x):
4257 def revsetmq(repo, subset, x):
4258 """Changesets managed by MQ."""
4258 """Changesets managed by MQ."""
4259 revsetlang.getargs(x, 0, 0, _(b"mq takes no arguments"))
4259 revsetlang.getargs(x, 0, 0, _(b"mq takes no arguments"))
4260 applied = {repo[r.node].rev() for r in repo.mq.applied}
4260 applied = {repo[r.node].rev() for r in repo.mq.applied}
4261 return smartset.baseset([r for r in subset if r in applied])
4261 return smartset.baseset([r for r in subset if r in applied])
4262
4262
4263
4263
4264 # tell hggettext to extract docstrings from these functions:
4264 # tell hggettext to extract docstrings from these functions:
4265 i18nfunctions = [revsetmq]
4265 i18nfunctions = [revsetmq]
4266
4266
4267
4267
4268 def extsetup(ui):
4268 def extsetup(ui):
4269 # Ensure mq wrappers are called first, regardless of extension load order by
4269 # Ensure mq wrappers are called first, regardless of extension load order by
4270 # NOT wrapping in uisetup() and instead deferring to init stage two here.
4270 # NOT wrapping in uisetup() and instead deferring to init stage two here.
4271 mqopt = [(b'', b'mq', None, _(b"operate on patch repository"))]
4271 mqopt = [(b'', b'mq', None, _(b"operate on patch repository"))]
4272
4272
4273 extensions.wrapcommand(commands.table, b'import', mqimport)
4273 extensions.wrapcommand(commands.table, b'import', mqimport)
4274 cmdutil.summaryhooks.add(b'mq', summaryhook)
4274 cmdutil.summaryhooks.add(b'mq', summaryhook)
4275
4275
4276 entry = extensions.wrapcommand(commands.table, b'init', mqinit)
4276 entry = extensions.wrapcommand(commands.table, b'init', mqinit)
4277 entry[1].extend(mqopt)
4277 entry[1].extend(mqopt)
4278
4278
4279 def dotable(cmdtable):
4279 def dotable(cmdtable):
4280 for cmd, entry in cmdtable.items():
4280 for cmd, entry in cmdtable.items():
4281 cmd = cmdutil.parsealiases(cmd)[0]
4281 cmd = cmdutil.parsealiases(cmd)[0]
4282 func = entry[0]
4282 func = entry[0]
4283 if func.norepo:
4283 if func.norepo:
4284 continue
4284 continue
4285 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
4285 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
4286 entry[1].extend(mqopt)
4286 entry[1].extend(mqopt)
4287
4287
4288 dotable(commands.table)
4288 dotable(commands.table)
4289
4289
4290 thismodule = sys.modules["hgext.mq"]
4290 thismodule = sys.modules["hgext.mq"]
4291 for extname, extmodule in extensions.extensions():
4291 for extname, extmodule in extensions.extensions():
4292 if extmodule != thismodule:
4292 if extmodule != thismodule:
4293 dotable(getattr(extmodule, 'cmdtable', {}))
4293 dotable(getattr(extmodule, 'cmdtable', {}))
4294
4294
4295
4295
4296 colortable = {
4296 colortable = {
4297 b'qguard.negative': b'red',
4297 b'qguard.negative': b'red',
4298 b'qguard.positive': b'yellow',
4298 b'qguard.positive': b'yellow',
4299 b'qguard.unguarded': b'green',
4299 b'qguard.unguarded': b'green',
4300 b'qseries.applied': b'blue bold underline',
4300 b'qseries.applied': b'blue bold underline',
4301 b'qseries.guarded': b'black bold',
4301 b'qseries.guarded': b'black bold',
4302 b'qseries.missing': b'red bold',
4302 b'qseries.missing': b'red bold',
4303 b'qseries.unapplied': b'black bold',
4303 b'qseries.unapplied': b'black bold',
4304 }
4304 }
General Comments 0
You need to be logged in to leave comments. Login now