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