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