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