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