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