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