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