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