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