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