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