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