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