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