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