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