##// END OF EJS Templates
mq: fix literal blocks in docstrings
Martin Geisler -
r9824:87c92b26 default
parent child Browse files
Show More
@@ -1,2697 +1,2698 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, incorporated herein by reference.
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 prepare repository to work with patches qinit
20 20 create new patch qnew
21 21 import existing patch qimport
22 22
23 23 print patch series qseries
24 24 print applied patches qapplied
25 25
26 26 add known patch to applied stack qpush
27 27 remove patch from applied stack qpop
28 28 refresh contents of top applied patch qrefresh
29 29 '''
30 30
31 31 from mercurial.i18n import _
32 32 from mercurial.node import bin, hex, short, nullid, nullrev
33 33 from mercurial.lock import release
34 34 from mercurial import commands, cmdutil, hg, patch, util
35 35 from mercurial import repair, extensions, url, error
36 36 import os, sys, re, errno
37 37
38 38 commands.norepo += " qclone"
39 39
40 40 # Patch names looks like unix-file names.
41 41 # They must be joinable with queue directory and result in the patch path.
42 42 normname = util.normpath
43 43
44 44 class statusentry(object):
45 45 def __init__(self, rev, name=None):
46 46 if not name:
47 47 fields = rev.split(':', 1)
48 48 if len(fields) == 2:
49 49 self.rev, self.name = fields
50 50 else:
51 51 self.rev, self.name = None, None
52 52 else:
53 53 self.rev, self.name = rev, name
54 54
55 55 def __str__(self):
56 56 return self.rev + ':' + self.name
57 57
58 58 class patchheader(object):
59 59 def __init__(self, pf):
60 60 def eatdiff(lines):
61 61 while lines:
62 62 l = lines[-1]
63 63 if (l.startswith("diff -") or
64 64 l.startswith("Index:") or
65 65 l.startswith("===========")):
66 66 del lines[-1]
67 67 else:
68 68 break
69 69 def eatempty(lines):
70 70 while lines:
71 71 l = lines[-1]
72 72 if re.match('\s*$', l):
73 73 del lines[-1]
74 74 else:
75 75 break
76 76
77 77 message = []
78 78 comments = []
79 79 user = None
80 80 date = None
81 81 format = None
82 82 subject = None
83 83 diffstart = 0
84 84
85 85 for line in file(pf):
86 86 line = line.rstrip()
87 87 if line.startswith('diff --git'):
88 88 diffstart = 2
89 89 break
90 90 if diffstart:
91 91 if line.startswith('+++ '):
92 92 diffstart = 2
93 93 break
94 94 if line.startswith("--- "):
95 95 diffstart = 1
96 96 continue
97 97 elif format == "hgpatch":
98 98 # parse values when importing the result of an hg export
99 99 if line.startswith("# User "):
100 100 user = line[7:]
101 101 elif line.startswith("# Date "):
102 102 date = line[7:]
103 103 elif not line.startswith("# ") and line:
104 104 message.append(line)
105 105 format = None
106 106 elif line == '# HG changeset patch':
107 107 message = []
108 108 format = "hgpatch"
109 109 elif (format != "tagdone" and (line.startswith("Subject: ") or
110 110 line.startswith("subject: "))):
111 111 subject = line[9:]
112 112 format = "tag"
113 113 elif (format != "tagdone" and (line.startswith("From: ") or
114 114 line.startswith("from: "))):
115 115 user = line[6:]
116 116 format = "tag"
117 117 elif format == "tag" and line == "":
118 118 # when looking for tags (subject: from: etc) they
119 119 # end once you find a blank line in the source
120 120 format = "tagdone"
121 121 elif message or line:
122 122 message.append(line)
123 123 comments.append(line)
124 124
125 125 eatdiff(message)
126 126 eatdiff(comments)
127 127 eatempty(message)
128 128 eatempty(comments)
129 129
130 130 # make sure message isn't empty
131 131 if format and format.startswith("tag") and subject:
132 132 message.insert(0, "")
133 133 message.insert(0, subject)
134 134
135 135 self.message = message
136 136 self.comments = comments
137 137 self.user = user
138 138 self.date = date
139 139 self.haspatch = diffstart > 1
140 140
141 141 def setuser(self, user):
142 142 if not self.updateheader(['From: ', '# User '], user):
143 143 try:
144 144 patchheaderat = self.comments.index('# HG changeset patch')
145 145 self.comments.insert(patchheaderat + 1, '# User ' + user)
146 146 except ValueError:
147 147 if self._hasheader(['Date: ']):
148 148 self.comments = ['From: ' + user] + self.comments
149 149 else:
150 150 tmp = ['# HG changeset patch', '# User ' + user, '']
151 151 self.comments = tmp + self.comments
152 152 self.user = user
153 153
154 154 def setdate(self, date):
155 155 if not self.updateheader(['Date: ', '# Date '], date):
156 156 try:
157 157 patchheaderat = self.comments.index('# HG changeset patch')
158 158 self.comments.insert(patchheaderat + 1, '# Date ' + date)
159 159 except ValueError:
160 160 if self._hasheader(['From: ']):
161 161 self.comments = ['Date: ' + date] + self.comments
162 162 else:
163 163 tmp = ['# HG changeset patch', '# Date ' + date, '']
164 164 self.comments = tmp + self.comments
165 165 self.date = date
166 166
167 167 def setmessage(self, message):
168 168 if self.comments:
169 169 self._delmsg()
170 170 self.message = [message]
171 171 self.comments += self.message
172 172
173 173 def updateheader(self, prefixes, new):
174 174 '''Update all references to a field in the patch header.
175 175 Return whether the field is present.'''
176 176 res = False
177 177 for prefix in prefixes:
178 178 for i in xrange(len(self.comments)):
179 179 if self.comments[i].startswith(prefix):
180 180 self.comments[i] = prefix + new
181 181 res = True
182 182 break
183 183 return res
184 184
185 185 def _hasheader(self, prefixes):
186 186 '''Check if a header starts with any of the given prefixes.'''
187 187 for prefix in prefixes:
188 188 for comment in self.comments:
189 189 if comment.startswith(prefix):
190 190 return True
191 191 return False
192 192
193 193 def __str__(self):
194 194 if not self.comments:
195 195 return ''
196 196 return '\n'.join(self.comments) + '\n\n'
197 197
198 198 def _delmsg(self):
199 199 '''Remove existing message, keeping the rest of the comments fields.
200 200 If comments contains 'subject: ', message will prepend
201 201 the field and a blank line.'''
202 202 if self.message:
203 203 subj = 'subject: ' + self.message[0].lower()
204 204 for i in xrange(len(self.comments)):
205 205 if subj == self.comments[i].lower():
206 206 del self.comments[i]
207 207 self.message = self.message[2:]
208 208 break
209 209 ci = 0
210 210 for mi in self.message:
211 211 while mi != self.comments[ci]:
212 212 ci += 1
213 213 del self.comments[ci]
214 214
215 215 class queue(object):
216 216 def __init__(self, ui, path, patchdir=None):
217 217 self.basepath = path
218 218 self.path = patchdir or os.path.join(path, "patches")
219 219 self.opener = util.opener(self.path)
220 220 self.ui = ui
221 221 self.applied_dirty = 0
222 222 self.series_dirty = 0
223 223 self.series_path = "series"
224 224 self.status_path = "status"
225 225 self.guards_path = "guards"
226 226 self.active_guards = None
227 227 self.guards_dirty = False
228 228 self._diffopts = None
229 229
230 230 @util.propertycache
231 231 def applied(self):
232 232 if os.path.exists(self.join(self.status_path)):
233 233 lines = self.opener(self.status_path).read().splitlines()
234 234 return [statusentry(l) for l in lines]
235 235 return []
236 236
237 237 @util.propertycache
238 238 def full_series(self):
239 239 if os.path.exists(self.join(self.series_path)):
240 240 return self.opener(self.series_path).read().splitlines()
241 241 return []
242 242
243 243 @util.propertycache
244 244 def series(self):
245 245 self.parse_series()
246 246 return self.series
247 247
248 248 @util.propertycache
249 249 def series_guards(self):
250 250 self.parse_series()
251 251 return self.series_guards
252 252
253 253 def invalidate(self):
254 254 for a in 'applied full_series series series_guards'.split():
255 255 if a in self.__dict__:
256 256 delattr(self, a)
257 257 self.applied_dirty = 0
258 258 self.series_dirty = 0
259 259 self.guards_dirty = False
260 260 self.active_guards = None
261 261
262 262 def diffopts(self):
263 263 if self._diffopts is None:
264 264 self._diffopts = patch.diffopts(self.ui)
265 265 return self._diffopts
266 266
267 267 def join(self, *p):
268 268 return os.path.join(self.path, *p)
269 269
270 270 def find_series(self, patch):
271 271 pre = re.compile("(\s*)([^#]+)")
272 272 index = 0
273 273 for l in self.full_series:
274 274 m = pre.match(l)
275 275 if m:
276 276 s = m.group(2)
277 277 s = s.rstrip()
278 278 if s == patch:
279 279 return index
280 280 index += 1
281 281 return None
282 282
283 283 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
284 284
285 285 def parse_series(self):
286 286 self.series = []
287 287 self.series_guards = []
288 288 for l in self.full_series:
289 289 h = l.find('#')
290 290 if h == -1:
291 291 patch = l
292 292 comment = ''
293 293 elif h == 0:
294 294 continue
295 295 else:
296 296 patch = l[:h]
297 297 comment = l[h:]
298 298 patch = patch.strip()
299 299 if patch:
300 300 if patch in self.series:
301 301 raise util.Abort(_('%s appears more than once in %s') %
302 302 (patch, self.join(self.series_path)))
303 303 self.series.append(patch)
304 304 self.series_guards.append(self.guard_re.findall(comment))
305 305
306 306 def check_guard(self, guard):
307 307 if not guard:
308 308 return _('guard cannot be an empty string')
309 309 bad_chars = '# \t\r\n\f'
310 310 first = guard[0]
311 311 if first in '-+':
312 312 return (_('guard %r starts with invalid character: %r') %
313 313 (guard, first))
314 314 for c in bad_chars:
315 315 if c in guard:
316 316 return _('invalid character in guard %r: %r') % (guard, c)
317 317
318 318 def set_active(self, guards):
319 319 for guard in guards:
320 320 bad = self.check_guard(guard)
321 321 if bad:
322 322 raise util.Abort(bad)
323 323 guards = sorted(set(guards))
324 324 self.ui.debug('active guards: %s\n' % ' '.join(guards))
325 325 self.active_guards = guards
326 326 self.guards_dirty = True
327 327
328 328 def active(self):
329 329 if self.active_guards is None:
330 330 self.active_guards = []
331 331 try:
332 332 guards = self.opener(self.guards_path).read().split()
333 333 except IOError, err:
334 334 if err.errno != errno.ENOENT: raise
335 335 guards = []
336 336 for i, guard in enumerate(guards):
337 337 bad = self.check_guard(guard)
338 338 if bad:
339 339 self.ui.warn('%s:%d: %s\n' %
340 340 (self.join(self.guards_path), i + 1, bad))
341 341 else:
342 342 self.active_guards.append(guard)
343 343 return self.active_guards
344 344
345 345 def set_guards(self, idx, guards):
346 346 for g in guards:
347 347 if len(g) < 2:
348 348 raise util.Abort(_('guard %r too short') % g)
349 349 if g[0] not in '-+':
350 350 raise util.Abort(_('guard %r starts with invalid char') % g)
351 351 bad = self.check_guard(g[1:])
352 352 if bad:
353 353 raise util.Abort(bad)
354 354 drop = self.guard_re.sub('', self.full_series[idx])
355 355 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
356 356 self.parse_series()
357 357 self.series_dirty = True
358 358
359 359 def pushable(self, idx):
360 360 if isinstance(idx, str):
361 361 idx = self.series.index(idx)
362 362 patchguards = self.series_guards[idx]
363 363 if not patchguards:
364 364 return True, None
365 365 guards = self.active()
366 366 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
367 367 if exactneg:
368 368 return False, exactneg[0]
369 369 pos = [g for g in patchguards if g[0] == '+']
370 370 exactpos = [g for g in pos if g[1:] in guards]
371 371 if pos:
372 372 if exactpos:
373 373 return True, exactpos[0]
374 374 return False, pos
375 375 return True, ''
376 376
377 377 def explain_pushable(self, idx, all_patches=False):
378 378 write = all_patches and self.ui.write or self.ui.warn
379 379 if all_patches or self.ui.verbose:
380 380 if isinstance(idx, str):
381 381 idx = self.series.index(idx)
382 382 pushable, why = self.pushable(idx)
383 383 if all_patches and pushable:
384 384 if why is None:
385 385 write(_('allowing %s - no guards in effect\n') %
386 386 self.series[idx])
387 387 else:
388 388 if not why:
389 389 write(_('allowing %s - no matching negative guards\n') %
390 390 self.series[idx])
391 391 else:
392 392 write(_('allowing %s - guarded by %r\n') %
393 393 (self.series[idx], why))
394 394 if not pushable:
395 395 if why:
396 396 write(_('skipping %s - guarded by %r\n') %
397 397 (self.series[idx], why))
398 398 else:
399 399 write(_('skipping %s - no matching guards\n') %
400 400 self.series[idx])
401 401
402 402 def save_dirty(self):
403 403 def write_list(items, path):
404 404 fp = self.opener(path, 'w')
405 405 for i in items:
406 406 fp.write("%s\n" % i)
407 407 fp.close()
408 408 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
409 409 if self.series_dirty: write_list(self.full_series, self.series_path)
410 410 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
411 411
412 412 def removeundo(self, repo):
413 413 undo = repo.sjoin('undo')
414 414 if not os.path.exists(undo):
415 415 return
416 416 try:
417 417 os.unlink(undo)
418 418 except OSError, inst:
419 419 self.ui.warn(_('error removing undo: %s\n') % str(inst))
420 420
421 421 def printdiff(self, repo, node1, node2=None, files=None,
422 422 fp=None, changes=None, opts={}):
423 423 stat = opts.get('stat')
424 424 if stat:
425 425 opts['unified'] = '0'
426 426
427 427 m = cmdutil.match(repo, files, opts)
428 428 chunks = patch.diff(repo, node1, node2, m, changes, self.diffopts())
429 429 write = fp is None and repo.ui.write or fp.write
430 430 if stat:
431 431 width = self.ui.interactive() and util.termwidth() or 80
432 432 write(patch.diffstat(util.iterlines(chunks), width=width,
433 433 git=self.diffopts().git))
434 434 else:
435 435 for chunk in chunks:
436 436 write(chunk)
437 437
438 438 def mergeone(self, repo, mergeq, head, patch, rev):
439 439 # first try just applying the patch
440 440 (err, n) = self.apply(repo, [ patch ], update_status=False,
441 441 strict=True, merge=rev)
442 442
443 443 if err == 0:
444 444 return (err, n)
445 445
446 446 if n is None:
447 447 raise util.Abort(_("apply failed for patch %s") % patch)
448 448
449 449 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
450 450
451 451 # apply failed, strip away that rev and merge.
452 452 hg.clean(repo, head)
453 453 self.strip(repo, n, update=False, backup='strip')
454 454
455 455 ctx = repo[rev]
456 456 ret = hg.merge(repo, rev)
457 457 if ret:
458 458 raise util.Abort(_("update returned %d") % ret)
459 459 n = repo.commit(ctx.description(), ctx.user(), force=True)
460 460 if n is None:
461 461 raise util.Abort(_("repo commit failed"))
462 462 try:
463 463 ph = patchheader(mergeq.join(patch))
464 464 except:
465 465 raise util.Abort(_("unable to read %s") % patch)
466 466
467 467 patchf = self.opener(patch, "w")
468 468 comments = str(ph)
469 469 if comments:
470 470 patchf.write(comments)
471 471 self.printdiff(repo, head, n, fp=patchf)
472 472 patchf.close()
473 473 self.removeundo(repo)
474 474 return (0, n)
475 475
476 476 def qparents(self, repo, rev=None):
477 477 if rev is None:
478 478 (p1, p2) = repo.dirstate.parents()
479 479 if p2 == nullid:
480 480 return p1
481 481 if len(self.applied) == 0:
482 482 return None
483 483 return bin(self.applied[-1].rev)
484 484 pp = repo.changelog.parents(rev)
485 485 if pp[1] != nullid:
486 486 arevs = [ x.rev for x in self.applied ]
487 487 p0 = hex(pp[0])
488 488 p1 = hex(pp[1])
489 489 if p0 in arevs:
490 490 return pp[0]
491 491 if p1 in arevs:
492 492 return pp[1]
493 493 return pp[0]
494 494
495 495 def mergepatch(self, repo, mergeq, series):
496 496 if len(self.applied) == 0:
497 497 # each of the patches merged in will have two parents. This
498 498 # can confuse the qrefresh, qdiff, and strip code because it
499 499 # needs to know which parent is actually in the patch queue.
500 500 # so, we insert a merge marker with only one parent. This way
501 501 # the first patch in the queue is never a merge patch
502 502 #
503 503 pname = ".hg.patches.merge.marker"
504 504 n = repo.commit('[mq]: merge marker', force=True)
505 505 self.removeundo(repo)
506 506 self.applied.append(statusentry(hex(n), pname))
507 507 self.applied_dirty = 1
508 508
509 509 head = self.qparents(repo)
510 510
511 511 for patch in series:
512 512 patch = mergeq.lookup(patch, strict=True)
513 513 if not patch:
514 514 self.ui.warn(_("patch %s does not exist\n") % patch)
515 515 return (1, None)
516 516 pushable, reason = self.pushable(patch)
517 517 if not pushable:
518 518 self.explain_pushable(patch, all_patches=True)
519 519 continue
520 520 info = mergeq.isapplied(patch)
521 521 if not info:
522 522 self.ui.warn(_("patch %s is not applied\n") % patch)
523 523 return (1, None)
524 524 rev = bin(info[1])
525 525 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
526 526 if head:
527 527 self.applied.append(statusentry(hex(head), patch))
528 528 self.applied_dirty = 1
529 529 if err:
530 530 return (err, head)
531 531 self.save_dirty()
532 532 return (0, head)
533 533
534 534 def patch(self, repo, patchfile):
535 535 '''Apply patchfile to the working directory.
536 536 patchfile: name of patch file'''
537 537 files = {}
538 538 try:
539 539 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
540 540 files=files, eolmode=None)
541 541 except Exception, inst:
542 542 self.ui.note(str(inst) + '\n')
543 543 if not self.ui.verbose:
544 544 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
545 545 return (False, files, False)
546 546
547 547 return (True, files, fuzz)
548 548
549 549 def apply(self, repo, series, list=False, update_status=True,
550 550 strict=False, patchdir=None, merge=None, all_files={}):
551 551 wlock = lock = tr = None
552 552 try:
553 553 wlock = repo.wlock()
554 554 lock = repo.lock()
555 555 tr = repo.transaction()
556 556 try:
557 557 ret = self._apply(repo, series, list, update_status,
558 558 strict, patchdir, merge, all_files=all_files)
559 559 tr.close()
560 560 self.save_dirty()
561 561 return ret
562 562 except:
563 563 try:
564 564 tr.abort()
565 565 finally:
566 566 repo.invalidate()
567 567 repo.dirstate.invalidate()
568 568 raise
569 569 finally:
570 570 del tr
571 571 release(lock, wlock)
572 572 self.removeundo(repo)
573 573
574 574 def _apply(self, repo, series, list=False, update_status=True,
575 575 strict=False, patchdir=None, merge=None, all_files={}):
576 576 '''returns (error, hash)
577 577 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
578 578 # TODO unify with commands.py
579 579 if not patchdir:
580 580 patchdir = self.path
581 581 err = 0
582 582 n = None
583 583 for patchname in series:
584 584 pushable, reason = self.pushable(patchname)
585 585 if not pushable:
586 586 self.explain_pushable(patchname, all_patches=True)
587 587 continue
588 588 self.ui.status(_("applying %s\n") % patchname)
589 589 pf = os.path.join(patchdir, patchname)
590 590
591 591 try:
592 592 ph = patchheader(self.join(patchname))
593 593 except:
594 594 self.ui.warn(_("unable to read %s\n") % patchname)
595 595 err = 1
596 596 break
597 597
598 598 message = ph.message
599 599 if not message:
600 600 message = _("imported patch %s\n") % patchname
601 601 else:
602 602 if list:
603 603 message.append(_("\nimported patch %s") % patchname)
604 604 message = '\n'.join(message)
605 605
606 606 if ph.haspatch:
607 607 (patcherr, files, fuzz) = self.patch(repo, pf)
608 608 all_files.update(files)
609 609 patcherr = not patcherr
610 610 else:
611 611 self.ui.warn(_("patch %s is empty\n") % patchname)
612 612 patcherr, files, fuzz = 0, [], 0
613 613
614 614 if merge and files:
615 615 # Mark as removed/merged and update dirstate parent info
616 616 removed = []
617 617 merged = []
618 618 for f in files:
619 619 if os.path.exists(repo.wjoin(f)):
620 620 merged.append(f)
621 621 else:
622 622 removed.append(f)
623 623 for f in removed:
624 624 repo.dirstate.remove(f)
625 625 for f in merged:
626 626 repo.dirstate.merge(f)
627 627 p1, p2 = repo.dirstate.parents()
628 628 repo.dirstate.setparents(p1, merge)
629 629
630 630 files = patch.updatedir(self.ui, repo, files)
631 631 match = cmdutil.matchfiles(repo, files or [])
632 632 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
633 633
634 634 if n is None:
635 635 raise util.Abort(_("repo commit failed"))
636 636
637 637 if update_status:
638 638 self.applied.append(statusentry(hex(n), patchname))
639 639
640 640 if patcherr:
641 641 self.ui.warn(_("patch failed, rejects left in working dir\n"))
642 642 err = 2
643 643 break
644 644
645 645 if fuzz and strict:
646 646 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
647 647 err = 3
648 648 break
649 649 return (err, n)
650 650
651 651 def _cleanup(self, patches, numrevs, keep=False):
652 652 if not keep:
653 653 r = self.qrepo()
654 654 if r:
655 655 r.remove(patches, True)
656 656 else:
657 657 for p in patches:
658 658 os.unlink(self.join(p))
659 659
660 660 if numrevs:
661 661 del self.applied[:numrevs]
662 662 self.applied_dirty = 1
663 663
664 664 for i in sorted([self.find_series(p) for p in patches], reverse=True):
665 665 del self.full_series[i]
666 666 self.parse_series()
667 667 self.series_dirty = 1
668 668
669 669 def _revpatches(self, repo, revs):
670 670 firstrev = repo[self.applied[0].rev].rev()
671 671 patches = []
672 672 for i, rev in enumerate(revs):
673 673
674 674 if rev < firstrev:
675 675 raise util.Abort(_('revision %d is not managed') % rev)
676 676
677 677 ctx = repo[rev]
678 678 base = bin(self.applied[i].rev)
679 679 if ctx.node() != base:
680 680 msg = _('cannot delete revision %d above applied patches')
681 681 raise util.Abort(msg % rev)
682 682
683 683 patch = self.applied[i].name
684 684 for fmt in ('[mq]: %s', 'imported patch %s'):
685 685 if ctx.description() == fmt % patch:
686 686 msg = _('patch %s finalized without changeset message\n')
687 687 repo.ui.status(msg % patch)
688 688 break
689 689
690 690 patches.append(patch)
691 691 return patches
692 692
693 693 def finish(self, repo, revs):
694 694 patches = self._revpatches(repo, sorted(revs))
695 695 self._cleanup(patches, len(patches))
696 696
697 697 def delete(self, repo, patches, opts):
698 698 if not patches and not opts.get('rev'):
699 699 raise util.Abort(_('qdelete requires at least one revision or '
700 700 'patch name'))
701 701
702 702 realpatches = []
703 703 for patch in patches:
704 704 patch = self.lookup(patch, strict=True)
705 705 info = self.isapplied(patch)
706 706 if info:
707 707 raise util.Abort(_("cannot delete applied patch %s") % patch)
708 708 if patch not in self.series:
709 709 raise util.Abort(_("patch %s not in series file") % patch)
710 710 realpatches.append(patch)
711 711
712 712 numrevs = 0
713 713 if opts.get('rev'):
714 714 if not self.applied:
715 715 raise util.Abort(_('no patches applied'))
716 716 revs = cmdutil.revrange(repo, opts['rev'])
717 717 if len(revs) > 1 and revs[0] > revs[1]:
718 718 revs.reverse()
719 719 revpatches = self._revpatches(repo, revs)
720 720 realpatches += revpatches
721 721 numrevs = len(revpatches)
722 722
723 723 self._cleanup(realpatches, numrevs, opts.get('keep'))
724 724
725 725 def check_toppatch(self, repo):
726 726 if len(self.applied) > 0:
727 727 top = bin(self.applied[-1].rev)
728 728 pp = repo.dirstate.parents()
729 729 if top not in pp:
730 730 raise util.Abort(_("working directory revision is not qtip"))
731 731 return top
732 732 return None
733 733 def check_localchanges(self, repo, force=False, refresh=True):
734 734 m, a, r, d = repo.status()[:4]
735 735 if m or a or r or d:
736 736 if not force:
737 737 if refresh:
738 738 raise util.Abort(_("local changes found, refresh first"))
739 739 else:
740 740 raise util.Abort(_("local changes found"))
741 741 return m, a, r, d
742 742
743 743 _reserved = ('series', 'status', 'guards')
744 744 def check_reserved_name(self, name):
745 745 if (name in self._reserved or name.startswith('.hg')
746 746 or name.startswith('.mq')):
747 747 raise util.Abort(_('"%s" cannot be used as the name of a patch')
748 748 % name)
749 749
750 750 def new(self, repo, patchfn, *pats, **opts):
751 751 """options:
752 752 msg: a string or a no-argument function returning a string
753 753 """
754 754 msg = opts.get('msg')
755 755 force = opts.get('force')
756 756 user = opts.get('user')
757 757 date = opts.get('date')
758 758 if date:
759 759 date = util.parsedate(date)
760 760 self.check_reserved_name(patchfn)
761 761 if os.path.exists(self.join(patchfn)):
762 762 raise util.Abort(_('patch "%s" already exists') % patchfn)
763 763 if opts.get('include') or opts.get('exclude') or pats:
764 764 match = cmdutil.match(repo, pats, opts)
765 765 # detect missing files in pats
766 766 def badfn(f, msg):
767 767 raise util.Abort('%s: %s' % (f, msg))
768 768 match.bad = badfn
769 769 m, a, r, d = repo.status(match=match)[:4]
770 770 else:
771 771 m, a, r, d = self.check_localchanges(repo, force)
772 772 match = cmdutil.matchfiles(repo, m + a + r)
773 773 commitfiles = m + a + r
774 774 self.check_toppatch(repo)
775 775 insert = self.full_series_end()
776 776 wlock = repo.wlock()
777 777 try:
778 778 # if patch file write fails, abort early
779 779 p = self.opener(patchfn, "w")
780 780 try:
781 781 if date:
782 782 p.write("# HG changeset patch\n")
783 783 if user:
784 784 p.write("# User " + user + "\n")
785 785 p.write("# Date %d %d\n\n" % date)
786 786 elif user:
787 787 p.write("From: " + user + "\n\n")
788 788
789 789 if hasattr(msg, '__call__'):
790 790 msg = msg()
791 791 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
792 792 n = repo.commit(commitmsg, user, date, match=match, force=True)
793 793 if n is None:
794 794 raise util.Abort(_("repo commit failed"))
795 795 try:
796 796 self.full_series[insert:insert] = [patchfn]
797 797 self.applied.append(statusentry(hex(n), patchfn))
798 798 self.parse_series()
799 799 self.series_dirty = 1
800 800 self.applied_dirty = 1
801 801 if msg:
802 802 msg = msg + "\n\n"
803 803 p.write(msg)
804 804 if commitfiles:
805 805 diffopts = self.diffopts()
806 806 if opts.get('git'): diffopts.git = True
807 807 parent = self.qparents(repo, n)
808 808 chunks = patch.diff(repo, node1=parent, node2=n,
809 809 match=match, opts=diffopts)
810 810 for chunk in chunks:
811 811 p.write(chunk)
812 812 p.close()
813 813 wlock.release()
814 814 wlock = None
815 815 r = self.qrepo()
816 816 if r: r.add([patchfn])
817 817 except:
818 818 repo.rollback()
819 819 raise
820 820 except Exception:
821 821 patchpath = self.join(patchfn)
822 822 try:
823 823 os.unlink(patchpath)
824 824 except:
825 825 self.ui.warn(_('error unlinking %s\n') % patchpath)
826 826 raise
827 827 self.removeundo(repo)
828 828 finally:
829 829 release(wlock)
830 830
831 831 def strip(self, repo, rev, update=True, backup="all", force=None):
832 832 wlock = lock = None
833 833 try:
834 834 wlock = repo.wlock()
835 835 lock = repo.lock()
836 836
837 837 if update:
838 838 self.check_localchanges(repo, force=force, refresh=False)
839 839 urev = self.qparents(repo, rev)
840 840 hg.clean(repo, urev)
841 841 repo.dirstate.write()
842 842
843 843 self.removeundo(repo)
844 844 repair.strip(self.ui, repo, rev, backup)
845 845 # strip may have unbundled a set of backed up revisions after
846 846 # the actual strip
847 847 self.removeundo(repo)
848 848 finally:
849 849 release(lock, wlock)
850 850
851 851 def isapplied(self, patch):
852 852 """returns (index, rev, patch)"""
853 853 for i, a in enumerate(self.applied):
854 854 if a.name == patch:
855 855 return (i, a.rev, a.name)
856 856 return None
857 857
858 858 # if the exact patch name does not exist, we try a few
859 859 # variations. If strict is passed, we try only #1
860 860 #
861 861 # 1) a number to indicate an offset in the series file
862 862 # 2) a unique substring of the patch name was given
863 863 # 3) patchname[-+]num to indicate an offset in the series file
864 864 def lookup(self, patch, strict=False):
865 865 patch = patch and str(patch)
866 866
867 867 def partial_name(s):
868 868 if s in self.series:
869 869 return s
870 870 matches = [x for x in self.series if s in x]
871 871 if len(matches) > 1:
872 872 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
873 873 for m in matches:
874 874 self.ui.warn(' %s\n' % m)
875 875 return None
876 876 if matches:
877 877 return matches[0]
878 878 if len(self.series) > 0 and len(self.applied) > 0:
879 879 if s == 'qtip':
880 880 return self.series[self.series_end(True)-1]
881 881 if s == 'qbase':
882 882 return self.series[0]
883 883 return None
884 884
885 885 if patch is None:
886 886 return None
887 887 if patch in self.series:
888 888 return patch
889 889
890 890 if not os.path.isfile(self.join(patch)):
891 891 try:
892 892 sno = int(patch)
893 893 except(ValueError, OverflowError):
894 894 pass
895 895 else:
896 896 if -len(self.series) <= sno < len(self.series):
897 897 return self.series[sno]
898 898
899 899 if not strict:
900 900 res = partial_name(patch)
901 901 if res:
902 902 return res
903 903 minus = patch.rfind('-')
904 904 if minus >= 0:
905 905 res = partial_name(patch[:minus])
906 906 if res:
907 907 i = self.series.index(res)
908 908 try:
909 909 off = int(patch[minus+1:] or 1)
910 910 except(ValueError, OverflowError):
911 911 pass
912 912 else:
913 913 if i - off >= 0:
914 914 return self.series[i - off]
915 915 plus = patch.rfind('+')
916 916 if plus >= 0:
917 917 res = partial_name(patch[:plus])
918 918 if res:
919 919 i = self.series.index(res)
920 920 try:
921 921 off = int(patch[plus+1:] or 1)
922 922 except(ValueError, OverflowError):
923 923 pass
924 924 else:
925 925 if i + off < len(self.series):
926 926 return self.series[i + off]
927 927 raise util.Abort(_("patch %s not in series") % patch)
928 928
929 929 def push(self, repo, patch=None, force=False, list=False,
930 930 mergeq=None, all=False):
931 931 wlock = repo.wlock()
932 932 try:
933 933 if repo.dirstate.parents()[0] not in repo.heads():
934 934 self.ui.status(_("(working directory not at a head)\n"))
935 935
936 936 if not self.series:
937 937 self.ui.warn(_('no patches in series\n'))
938 938 return 0
939 939
940 940 patch = self.lookup(patch)
941 941 # Suppose our series file is: A B C and the current 'top'
942 942 # patch is B. qpush C should be performed (moving forward)
943 943 # qpush B is a NOP (no change) qpush A is an error (can't
944 944 # go backwards with qpush)
945 945 if patch:
946 946 info = self.isapplied(patch)
947 947 if info:
948 948 if info[0] < len(self.applied) - 1:
949 949 raise util.Abort(
950 950 _("cannot push to a previous patch: %s") % patch)
951 951 self.ui.warn(
952 952 _('qpush: %s is already at the top\n') % patch)
953 953 return
954 954 pushable, reason = self.pushable(patch)
955 955 if not pushable:
956 956 if reason:
957 957 reason = _('guarded by %r') % reason
958 958 else:
959 959 reason = _('no matching guards')
960 960 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
961 961 return 1
962 962 elif all:
963 963 patch = self.series[-1]
964 964 if self.isapplied(patch):
965 965 self.ui.warn(_('all patches are currently applied\n'))
966 966 return 0
967 967
968 968 # Following the above example, starting at 'top' of B:
969 969 # qpush should be performed (pushes C), but a subsequent
970 970 # qpush without an argument is an error (nothing to
971 971 # apply). This allows a loop of "...while hg qpush..." to
972 972 # work as it detects an error when done
973 973 start = self.series_end()
974 974 if start == len(self.series):
975 975 self.ui.warn(_('patch series already fully applied\n'))
976 976 return 1
977 977 if not force:
978 978 self.check_localchanges(repo)
979 979
980 980 self.applied_dirty = 1
981 981 if start > 0:
982 982 self.check_toppatch(repo)
983 983 if not patch:
984 984 patch = self.series[start]
985 985 end = start + 1
986 986 else:
987 987 end = self.series.index(patch, start) + 1
988 988
989 989 s = self.series[start:end]
990 990 all_files = {}
991 991 try:
992 992 if mergeq:
993 993 ret = self.mergepatch(repo, mergeq, s)
994 994 else:
995 995 ret = self.apply(repo, s, list, all_files=all_files)
996 996 except:
997 997 self.ui.warn(_('cleaning up working directory...'))
998 998 node = repo.dirstate.parents()[0]
999 999 hg.revert(repo, node, None)
1000 1000 unknown = repo.status(unknown=True)[4]
1001 1001 # only remove unknown files that we know we touched or
1002 1002 # created while patching
1003 1003 for f in unknown:
1004 1004 if f in all_files:
1005 1005 util.unlink(repo.wjoin(f))
1006 1006 self.ui.warn(_('done\n'))
1007 1007 raise
1008 1008
1009 1009 if not self.applied:
1010 1010 return ret[0]
1011 1011 top = self.applied[-1].name
1012 1012 if ret[0] and ret[0] > 1:
1013 1013 msg = _("errors during apply, please fix and refresh %s\n")
1014 1014 self.ui.write(msg % top)
1015 1015 else:
1016 1016 self.ui.write(_("now at: %s\n") % top)
1017 1017 return ret[0]
1018 1018
1019 1019 finally:
1020 1020 wlock.release()
1021 1021
1022 1022 def pop(self, repo, patch=None, force=False, update=True, all=False):
1023 1023 def getfile(f, rev, flags):
1024 1024 t = repo.file(f).read(rev)
1025 1025 repo.wwrite(f, t, flags)
1026 1026
1027 1027 wlock = repo.wlock()
1028 1028 try:
1029 1029 if patch:
1030 1030 # index, rev, patch
1031 1031 info = self.isapplied(patch)
1032 1032 if not info:
1033 1033 patch = self.lookup(patch)
1034 1034 info = self.isapplied(patch)
1035 1035 if not info:
1036 1036 raise util.Abort(_("patch %s is not applied") % patch)
1037 1037
1038 1038 if len(self.applied) == 0:
1039 1039 # Allow qpop -a to work repeatedly,
1040 1040 # but not qpop without an argument
1041 1041 self.ui.warn(_("no patches applied\n"))
1042 1042 return not all
1043 1043
1044 1044 if all:
1045 1045 start = 0
1046 1046 elif patch:
1047 1047 start = info[0] + 1
1048 1048 else:
1049 1049 start = len(self.applied) - 1
1050 1050
1051 1051 if start >= len(self.applied):
1052 1052 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1053 1053 return
1054 1054
1055 1055 if not update:
1056 1056 parents = repo.dirstate.parents()
1057 1057 rr = [ bin(x.rev) for x in self.applied ]
1058 1058 for p in parents:
1059 1059 if p in rr:
1060 1060 self.ui.warn(_("qpop: forcing dirstate update\n"))
1061 1061 update = True
1062 1062 else:
1063 1063 parents = [p.hex() for p in repo[None].parents()]
1064 1064 needupdate = False
1065 1065 for entry in self.applied[start:]:
1066 1066 if entry.rev in parents:
1067 1067 needupdate = True
1068 1068 break
1069 1069 update = needupdate
1070 1070
1071 1071 if not force and update:
1072 1072 self.check_localchanges(repo)
1073 1073
1074 1074 self.applied_dirty = 1
1075 1075 end = len(self.applied)
1076 1076 rev = bin(self.applied[start].rev)
1077 1077 if update:
1078 1078 top = self.check_toppatch(repo)
1079 1079
1080 1080 try:
1081 1081 heads = repo.changelog.heads(rev)
1082 1082 except error.LookupError:
1083 1083 node = short(rev)
1084 1084 raise util.Abort(_('trying to pop unknown node %s') % node)
1085 1085
1086 1086 if heads != [bin(self.applied[-1].rev)]:
1087 1087 raise util.Abort(_("popping would remove a revision not "
1088 1088 "managed by this patch queue"))
1089 1089
1090 1090 # we know there are no local changes, so we can make a simplified
1091 1091 # form of hg.update.
1092 1092 if update:
1093 1093 qp = self.qparents(repo, rev)
1094 1094 changes = repo.changelog.read(qp)
1095 1095 mmap = repo.manifest.read(changes[0])
1096 1096 m, a, r, d = repo.status(qp, top)[:4]
1097 1097 if d:
1098 1098 raise util.Abort(_("deletions found between repo revs"))
1099 1099 for f in m:
1100 1100 getfile(f, mmap[f], mmap.flags(f))
1101 1101 for f in r:
1102 1102 getfile(f, mmap[f], mmap.flags(f))
1103 1103 for f in m + r:
1104 1104 repo.dirstate.normal(f)
1105 1105 for f in a:
1106 1106 try:
1107 1107 os.unlink(repo.wjoin(f))
1108 1108 except OSError, e:
1109 1109 if e.errno != errno.ENOENT:
1110 1110 raise
1111 1111 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
1112 1112 except: pass
1113 1113 repo.dirstate.forget(f)
1114 1114 repo.dirstate.setparents(qp, nullid)
1115 1115 for patch in reversed(self.applied[start:end]):
1116 1116 self.ui.status(_("popping %s\n") % patch.name)
1117 1117 del self.applied[start:end]
1118 1118 self.strip(repo, rev, update=False, backup='strip')
1119 1119 if len(self.applied):
1120 1120 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1121 1121 else:
1122 1122 self.ui.write(_("patch queue now empty\n"))
1123 1123 finally:
1124 1124 wlock.release()
1125 1125
1126 1126 def diff(self, repo, pats, opts):
1127 1127 top = self.check_toppatch(repo)
1128 1128 if not top:
1129 1129 self.ui.write(_("no patches applied\n"))
1130 1130 return
1131 1131 qp = self.qparents(repo, top)
1132 1132 if opts.get('inverse'):
1133 1133 node1, node2 = None, qp
1134 1134 else:
1135 1135 node1, node2 = qp, None
1136 1136 self._diffopts = patch.diffopts(self.ui, opts)
1137 1137 self.printdiff(repo, node1, node2, files=pats, opts=opts)
1138 1138
1139 1139 def refresh(self, repo, pats=None, **opts):
1140 1140 if len(self.applied) == 0:
1141 1141 self.ui.write(_("no patches applied\n"))
1142 1142 return 1
1143 1143 msg = opts.get('msg', '').rstrip()
1144 1144 newuser = opts.get('user')
1145 1145 newdate = opts.get('date')
1146 1146 if newdate:
1147 1147 newdate = '%d %d' % util.parsedate(newdate)
1148 1148 wlock = repo.wlock()
1149 1149 try:
1150 1150 self.check_toppatch(repo)
1151 1151 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1152 1152 top = bin(top)
1153 1153 if repo.changelog.heads(top) != [top]:
1154 1154 raise util.Abort(_("cannot refresh a revision with children"))
1155 1155 cparents = repo.changelog.parents(top)
1156 1156 patchparent = self.qparents(repo, top)
1157 1157 ph = patchheader(self.join(patchfn))
1158 1158
1159 1159 patchf = self.opener(patchfn, 'r')
1160 1160
1161 1161 # if the patch was a git patch, refresh it as a git patch
1162 1162 for line in patchf:
1163 1163 if line.startswith('diff --git'):
1164 1164 self.diffopts().git = True
1165 1165 break
1166 1166
1167 1167 if msg:
1168 1168 ph.setmessage(msg)
1169 1169 if newuser:
1170 1170 ph.setuser(newuser)
1171 1171 if newdate:
1172 1172 ph.setdate(newdate)
1173 1173
1174 1174 # only commit new patch when write is complete
1175 1175 patchf = self.opener(patchfn, 'w', atomictemp=True)
1176 1176
1177 1177 patchf.seek(0)
1178 1178 patchf.truncate()
1179 1179
1180 1180 comments = str(ph)
1181 1181 if comments:
1182 1182 patchf.write(comments)
1183 1183
1184 1184 if opts.get('git'):
1185 1185 self.diffopts().git = True
1186 1186 tip = repo.changelog.tip()
1187 1187 if top == tip:
1188 1188 # if the top of our patch queue is also the tip, there is an
1189 1189 # optimization here. We update the dirstate in place and strip
1190 1190 # off the tip commit. Then just commit the current directory
1191 1191 # tree. We can also send repo.commit the list of files
1192 1192 # changed to speed up the diff
1193 1193 #
1194 1194 # in short mode, we only diff the files included in the
1195 1195 # patch already plus specified files
1196 1196 #
1197 1197 # this should really read:
1198 1198 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1199 1199 # but we do it backwards to take advantage of manifest/chlog
1200 1200 # caching against the next repo.status call
1201 1201 #
1202 1202 mm, aa, dd, aa2 = repo.status(patchparent, tip)[:4]
1203 1203 changes = repo.changelog.read(tip)
1204 1204 man = repo.manifest.read(changes[0])
1205 1205 aaa = aa[:]
1206 1206 matchfn = cmdutil.match(repo, pats, opts)
1207 1207 if opts.get('short'):
1208 1208 # if amending a patch, we start with existing
1209 1209 # files plus specified files - unfiltered
1210 1210 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1211 1211 # filter with inc/exl options
1212 1212 matchfn = cmdutil.match(repo, opts=opts)
1213 1213 else:
1214 1214 match = cmdutil.matchall(repo)
1215 1215 m, a, r, d = repo.status(match=match)[:4]
1216 1216
1217 1217 # we might end up with files that were added between
1218 1218 # tip and the dirstate parent, but then changed in the
1219 1219 # local dirstate. in this case, we want them to only
1220 1220 # show up in the added section
1221 1221 for x in m:
1222 1222 if x not in aa:
1223 1223 mm.append(x)
1224 1224 # we might end up with files added by the local dirstate that
1225 1225 # were deleted by the patch. In this case, they should only
1226 1226 # show up in the changed section.
1227 1227 for x in a:
1228 1228 if x in dd:
1229 1229 del dd[dd.index(x)]
1230 1230 mm.append(x)
1231 1231 else:
1232 1232 aa.append(x)
1233 1233 # make sure any files deleted in the local dirstate
1234 1234 # are not in the add or change column of the patch
1235 1235 forget = []
1236 1236 for x in d + r:
1237 1237 if x in aa:
1238 1238 del aa[aa.index(x)]
1239 1239 forget.append(x)
1240 1240 continue
1241 1241 elif x in mm:
1242 1242 del mm[mm.index(x)]
1243 1243 dd.append(x)
1244 1244
1245 1245 m = list(set(mm))
1246 1246 r = list(set(dd))
1247 1247 a = list(set(aa))
1248 1248 c = [filter(matchfn, l) for l in (m, a, r)]
1249 1249 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1250 1250 chunks = patch.diff(repo, patchparent, match=match,
1251 1251 changes=c, opts=self.diffopts())
1252 1252 for chunk in chunks:
1253 1253 patchf.write(chunk)
1254 1254
1255 1255 try:
1256 1256 if self.diffopts().git:
1257 1257 copies = {}
1258 1258 for dst in a:
1259 1259 src = repo.dirstate.copied(dst)
1260 1260 # during qfold, the source file for copies may
1261 1261 # be removed. Treat this as a simple add.
1262 1262 if src is not None and src in repo.dirstate:
1263 1263 copies.setdefault(src, []).append(dst)
1264 1264 repo.dirstate.add(dst)
1265 1265 # remember the copies between patchparent and tip
1266 1266 for dst in aaa:
1267 1267 f = repo.file(dst)
1268 1268 src = f.renamed(man[dst])
1269 1269 if src:
1270 1270 copies.setdefault(src[0], []).extend(copies.get(dst, []))
1271 1271 if dst in a:
1272 1272 copies[src[0]].append(dst)
1273 1273 # we can't copy a file created by the patch itself
1274 1274 if dst in copies:
1275 1275 del copies[dst]
1276 1276 for src, dsts in copies.iteritems():
1277 1277 for dst in dsts:
1278 1278 repo.dirstate.copy(src, dst)
1279 1279 else:
1280 1280 for dst in a:
1281 1281 repo.dirstate.add(dst)
1282 1282 # Drop useless copy information
1283 1283 for f in list(repo.dirstate.copies()):
1284 1284 repo.dirstate.copy(None, f)
1285 1285 for f in r:
1286 1286 repo.dirstate.remove(f)
1287 1287 # if the patch excludes a modified file, mark that
1288 1288 # file with mtime=0 so status can see it.
1289 1289 mm = []
1290 1290 for i in xrange(len(m)-1, -1, -1):
1291 1291 if not matchfn(m[i]):
1292 1292 mm.append(m[i])
1293 1293 del m[i]
1294 1294 for f in m:
1295 1295 repo.dirstate.normal(f)
1296 1296 for f in mm:
1297 1297 repo.dirstate.normallookup(f)
1298 1298 for f in forget:
1299 1299 repo.dirstate.forget(f)
1300 1300
1301 1301 if not msg:
1302 1302 if not ph.message:
1303 1303 message = "[mq]: %s\n" % patchfn
1304 1304 else:
1305 1305 message = "\n".join(ph.message)
1306 1306 else:
1307 1307 message = msg
1308 1308
1309 1309 user = ph.user or changes[1]
1310 1310
1311 1311 # assumes strip can roll itself back if interrupted
1312 1312 repo.dirstate.setparents(*cparents)
1313 1313 self.applied.pop()
1314 1314 self.applied_dirty = 1
1315 1315 self.strip(repo, top, update=False,
1316 1316 backup='strip')
1317 1317 except:
1318 1318 repo.dirstate.invalidate()
1319 1319 raise
1320 1320
1321 1321 try:
1322 1322 # might be nice to attempt to roll back strip after this
1323 1323 patchf.rename()
1324 1324 n = repo.commit(message, user, ph.date, match=match,
1325 1325 force=True)
1326 1326 self.applied.append(statusentry(hex(n), patchfn))
1327 1327 except:
1328 1328 ctx = repo[cparents[0]]
1329 1329 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1330 1330 self.save_dirty()
1331 1331 self.ui.warn(_('refresh interrupted while patch was popped! '
1332 1332 '(revert --all, qpush to recover)\n'))
1333 1333 raise
1334 1334 else:
1335 1335 self.printdiff(repo, patchparent, fp=patchf)
1336 1336 patchf.rename()
1337 1337 added = repo.status()[1]
1338 1338 for a in added:
1339 1339 f = repo.wjoin(a)
1340 1340 try:
1341 1341 os.unlink(f)
1342 1342 except OSError, e:
1343 1343 if e.errno != errno.ENOENT:
1344 1344 raise
1345 1345 try: os.removedirs(os.path.dirname(f))
1346 1346 except: pass
1347 1347 # forget the file copies in the dirstate
1348 1348 # push should readd the files later on
1349 1349 repo.dirstate.forget(a)
1350 1350 self.pop(repo, force=True)
1351 1351 self.push(repo, force=True)
1352 1352 finally:
1353 1353 wlock.release()
1354 1354 self.removeundo(repo)
1355 1355
1356 1356 def init(self, repo, create=False):
1357 1357 if not create and os.path.isdir(self.path):
1358 1358 raise util.Abort(_("patch queue directory already exists"))
1359 1359 try:
1360 1360 os.mkdir(self.path)
1361 1361 except OSError, inst:
1362 1362 if inst.errno != errno.EEXIST or not create:
1363 1363 raise
1364 1364 if create:
1365 1365 return self.qrepo(create=True)
1366 1366
1367 1367 def unapplied(self, repo, patch=None):
1368 1368 if patch and patch not in self.series:
1369 1369 raise util.Abort(_("patch %s is not in series file") % patch)
1370 1370 if not patch:
1371 1371 start = self.series_end()
1372 1372 else:
1373 1373 start = self.series.index(patch) + 1
1374 1374 unapplied = []
1375 1375 for i in xrange(start, len(self.series)):
1376 1376 pushable, reason = self.pushable(i)
1377 1377 if pushable:
1378 1378 unapplied.append((i, self.series[i]))
1379 1379 self.explain_pushable(i)
1380 1380 return unapplied
1381 1381
1382 1382 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1383 1383 summary=False):
1384 1384 def displayname(pfx, patchname):
1385 1385 if summary:
1386 1386 ph = patchheader(self.join(patchname))
1387 1387 msg = ph.message
1388 1388 msg = msg and ': ' + msg[0] or ': '
1389 1389 else:
1390 1390 msg = ''
1391 1391 msg = "%s%s%s" % (pfx, patchname, msg)
1392 1392 if self.ui.interactive():
1393 1393 msg = util.ellipsis(msg, util.termwidth())
1394 1394 self.ui.write(msg + '\n')
1395 1395
1396 1396 applied = set([p.name for p in self.applied])
1397 1397 if length is None:
1398 1398 length = len(self.series) - start
1399 1399 if not missing:
1400 1400 if self.ui.verbose:
1401 1401 idxwidth = len(str(start+length - 1))
1402 1402 for i in xrange(start, start+length):
1403 1403 patch = self.series[i]
1404 1404 if patch in applied:
1405 1405 stat = 'A'
1406 1406 elif self.pushable(i)[0]:
1407 1407 stat = 'U'
1408 1408 else:
1409 1409 stat = 'G'
1410 1410 pfx = ''
1411 1411 if self.ui.verbose:
1412 1412 pfx = '%*d %s ' % (idxwidth, i, stat)
1413 1413 elif status and status != stat:
1414 1414 continue
1415 1415 displayname(pfx, patch)
1416 1416 else:
1417 1417 msng_list = []
1418 1418 for root, dirs, files in os.walk(self.path):
1419 1419 d = root[len(self.path) + 1:]
1420 1420 for f in files:
1421 1421 fl = os.path.join(d, f)
1422 1422 if (fl not in self.series and
1423 1423 fl not in (self.status_path, self.series_path,
1424 1424 self.guards_path)
1425 1425 and not fl.startswith('.')):
1426 1426 msng_list.append(fl)
1427 1427 for x in sorted(msng_list):
1428 1428 pfx = self.ui.verbose and ('D ') or ''
1429 1429 displayname(pfx, x)
1430 1430
1431 1431 def issaveline(self, l):
1432 1432 if l.name == '.hg.patches.save.line':
1433 1433 return True
1434 1434
1435 1435 def qrepo(self, create=False):
1436 1436 if create or os.path.isdir(self.join(".hg")):
1437 1437 return hg.repository(self.ui, path=self.path, create=create)
1438 1438
1439 1439 def restore(self, repo, rev, delete=None, qupdate=None):
1440 1440 c = repo.changelog.read(rev)
1441 1441 desc = c[4].strip()
1442 1442 lines = desc.splitlines()
1443 1443 i = 0
1444 1444 datastart = None
1445 1445 series = []
1446 1446 applied = []
1447 1447 qpp = None
1448 1448 for i, line in enumerate(lines):
1449 1449 if line == 'Patch Data:':
1450 1450 datastart = i + 1
1451 1451 elif line.startswith('Dirstate:'):
1452 1452 l = line.rstrip()
1453 1453 l = l[10:].split(' ')
1454 1454 qpp = [ bin(x) for x in l ]
1455 1455 elif datastart != None:
1456 1456 l = line.rstrip()
1457 1457 se = statusentry(l)
1458 1458 file_ = se.name
1459 1459 if se.rev:
1460 1460 applied.append(se)
1461 1461 else:
1462 1462 series.append(file_)
1463 1463 if datastart is None:
1464 1464 self.ui.warn(_("No saved patch data found\n"))
1465 1465 return 1
1466 1466 self.ui.warn(_("restoring status: %s\n") % lines[0])
1467 1467 self.full_series = series
1468 1468 self.applied = applied
1469 1469 self.parse_series()
1470 1470 self.series_dirty = 1
1471 1471 self.applied_dirty = 1
1472 1472 heads = repo.changelog.heads()
1473 1473 if delete:
1474 1474 if rev not in heads:
1475 1475 self.ui.warn(_("save entry has children, leaving it alone\n"))
1476 1476 else:
1477 1477 self.ui.warn(_("removing save entry %s\n") % short(rev))
1478 1478 pp = repo.dirstate.parents()
1479 1479 if rev in pp:
1480 1480 update = True
1481 1481 else:
1482 1482 update = False
1483 1483 self.strip(repo, rev, update=update, backup='strip')
1484 1484 if qpp:
1485 1485 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1486 1486 (short(qpp[0]), short(qpp[1])))
1487 1487 if qupdate:
1488 1488 self.ui.status(_("queue directory updating\n"))
1489 1489 r = self.qrepo()
1490 1490 if not r:
1491 1491 self.ui.warn(_("Unable to load queue repository\n"))
1492 1492 return 1
1493 1493 hg.clean(r, qpp[0])
1494 1494
1495 1495 def save(self, repo, msg=None):
1496 1496 if len(self.applied) == 0:
1497 1497 self.ui.warn(_("save: no patches applied, exiting\n"))
1498 1498 return 1
1499 1499 if self.issaveline(self.applied[-1]):
1500 1500 self.ui.warn(_("status is already saved\n"))
1501 1501 return 1
1502 1502
1503 1503 ar = [ ':' + x for x in self.full_series ]
1504 1504 if not msg:
1505 1505 msg = _("hg patches saved state")
1506 1506 else:
1507 1507 msg = "hg patches: " + msg.rstrip('\r\n')
1508 1508 r = self.qrepo()
1509 1509 if r:
1510 1510 pp = r.dirstate.parents()
1511 1511 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1512 1512 msg += "\n\nPatch Data:\n"
1513 1513 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1514 1514 "\n".join(ar) + '\n' or "")
1515 1515 n = repo.commit(text, force=True)
1516 1516 if not n:
1517 1517 self.ui.warn(_("repo commit failed\n"))
1518 1518 return 1
1519 1519 self.applied.append(statusentry(hex(n),'.hg.patches.save.line'))
1520 1520 self.applied_dirty = 1
1521 1521 self.removeundo(repo)
1522 1522
1523 1523 def full_series_end(self):
1524 1524 if len(self.applied) > 0:
1525 1525 p = self.applied[-1].name
1526 1526 end = self.find_series(p)
1527 1527 if end is None:
1528 1528 return len(self.full_series)
1529 1529 return end + 1
1530 1530 return 0
1531 1531
1532 1532 def series_end(self, all_patches=False):
1533 1533 """If all_patches is False, return the index of the next pushable patch
1534 1534 in the series, or the series length. If all_patches is True, return the
1535 1535 index of the first patch past the last applied one.
1536 1536 """
1537 1537 end = 0
1538 1538 def next(start):
1539 1539 if all_patches:
1540 1540 return start
1541 1541 i = start
1542 1542 while i < len(self.series):
1543 1543 p, reason = self.pushable(i)
1544 1544 if p:
1545 1545 break
1546 1546 self.explain_pushable(i)
1547 1547 i += 1
1548 1548 return i
1549 1549 if len(self.applied) > 0:
1550 1550 p = self.applied[-1].name
1551 1551 try:
1552 1552 end = self.series.index(p)
1553 1553 except ValueError:
1554 1554 return 0
1555 1555 return next(end + 1)
1556 1556 return next(end)
1557 1557
1558 1558 def appliedname(self, index):
1559 1559 pname = self.applied[index].name
1560 1560 if not self.ui.verbose:
1561 1561 p = pname
1562 1562 else:
1563 1563 p = str(self.series.index(pname)) + " " + pname
1564 1564 return p
1565 1565
1566 1566 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1567 1567 force=None, git=False):
1568 1568 def checkseries(patchname):
1569 1569 if patchname in self.series:
1570 1570 raise util.Abort(_('patch %s is already in the series file')
1571 1571 % patchname)
1572 1572 def checkfile(patchname):
1573 1573 if not force and os.path.exists(self.join(patchname)):
1574 1574 raise util.Abort(_('patch "%s" already exists')
1575 1575 % patchname)
1576 1576
1577 1577 if rev:
1578 1578 if files:
1579 1579 raise util.Abort(_('option "-r" not valid when importing '
1580 1580 'files'))
1581 1581 rev = cmdutil.revrange(repo, rev)
1582 1582 rev.sort(reverse=True)
1583 1583 if (len(files) > 1 or len(rev) > 1) and patchname:
1584 1584 raise util.Abort(_('option "-n" not valid when importing multiple '
1585 1585 'patches'))
1586 1586 i = 0
1587 1587 added = []
1588 1588 if rev:
1589 1589 # If mq patches are applied, we can only import revisions
1590 1590 # that form a linear path to qbase.
1591 1591 # Otherwise, they should form a linear path to a head.
1592 1592 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1593 1593 if len(heads) > 1:
1594 1594 raise util.Abort(_('revision %d is the root of more than one '
1595 1595 'branch') % rev[-1])
1596 1596 if self.applied:
1597 1597 base = hex(repo.changelog.node(rev[0]))
1598 1598 if base in [n.rev for n in self.applied]:
1599 1599 raise util.Abort(_('revision %d is already managed')
1600 1600 % rev[0])
1601 1601 if heads != [bin(self.applied[-1].rev)]:
1602 1602 raise util.Abort(_('revision %d is not the parent of '
1603 1603 'the queue') % rev[0])
1604 1604 base = repo.changelog.rev(bin(self.applied[0].rev))
1605 1605 lastparent = repo.changelog.parentrevs(base)[0]
1606 1606 else:
1607 1607 if heads != [repo.changelog.node(rev[0])]:
1608 1608 raise util.Abort(_('revision %d has unmanaged children')
1609 1609 % rev[0])
1610 1610 lastparent = None
1611 1611
1612 1612 if git:
1613 1613 self.diffopts().git = True
1614 1614
1615 1615 for r in rev:
1616 1616 p1, p2 = repo.changelog.parentrevs(r)
1617 1617 n = repo.changelog.node(r)
1618 1618 if p2 != nullrev:
1619 1619 raise util.Abort(_('cannot import merge revision %d') % r)
1620 1620 if lastparent and lastparent != r:
1621 1621 raise util.Abort(_('revision %d is not the parent of %d')
1622 1622 % (r, lastparent))
1623 1623 lastparent = p1
1624 1624
1625 1625 if not patchname:
1626 1626 patchname = normname('%d.diff' % r)
1627 1627 self.check_reserved_name(patchname)
1628 1628 checkseries(patchname)
1629 1629 checkfile(patchname)
1630 1630 self.full_series.insert(0, patchname)
1631 1631
1632 1632 patchf = self.opener(patchname, "w")
1633 1633 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1634 1634 patchf.close()
1635 1635
1636 1636 se = statusentry(hex(n), patchname)
1637 1637 self.applied.insert(0, se)
1638 1638
1639 1639 added.append(patchname)
1640 1640 patchname = None
1641 1641 self.parse_series()
1642 1642 self.applied_dirty = 1
1643 1643
1644 1644 for filename in files:
1645 1645 if existing:
1646 1646 if filename == '-':
1647 1647 raise util.Abort(_('-e is incompatible with import from -'))
1648 1648 if not patchname:
1649 1649 patchname = normname(filename)
1650 1650 self.check_reserved_name(patchname)
1651 1651 if not os.path.isfile(self.join(patchname)):
1652 1652 raise util.Abort(_("patch %s does not exist") % patchname)
1653 1653 else:
1654 1654 try:
1655 1655 if filename == '-':
1656 1656 if not patchname:
1657 1657 raise util.Abort(_('need --name to import a patch from -'))
1658 1658 text = sys.stdin.read()
1659 1659 else:
1660 1660 text = url.open(self.ui, filename).read()
1661 1661 except (OSError, IOError):
1662 1662 raise util.Abort(_("unable to read %s") % filename)
1663 1663 if not patchname:
1664 1664 patchname = normname(os.path.basename(filename))
1665 1665 self.check_reserved_name(patchname)
1666 1666 checkfile(patchname)
1667 1667 patchf = self.opener(patchname, "w")
1668 1668 patchf.write(text)
1669 1669 if not force:
1670 1670 checkseries(patchname)
1671 1671 if patchname not in self.series:
1672 1672 index = self.full_series_end() + i
1673 1673 self.full_series[index:index] = [patchname]
1674 1674 self.parse_series()
1675 1675 self.ui.warn(_("adding %s to series file\n") % patchname)
1676 1676 i += 1
1677 1677 added.append(patchname)
1678 1678 patchname = None
1679 1679 self.series_dirty = 1
1680 1680 qrepo = self.qrepo()
1681 1681 if qrepo:
1682 1682 qrepo.add(added)
1683 1683
1684 1684 def delete(ui, repo, *patches, **opts):
1685 1685 """remove patches from queue
1686 1686
1687 1687 The patches must not be applied, and at least one patch is required. With
1688 1688 -k/--keep, the patch files are preserved in the patch directory.
1689 1689
1690 1690 To stop managing a patch and move it into permanent history,
1691 1691 use the qfinish command."""
1692 1692 q = repo.mq
1693 1693 q.delete(repo, patches, opts)
1694 1694 q.save_dirty()
1695 1695 return 0
1696 1696
1697 1697 def applied(ui, repo, patch=None, **opts):
1698 1698 """print the patches already applied"""
1699 1699
1700 1700 q = repo.mq
1701 1701 l = len(q.applied)
1702 1702
1703 1703 if patch:
1704 1704 if patch not in q.series:
1705 1705 raise util.Abort(_("patch %s is not in series file") % patch)
1706 1706 end = q.series.index(patch) + 1
1707 1707 else:
1708 1708 end = q.series_end(True)
1709 1709
1710 1710 if opts.get('last') and not end:
1711 1711 ui.write(_("no patches applied\n"))
1712 1712 return 1
1713 1713 elif opts.get('last') and end == 1:
1714 1714 ui.write(_("only one patch applied\n"))
1715 1715 return 1
1716 1716 elif opts.get('last'):
1717 1717 start = end - 2
1718 1718 end = 1
1719 1719 else:
1720 1720 start = 0
1721 1721
1722 1722 return q.qseries(repo, length=end, start=start, status='A',
1723 1723 summary=opts.get('summary'))
1724 1724
1725 1725 def unapplied(ui, repo, patch=None, **opts):
1726 1726 """print the patches not yet applied"""
1727 1727
1728 1728 q = repo.mq
1729 1729 if patch:
1730 1730 if patch not in q.series:
1731 1731 raise util.Abort(_("patch %s is not in series file") % patch)
1732 1732 start = q.series.index(patch) + 1
1733 1733 else:
1734 1734 start = q.series_end(True)
1735 1735
1736 1736 if start == len(q.series) and opts.get('first'):
1737 1737 ui.write(_("all patches applied\n"))
1738 1738 return 1
1739 1739
1740 1740 length = opts.get('first') and 1 or None
1741 1741 return q.qseries(repo, start=start, length=length, status='U',
1742 1742 summary=opts.get('summary'))
1743 1743
1744 1744 def qimport(ui, repo, *filename, **opts):
1745 1745 """import a patch
1746 1746
1747 1747 The patch is inserted into the series after the last applied
1748 1748 patch. If no patches have been applied, qimport prepends the patch
1749 1749 to the series.
1750 1750
1751 1751 The patch will have the same name as its source file unless you
1752 1752 give it a new one with -n/--name.
1753 1753
1754 1754 You can register an existing patch inside the patch directory with
1755 1755 the -e/--existing flag.
1756 1756
1757 1757 With -f/--force, an existing patch of the same name will be
1758 1758 overwritten.
1759 1759
1760 1760 An existing changeset may be placed under mq control with -r/--rev
1761 1761 (e.g. qimport --rev tip -n patch will place tip under mq control).
1762 1762 With -g/--git, patches imported with --rev will use the git diff
1763 1763 format. See the diffs help topic for information on why this is
1764 1764 important for preserving rename/copy information and permission
1765 1765 changes.
1766 1766
1767 1767 To import a patch from standard input, pass - as the patch file.
1768 1768 When importing from standard input, a patch name must be specified
1769 1769 using the --name flag.
1770 1770 """
1771 1771 q = repo.mq
1772 1772 q.qimport(repo, filename, patchname=opts['name'],
1773 1773 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1774 1774 git=opts['git'])
1775 1775 q.save_dirty()
1776 1776
1777 1777 if opts.get('push') and not opts.get('rev'):
1778 1778 return q.push(repo, None)
1779 1779 return 0
1780 1780
1781 1781 def init(ui, repo, **opts):
1782 1782 """init a new queue repository
1783 1783
1784 1784 The queue repository is unversioned by default. If
1785 1785 -c/--create-repo is specified, qinit will create a separate nested
1786 1786 repository for patches (qinit -c may also be run later to convert
1787 1787 an unversioned patch repository into a versioned one). You can use
1788 1788 qcommit to commit changes to this queue repository."""
1789 1789 q = repo.mq
1790 1790 r = q.init(repo, create=opts['create_repo'])
1791 1791 q.save_dirty()
1792 1792 if r:
1793 1793 if not os.path.exists(r.wjoin('.hgignore')):
1794 1794 fp = r.wopener('.hgignore', 'w')
1795 1795 fp.write('^\\.hg\n')
1796 1796 fp.write('^\\.mq\n')
1797 1797 fp.write('syntax: glob\n')
1798 1798 fp.write('status\n')
1799 1799 fp.write('guards\n')
1800 1800 fp.close()
1801 1801 if not os.path.exists(r.wjoin('series')):
1802 1802 r.wopener('series', 'w').close()
1803 1803 r.add(['.hgignore', 'series'])
1804 1804 commands.add(ui, r)
1805 1805 return 0
1806 1806
1807 1807 def clone(ui, source, dest=None, **opts):
1808 1808 '''clone main and patch repository at same time
1809 1809
1810 1810 If source is local, destination will have no patches applied. If
1811 1811 source is remote, this command can not check if patches are
1812 1812 applied in source, so cannot guarantee that patches are not
1813 1813 applied in destination. If you clone remote repository, be sure
1814 1814 before that it has no patches applied.
1815 1815
1816 1816 Source patch repository is looked for in <src>/.hg/patches by
1817 1817 default. Use -p <url> to change.
1818 1818
1819 1819 The patch directory must be a nested Mercurial repository, as
1820 1820 would be created by qinit -c.
1821 1821 '''
1822 1822 def patchdir(repo):
1823 1823 url = repo.url()
1824 1824 if url.endswith('/'):
1825 1825 url = url[:-1]
1826 1826 return url + '/.hg/patches'
1827 1827 if dest is None:
1828 1828 dest = hg.defaultdest(source)
1829 1829 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1830 1830 if opts['patches']:
1831 1831 patchespath = ui.expandpath(opts['patches'])
1832 1832 else:
1833 1833 patchespath = patchdir(sr)
1834 1834 try:
1835 1835 hg.repository(ui, patchespath)
1836 1836 except error.RepoError:
1837 1837 raise util.Abort(_('versioned patch repository not found'
1838 1838 ' (see qinit -c)'))
1839 1839 qbase, destrev = None, None
1840 1840 if sr.local():
1841 1841 if sr.mq.applied:
1842 1842 qbase = bin(sr.mq.applied[0].rev)
1843 1843 if not hg.islocal(dest):
1844 1844 heads = set(sr.heads())
1845 1845 destrev = list(heads.difference(sr.heads(qbase)))
1846 1846 destrev.append(sr.changelog.parents(qbase)[0])
1847 1847 elif sr.capable('lookup'):
1848 1848 try:
1849 1849 qbase = sr.lookup('qbase')
1850 1850 except error.RepoError:
1851 1851 pass
1852 1852 ui.note(_('cloning main repository\n'))
1853 1853 sr, dr = hg.clone(ui, sr.url(), dest,
1854 1854 pull=opts['pull'],
1855 1855 rev=destrev,
1856 1856 update=False,
1857 1857 stream=opts['uncompressed'])
1858 1858 ui.note(_('cloning patch repository\n'))
1859 1859 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1860 1860 pull=opts['pull'], update=not opts['noupdate'],
1861 1861 stream=opts['uncompressed'])
1862 1862 if dr.local():
1863 1863 if qbase:
1864 1864 ui.note(_('stripping applied patches from destination '
1865 1865 'repository\n'))
1866 1866 dr.mq.strip(dr, qbase, update=False, backup=None)
1867 1867 if not opts['noupdate']:
1868 1868 ui.note(_('updating destination repository\n'))
1869 1869 hg.update(dr, dr.changelog.tip())
1870 1870
1871 1871 def commit(ui, repo, *pats, **opts):
1872 1872 """commit changes in the queue repository"""
1873 1873 q = repo.mq
1874 1874 r = q.qrepo()
1875 1875 if not r: raise util.Abort('no queue repository')
1876 1876 commands.commit(r.ui, r, *pats, **opts)
1877 1877
1878 1878 def series(ui, repo, **opts):
1879 1879 """print the entire series file"""
1880 1880 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1881 1881 return 0
1882 1882
1883 1883 def top(ui, repo, **opts):
1884 1884 """print the name of the current patch"""
1885 1885 q = repo.mq
1886 1886 t = q.applied and q.series_end(True) or 0
1887 1887 if t:
1888 1888 return q.qseries(repo, start=t-1, length=1, status='A',
1889 1889 summary=opts.get('summary'))
1890 1890 else:
1891 1891 ui.write(_("no patches applied\n"))
1892 1892 return 1
1893 1893
1894 1894 def next(ui, repo, **opts):
1895 1895 """print the name of the next patch"""
1896 1896 q = repo.mq
1897 1897 end = q.series_end()
1898 1898 if end == len(q.series):
1899 1899 ui.write(_("all patches applied\n"))
1900 1900 return 1
1901 1901 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1902 1902
1903 1903 def prev(ui, repo, **opts):
1904 1904 """print the name of the previous patch"""
1905 1905 q = repo.mq
1906 1906 l = len(q.applied)
1907 1907 if l == 1:
1908 1908 ui.write(_("only one patch applied\n"))
1909 1909 return 1
1910 1910 if not l:
1911 1911 ui.write(_("no patches applied\n"))
1912 1912 return 1
1913 1913 return q.qseries(repo, start=l-2, length=1, status='A',
1914 1914 summary=opts.get('summary'))
1915 1915
1916 1916 def setupheaderopts(ui, opts):
1917 1917 if not opts.get('user') and opts.get('currentuser'):
1918 1918 opts['user'] = ui.username()
1919 1919 if not opts.get('date') and opts.get('currentdate'):
1920 1920 opts['date'] = "%d %d" % util.makedate()
1921 1921
1922 1922 def new(ui, repo, patch, *args, **opts):
1923 1923 """create a new patch
1924 1924
1925 1925 qnew creates a new patch on top of the currently-applied patch (if
1926 1926 any). It will refuse to run if there are any outstanding changes
1927 1927 unless -f/--force is specified, in which case the patch will be
1928 1928 initialized with them. You may also use -I/--include,
1929 1929 -X/--exclude, and/or a list of files after the patch name to add
1930 1930 only changes to matching files to the new patch, leaving the rest
1931 1931 as uncommitted modifications.
1932 1932
1933 1933 -u/--user and -d/--date can be used to set the (given) user and
1934 1934 date, respectively. -U/--currentuser and -D/--currentdate set user
1935 1935 to current user and date to current date.
1936 1936
1937 1937 -e/--edit, -m/--message or -l/--logfile set the patch header as
1938 1938 well as the commit message. If none is specified, the header is
1939 1939 empty and the commit message is '[mq]: PATCH'.
1940 1940
1941 1941 Use the -g/--git option to keep the patch in the git extended diff
1942 1942 format. Read the diffs help topic for more information on why this
1943 1943 is important for preserving permission changes and copy/rename
1944 1944 information.
1945 1945 """
1946 1946 msg = cmdutil.logmessage(opts)
1947 1947 def getmsg(): return ui.edit(msg, ui.username())
1948 1948 q = repo.mq
1949 1949 opts['msg'] = msg
1950 1950 if opts.get('edit'):
1951 1951 opts['msg'] = getmsg
1952 1952 else:
1953 1953 opts['msg'] = msg
1954 1954 setupheaderopts(ui, opts)
1955 1955 q.new(repo, patch, *args, **opts)
1956 1956 q.save_dirty()
1957 1957 return 0
1958 1958
1959 1959 def refresh(ui, repo, *pats, **opts):
1960 1960 """update the current patch
1961 1961
1962 1962 If any file patterns are provided, the refreshed patch will
1963 1963 contain only the modifications that match those patterns; the
1964 1964 remaining modifications will remain in the working directory.
1965 1965
1966 1966 If -s/--short is specified, files currently included in the patch
1967 1967 will be refreshed just like matched files and remain in the patch.
1968 1968
1969 1969 hg add/remove/copy/rename work as usual, though you might want to
1970 1970 use git-style patches (-g/--git or [diff] git=1) to track copies
1971 1971 and renames. See the diffs help topic for more information on the
1972 1972 git diff format.
1973 1973 """
1974 1974 q = repo.mq
1975 1975 message = cmdutil.logmessage(opts)
1976 1976 if opts['edit']:
1977 1977 if not q.applied:
1978 1978 ui.write(_("no patches applied\n"))
1979 1979 return 1
1980 1980 if message:
1981 1981 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1982 1982 patch = q.applied[-1].name
1983 1983 ph = patchheader(q.join(patch))
1984 1984 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
1985 1985 setupheaderopts(ui, opts)
1986 1986 ret = q.refresh(repo, pats, msg=message, **opts)
1987 1987 q.save_dirty()
1988 1988 return ret
1989 1989
1990 1990 def diff(ui, repo, *pats, **opts):
1991 1991 """diff of the current patch and subsequent modifications
1992 1992
1993 1993 Shows a diff which includes the current patch as well as any
1994 1994 changes which have been made in the working directory since the
1995 1995 last refresh (thus showing what the current patch would become
1996 1996 after a qrefresh).
1997 1997
1998 1998 Use 'hg diff' if you only want to see the changes made since the
1999 1999 last qrefresh, or 'hg export qtip' if you want to see changes made
2000 2000 by the current patch without including changes made since the
2001 2001 qrefresh.
2002 2002 """
2003 2003 repo.mq.diff(repo, pats, opts)
2004 2004 return 0
2005 2005
2006 2006 def fold(ui, repo, *files, **opts):
2007 2007 """fold the named patches into the current patch
2008 2008
2009 2009 Patches must not yet be applied. Each patch will be successively
2010 2010 applied to the current patch in the order given. If all the
2011 2011 patches apply successfully, the current patch will be refreshed
2012 2012 with the new cumulative patch, and the folded patches will be
2013 2013 deleted. With -k/--keep, the folded patch files will not be
2014 2014 removed afterwards.
2015 2015
2016 2016 The header for each folded patch will be concatenated with the
2017 2017 current patch header, separated by a line of '* * *'."""
2018 2018
2019 2019 q = repo.mq
2020 2020
2021 2021 if not files:
2022 2022 raise util.Abort(_('qfold requires at least one patch name'))
2023 2023 if not q.check_toppatch(repo):
2024 2024 raise util.Abort(_('No patches applied'))
2025 2025 q.check_localchanges(repo)
2026 2026
2027 2027 message = cmdutil.logmessage(opts)
2028 2028 if opts['edit']:
2029 2029 if message:
2030 2030 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2031 2031
2032 2032 parent = q.lookup('qtip')
2033 2033 patches = []
2034 2034 messages = []
2035 2035 for f in files:
2036 2036 p = q.lookup(f)
2037 2037 if p in patches or p == parent:
2038 2038 ui.warn(_('Skipping already folded patch %s') % p)
2039 2039 if q.isapplied(p):
2040 2040 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2041 2041 patches.append(p)
2042 2042
2043 2043 for p in patches:
2044 2044 if not message:
2045 2045 ph = patchheader(q.join(p))
2046 2046 if ph.message:
2047 2047 messages.append(ph.message)
2048 2048 pf = q.join(p)
2049 2049 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2050 2050 if not patchsuccess:
2051 2051 raise util.Abort(_('Error folding patch %s') % p)
2052 2052 patch.updatedir(ui, repo, files)
2053 2053
2054 2054 if not message:
2055 2055 ph = patchheader(q.join(parent))
2056 2056 message, user = ph.message, ph.user
2057 2057 for msg in messages:
2058 2058 message.append('* * *')
2059 2059 message.extend(msg)
2060 2060 message = '\n'.join(message)
2061 2061
2062 2062 if opts['edit']:
2063 2063 message = ui.edit(message, user or ui.username())
2064 2064
2065 2065 q.refresh(repo, msg=message)
2066 2066 q.delete(repo, patches, opts)
2067 2067 q.save_dirty()
2068 2068
2069 2069 def goto(ui, repo, patch, **opts):
2070 2070 '''push or pop patches until named patch is at top of stack'''
2071 2071 q = repo.mq
2072 2072 patch = q.lookup(patch)
2073 2073 if q.isapplied(patch):
2074 2074 ret = q.pop(repo, patch, force=opts['force'])
2075 2075 else:
2076 2076 ret = q.push(repo, patch, force=opts['force'])
2077 2077 q.save_dirty()
2078 2078 return ret
2079 2079
2080 2080 def guard(ui, repo, *args, **opts):
2081 2081 '''set or print guards for a patch
2082 2082
2083 2083 Guards control whether a patch can be pushed. A patch with no
2084 2084 guards is always pushed. A patch with a positive guard ("+foo") is
2085 2085 pushed only if the qselect command has activated it. A patch with
2086 2086 a negative guard ("-foo") is never pushed if the qselect command
2087 2087 has activated it.
2088 2088
2089 2089 With no arguments, print the currently active guards.
2090 2090 With arguments, set guards for the named patch.
2091 2091 NOTE: Specifying negative guards now requires '--'.
2092 2092
2093 To set guards on another patch:
2093 To set guards on another patch::
2094
2094 2095 hg qguard -- other.patch +2.6.17 -stable
2095 2096 '''
2096 2097 def status(idx):
2097 2098 guards = q.series_guards[idx] or ['unguarded']
2098 2099 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
2099 2100 q = repo.mq
2100 2101 patch = None
2101 2102 args = list(args)
2102 2103 if opts['list']:
2103 2104 if args or opts['none']:
2104 2105 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2105 2106 for i in xrange(len(q.series)):
2106 2107 status(i)
2107 2108 return
2108 2109 if not args or args[0][0:1] in '-+':
2109 2110 if not q.applied:
2110 2111 raise util.Abort(_('no patches applied'))
2111 2112 patch = q.applied[-1].name
2112 2113 if patch is None and args[0][0:1] not in '-+':
2113 2114 patch = args.pop(0)
2114 2115 if patch is None:
2115 2116 raise util.Abort(_('no patch to work with'))
2116 2117 if args or opts['none']:
2117 2118 idx = q.find_series(patch)
2118 2119 if idx is None:
2119 2120 raise util.Abort(_('no patch named %s') % patch)
2120 2121 q.set_guards(idx, args)
2121 2122 q.save_dirty()
2122 2123 else:
2123 2124 status(q.series.index(q.lookup(patch)))
2124 2125
2125 2126 def header(ui, repo, patch=None):
2126 2127 """print the header of the topmost or specified patch"""
2127 2128 q = repo.mq
2128 2129
2129 2130 if patch:
2130 2131 patch = q.lookup(patch)
2131 2132 else:
2132 2133 if not q.applied:
2133 2134 ui.write('no patches applied\n')
2134 2135 return 1
2135 2136 patch = q.lookup('qtip')
2136 2137 ph = patchheader(repo.mq.join(patch))
2137 2138
2138 2139 ui.write('\n'.join(ph.message) + '\n')
2139 2140
2140 2141 def lastsavename(path):
2141 2142 (directory, base) = os.path.split(path)
2142 2143 names = os.listdir(directory)
2143 2144 namere = re.compile("%s.([0-9]+)" % base)
2144 2145 maxindex = None
2145 2146 maxname = None
2146 2147 for f in names:
2147 2148 m = namere.match(f)
2148 2149 if m:
2149 2150 index = int(m.group(1))
2150 2151 if maxindex is None or index > maxindex:
2151 2152 maxindex = index
2152 2153 maxname = f
2153 2154 if maxname:
2154 2155 return (os.path.join(directory, maxname), maxindex)
2155 2156 return (None, None)
2156 2157
2157 2158 def savename(path):
2158 2159 (last, index) = lastsavename(path)
2159 2160 if last is None:
2160 2161 index = 0
2161 2162 newpath = path + ".%d" % (index + 1)
2162 2163 return newpath
2163 2164
2164 2165 def push(ui, repo, patch=None, **opts):
2165 2166 """push the next patch onto the stack
2166 2167
2167 2168 When -f/--force is applied, all local changes in patched files
2168 2169 will be lost.
2169 2170 """
2170 2171 q = repo.mq
2171 2172 mergeq = None
2172 2173
2173 2174 if opts['merge']:
2174 2175 if opts['name']:
2175 2176 newpath = repo.join(opts['name'])
2176 2177 else:
2177 2178 newpath, i = lastsavename(q.path)
2178 2179 if not newpath:
2179 2180 ui.warn(_("no saved queues found, please use -n\n"))
2180 2181 return 1
2181 2182 mergeq = queue(ui, repo.join(""), newpath)
2182 2183 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2183 2184 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2184 2185 mergeq=mergeq, all=opts.get('all'))
2185 2186 return ret
2186 2187
2187 2188 def pop(ui, repo, patch=None, **opts):
2188 2189 """pop the current patch off the stack
2189 2190
2190 2191 By default, pops off the top of the patch stack. If given a patch
2191 2192 name, keeps popping off patches until the named patch is at the
2192 2193 top of the stack.
2193 2194 """
2194 2195 localupdate = True
2195 2196 if opts['name']:
2196 2197 q = queue(ui, repo.join(""), repo.join(opts['name']))
2197 2198 ui.warn(_('using patch queue: %s\n') % q.path)
2198 2199 localupdate = False
2199 2200 else:
2200 2201 q = repo.mq
2201 2202 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2202 2203 all=opts['all'])
2203 2204 q.save_dirty()
2204 2205 return ret
2205 2206
2206 2207 def rename(ui, repo, patch, name=None, **opts):
2207 2208 """rename a patch
2208 2209
2209 2210 With one argument, renames the current patch to PATCH1.
2210 2211 With two arguments, renames PATCH1 to PATCH2."""
2211 2212
2212 2213 q = repo.mq
2213 2214
2214 2215 if not name:
2215 2216 name = patch
2216 2217 patch = None
2217 2218
2218 2219 if patch:
2219 2220 patch = q.lookup(patch)
2220 2221 else:
2221 2222 if not q.applied:
2222 2223 ui.write(_('no patches applied\n'))
2223 2224 return
2224 2225 patch = q.lookup('qtip')
2225 2226 absdest = q.join(name)
2226 2227 if os.path.isdir(absdest):
2227 2228 name = normname(os.path.join(name, os.path.basename(patch)))
2228 2229 absdest = q.join(name)
2229 2230 if os.path.exists(absdest):
2230 2231 raise util.Abort(_('%s already exists') % absdest)
2231 2232
2232 2233 if name in q.series:
2233 2234 raise util.Abort(_('A patch named %s already exists in the series file') % name)
2234 2235
2235 2236 if ui.verbose:
2236 2237 ui.write('renaming %s to %s\n' % (patch, name))
2237 2238 i = q.find_series(patch)
2238 2239 guards = q.guard_re.findall(q.full_series[i])
2239 2240 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2240 2241 q.parse_series()
2241 2242 q.series_dirty = 1
2242 2243
2243 2244 info = q.isapplied(patch)
2244 2245 if info:
2245 2246 q.applied[info[0]] = statusentry(info[1], name)
2246 2247 q.applied_dirty = 1
2247 2248
2248 2249 util.rename(q.join(patch), absdest)
2249 2250 r = q.qrepo()
2250 2251 if r:
2251 2252 wlock = r.wlock()
2252 2253 try:
2253 2254 if r.dirstate[patch] == 'a':
2254 2255 r.dirstate.forget(patch)
2255 2256 r.dirstate.add(name)
2256 2257 else:
2257 2258 if r.dirstate[name] == 'r':
2258 2259 r.undelete([name])
2259 2260 r.copy(patch, name)
2260 2261 r.remove([patch], False)
2261 2262 finally:
2262 2263 wlock.release()
2263 2264
2264 2265 q.save_dirty()
2265 2266
2266 2267 def restore(ui, repo, rev, **opts):
2267 2268 """restore the queue state saved by a revision"""
2268 2269 rev = repo.lookup(rev)
2269 2270 q = repo.mq
2270 2271 q.restore(repo, rev, delete=opts['delete'],
2271 2272 qupdate=opts['update'])
2272 2273 q.save_dirty()
2273 2274 return 0
2274 2275
2275 2276 def save(ui, repo, **opts):
2276 2277 """save current queue state"""
2277 2278 q = repo.mq
2278 2279 message = cmdutil.logmessage(opts)
2279 2280 ret = q.save(repo, msg=message)
2280 2281 if ret:
2281 2282 return ret
2282 2283 q.save_dirty()
2283 2284 if opts['copy']:
2284 2285 path = q.path
2285 2286 if opts['name']:
2286 2287 newpath = os.path.join(q.basepath, opts['name'])
2287 2288 if os.path.exists(newpath):
2288 2289 if not os.path.isdir(newpath):
2289 2290 raise util.Abort(_('destination %s exists and is not '
2290 2291 'a directory') % newpath)
2291 2292 if not opts['force']:
2292 2293 raise util.Abort(_('destination %s exists, '
2293 2294 'use -f to force') % newpath)
2294 2295 else:
2295 2296 newpath = savename(path)
2296 2297 ui.warn(_("copy %s to %s\n") % (path, newpath))
2297 2298 util.copyfiles(path, newpath)
2298 2299 if opts['empty']:
2299 2300 try:
2300 2301 os.unlink(q.join(q.status_path))
2301 2302 except:
2302 2303 pass
2303 2304 return 0
2304 2305
2305 2306 def strip(ui, repo, rev, **opts):
2306 2307 """strip a revision and all its descendants from the repository
2307 2308
2308 2309 If one of the working directory's parent revisions is stripped, the
2309 2310 working directory will be updated to the parent of the stripped
2310 2311 revision.
2311 2312 """
2312 2313 backup = 'all'
2313 2314 if opts['backup']:
2314 2315 backup = 'strip'
2315 2316 elif opts['nobackup']:
2316 2317 backup = 'none'
2317 2318
2318 2319 rev = repo.lookup(rev)
2319 2320 p = repo.dirstate.parents()
2320 2321 cl = repo.changelog
2321 2322 update = True
2322 2323 if p[0] == nullid:
2323 2324 update = False
2324 2325 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2325 2326 update = False
2326 2327 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2327 2328 update = False
2328 2329
2329 2330 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2330 2331 return 0
2331 2332
2332 2333 def select(ui, repo, *args, **opts):
2333 2334 '''set or print guarded patches to push
2334 2335
2335 2336 Use the qguard command to set or print guards on patch, then use
2336 2337 qselect to tell mq which guards to use. A patch will be pushed if
2337 2338 it has no guards or any positive guards match the currently
2338 2339 selected guard, but will not be pushed if any negative guards
2339 match the current guard. For example:
2340 match the current guard. For example::
2340 2341
2341 2342 qguard foo.patch -stable (negative guard)
2342 2343 qguard bar.patch +stable (positive guard)
2343 2344 qselect stable
2344 2345
2345 2346 This activates the "stable" guard. mq will skip foo.patch (because
2346 2347 it has a negative match) but push bar.patch (because it has a
2347 2348 positive match).
2348 2349
2349 2350 With no arguments, prints the currently active guards.
2350 2351 With one argument, sets the active guard.
2351 2352
2352 2353 Use -n/--none to deactivate guards (no other arguments needed).
2353 2354 When no guards are active, patches with positive guards are
2354 2355 skipped and patches with negative guards are pushed.
2355 2356
2356 2357 qselect can change the guards on applied patches. It does not pop
2357 2358 guarded patches by default. Use --pop to pop back to the last
2358 2359 applied patch that is not guarded. Use --reapply (which implies
2359 2360 --pop) to push back to the current patch afterwards, but skip
2360 2361 guarded patches.
2361 2362
2362 2363 Use -s/--series to print a list of all guards in the series file
2363 2364 (no other arguments needed). Use -v for more information.'''
2364 2365
2365 2366 q = repo.mq
2366 2367 guards = q.active()
2367 2368 if args or opts['none']:
2368 2369 old_unapplied = q.unapplied(repo)
2369 2370 old_guarded = [i for i in xrange(len(q.applied)) if
2370 2371 not q.pushable(i)[0]]
2371 2372 q.set_active(args)
2372 2373 q.save_dirty()
2373 2374 if not args:
2374 2375 ui.status(_('guards deactivated\n'))
2375 2376 if not opts['pop'] and not opts['reapply']:
2376 2377 unapplied = q.unapplied(repo)
2377 2378 guarded = [i for i in xrange(len(q.applied))
2378 2379 if not q.pushable(i)[0]]
2379 2380 if len(unapplied) != len(old_unapplied):
2380 2381 ui.status(_('number of unguarded, unapplied patches has '
2381 2382 'changed from %d to %d\n') %
2382 2383 (len(old_unapplied), len(unapplied)))
2383 2384 if len(guarded) != len(old_guarded):
2384 2385 ui.status(_('number of guarded, applied patches has changed '
2385 2386 'from %d to %d\n') %
2386 2387 (len(old_guarded), len(guarded)))
2387 2388 elif opts['series']:
2388 2389 guards = {}
2389 2390 noguards = 0
2390 2391 for gs in q.series_guards:
2391 2392 if not gs:
2392 2393 noguards += 1
2393 2394 for g in gs:
2394 2395 guards.setdefault(g, 0)
2395 2396 guards[g] += 1
2396 2397 if ui.verbose:
2397 2398 guards['NONE'] = noguards
2398 2399 guards = guards.items()
2399 2400 guards.sort(key=lambda x: x[0][1:])
2400 2401 if guards:
2401 2402 ui.note(_('guards in series file:\n'))
2402 2403 for guard, count in guards:
2403 2404 ui.note('%2d ' % count)
2404 2405 ui.write(guard, '\n')
2405 2406 else:
2406 2407 ui.note(_('no guards in series file\n'))
2407 2408 else:
2408 2409 if guards:
2409 2410 ui.note(_('active guards:\n'))
2410 2411 for g in guards:
2411 2412 ui.write(g, '\n')
2412 2413 else:
2413 2414 ui.write(_('no active guards\n'))
2414 2415 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2415 2416 popped = False
2416 2417 if opts['pop'] or opts['reapply']:
2417 2418 for i in xrange(len(q.applied)):
2418 2419 pushable, reason = q.pushable(i)
2419 2420 if not pushable:
2420 2421 ui.status(_('popping guarded patches\n'))
2421 2422 popped = True
2422 2423 if i == 0:
2423 2424 q.pop(repo, all=True)
2424 2425 else:
2425 2426 q.pop(repo, i-1)
2426 2427 break
2427 2428 if popped:
2428 2429 try:
2429 2430 if reapply:
2430 2431 ui.status(_('reapplying unguarded patches\n'))
2431 2432 q.push(repo, reapply)
2432 2433 finally:
2433 2434 q.save_dirty()
2434 2435
2435 2436 def finish(ui, repo, *revrange, **opts):
2436 2437 """move applied patches into repository history
2437 2438
2438 2439 Finishes the specified revisions (corresponding to applied
2439 2440 patches) by moving them out of mq control into regular repository
2440 2441 history.
2441 2442
2442 2443 Accepts a revision range or the -a/--applied option. If --applied
2443 2444 is specified, all applied mq revisions are removed from mq
2444 2445 control. Otherwise, the given revisions must be at the base of the
2445 2446 stack of applied patches.
2446 2447
2447 2448 This can be especially useful if your changes have been applied to
2448 2449 an upstream repository, or if you are about to push your changes
2449 2450 to upstream.
2450 2451 """
2451 2452 if not opts['applied'] and not revrange:
2452 2453 raise util.Abort(_('no revisions specified'))
2453 2454 elif opts['applied']:
2454 2455 revrange = ('qbase:qtip',) + revrange
2455 2456
2456 2457 q = repo.mq
2457 2458 if not q.applied:
2458 2459 ui.status(_('no patches applied\n'))
2459 2460 return 0
2460 2461
2461 2462 revs = cmdutil.revrange(repo, revrange)
2462 2463 q.finish(repo, revs)
2463 2464 q.save_dirty()
2464 2465 return 0
2465 2466
2466 2467 def reposetup(ui, repo):
2467 2468 class mqrepo(repo.__class__):
2468 2469 @util.propertycache
2469 2470 def mq(self):
2470 2471 return queue(self.ui, self.join(""))
2471 2472
2472 2473 def abort_if_wdir_patched(self, errmsg, force=False):
2473 2474 if self.mq.applied and not force:
2474 2475 parent = hex(self.dirstate.parents()[0])
2475 2476 if parent in [s.rev for s in self.mq.applied]:
2476 2477 raise util.Abort(errmsg)
2477 2478
2478 2479 def commit(self, text="", user=None, date=None, match=None,
2479 2480 force=False, editor=False, extra={}):
2480 2481 self.abort_if_wdir_patched(
2481 2482 _('cannot commit over an applied mq patch'),
2482 2483 force)
2483 2484
2484 2485 return super(mqrepo, self).commit(text, user, date, match, force,
2485 2486 editor, extra)
2486 2487
2487 2488 def push(self, remote, force=False, revs=None):
2488 2489 if self.mq.applied and not force and not revs:
2489 2490 raise util.Abort(_('source has mq patches applied'))
2490 2491 return super(mqrepo, self).push(remote, force, revs)
2491 2492
2492 2493 def _findtags(self):
2493 2494 '''augment tags from base class with patch tags'''
2494 2495 result = super(mqrepo, self)._findtags()
2495 2496
2496 2497 q = self.mq
2497 2498 if not q.applied:
2498 2499 return result
2499 2500
2500 2501 mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
2501 2502
2502 2503 if mqtags[-1][0] not in self.changelog.nodemap:
2503 2504 self.ui.warn(_('mq status file refers to unknown node %s\n')
2504 2505 % short(mqtags[-1][0]))
2505 2506 return result
2506 2507
2507 2508 mqtags.append((mqtags[-1][0], 'qtip'))
2508 2509 mqtags.append((mqtags[0][0], 'qbase'))
2509 2510 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2510 2511 tags = result[0]
2511 2512 for patch in mqtags:
2512 2513 if patch[1] in tags:
2513 2514 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2514 2515 % patch[1])
2515 2516 else:
2516 2517 tags[patch[1]] = patch[0]
2517 2518
2518 2519 return result
2519 2520
2520 2521 def _branchtags(self, partial, lrev):
2521 2522 q = self.mq
2522 2523 if not q.applied:
2523 2524 return super(mqrepo, self)._branchtags(partial, lrev)
2524 2525
2525 2526 cl = self.changelog
2526 2527 qbasenode = bin(q.applied[0].rev)
2527 2528 if qbasenode not in cl.nodemap:
2528 2529 self.ui.warn(_('mq status file refers to unknown node %s\n')
2529 2530 % short(qbasenode))
2530 2531 return super(mqrepo, self)._branchtags(partial, lrev)
2531 2532
2532 2533 qbase = cl.rev(qbasenode)
2533 2534 start = lrev + 1
2534 2535 if start < qbase:
2535 2536 # update the cache (excluding the patches) and save it
2536 2537 self._updatebranchcache(partial, lrev+1, qbase)
2537 2538 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2538 2539 start = qbase
2539 2540 # if start = qbase, the cache is as updated as it should be.
2540 2541 # if start > qbase, the cache includes (part of) the patches.
2541 2542 # we might as well use it, but we won't save it.
2542 2543
2543 2544 # update the cache up to the tip
2544 2545 self._updatebranchcache(partial, start, len(cl))
2545 2546
2546 2547 return partial
2547 2548
2548 2549 if repo.local():
2549 2550 repo.__class__ = mqrepo
2550 2551
2551 2552 def mqimport(orig, ui, repo, *args, **kwargs):
2552 2553 if hasattr(repo, 'abort_if_wdir_patched'):
2553 2554 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2554 2555 kwargs.get('force'))
2555 2556 return orig(ui, repo, *args, **kwargs)
2556 2557
2557 2558 def uisetup(ui):
2558 2559 extensions.wrapcommand(commands.table, 'import', mqimport)
2559 2560
2560 2561 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2561 2562
2562 2563 cmdtable = {
2563 2564 "qapplied":
2564 2565 (applied,
2565 2566 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2566 2567 _('hg qapplied [-1] [-s] [PATCH]')),
2567 2568 "qclone":
2568 2569 (clone,
2569 2570 [('', 'pull', None, _('use pull protocol to copy metadata')),
2570 2571 ('U', 'noupdate', None, _('do not update the new working directories')),
2571 2572 ('', 'uncompressed', None,
2572 2573 _('use uncompressed transfer (fast over LAN)')),
2573 2574 ('p', 'patches', '', _('location of source patch repository')),
2574 2575 ] + commands.remoteopts,
2575 2576 _('hg qclone [OPTION]... SOURCE [DEST]')),
2576 2577 "qcommit|qci":
2577 2578 (commit,
2578 2579 commands.table["^commit|ci"][1],
2579 2580 _('hg qcommit [OPTION]... [FILE]...')),
2580 2581 "^qdiff":
2581 2582 (diff,
2582 2583 commands.diffopts + commands.diffopts2 + commands.walkopts,
2583 2584 _('hg qdiff [OPTION]... [FILE]...')),
2584 2585 "qdelete|qremove|qrm":
2585 2586 (delete,
2586 2587 [('k', 'keep', None, _('keep patch file')),
2587 2588 ('r', 'rev', [], _('stop managing a revision (DEPRECATED)'))],
2588 2589 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2589 2590 'qfold':
2590 2591 (fold,
2591 2592 [('e', 'edit', None, _('edit patch header')),
2592 2593 ('k', 'keep', None, _('keep folded patch files')),
2593 2594 ] + commands.commitopts,
2594 2595 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2595 2596 'qgoto':
2596 2597 (goto,
2597 2598 [('f', 'force', None, _('overwrite any local changes'))],
2598 2599 _('hg qgoto [OPTION]... PATCH')),
2599 2600 'qguard':
2600 2601 (guard,
2601 2602 [('l', 'list', None, _('list all patches and guards')),
2602 2603 ('n', 'none', None, _('drop all guards'))],
2603 2604 _('hg qguard [-l] [-n] -- [PATCH] [+GUARD]... [-GUARD]...')),
2604 2605 'qheader': (header, [], _('hg qheader [PATCH]')),
2605 2606 "^qimport":
2606 2607 (qimport,
2607 2608 [('e', 'existing', None, _('import file in patch directory')),
2608 2609 ('n', 'name', '', _('name of patch file')),
2609 2610 ('f', 'force', None, _('overwrite existing files')),
2610 2611 ('r', 'rev', [], _('place existing revisions under mq control')),
2611 2612 ('g', 'git', None, _('use git extended diff format')),
2612 2613 ('P', 'push', None, _('qpush after importing'))],
2613 2614 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2614 2615 "^qinit":
2615 2616 (init,
2616 2617 [('c', 'create-repo', None, _('create queue repository'))],
2617 2618 _('hg qinit [-c]')),
2618 2619 "qnew":
2619 2620 (new,
2620 2621 [('e', 'edit', None, _('edit commit message')),
2621 2622 ('f', 'force', None, _('import uncommitted changes into patch')),
2622 2623 ('g', 'git', None, _('use git extended diff format')),
2623 2624 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2624 2625 ('u', 'user', '', _('add "From: <given user>" to patch')),
2625 2626 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2626 2627 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2627 2628 ] + commands.walkopts + commands.commitopts,
2628 2629 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2629 2630 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2630 2631 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2631 2632 "^qpop":
2632 2633 (pop,
2633 2634 [('a', 'all', None, _('pop all patches')),
2634 2635 ('n', 'name', '', _('queue name to pop')),
2635 2636 ('f', 'force', None, _('forget any local changes to patched files'))],
2636 2637 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2637 2638 "^qpush":
2638 2639 (push,
2639 2640 [('f', 'force', None, _('apply if the patch has rejects')),
2640 2641 ('l', 'list', None, _('list patch name in commit text')),
2641 2642 ('a', 'all', None, _('apply all patches')),
2642 2643 ('m', 'merge', None, _('merge from another queue')),
2643 2644 ('n', 'name', '', _('merge queue name'))],
2644 2645 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2645 2646 "^qrefresh":
2646 2647 (refresh,
2647 2648 [('e', 'edit', None, _('edit commit message')),
2648 2649 ('g', 'git', None, _('use git extended diff format')),
2649 2650 ('s', 'short', None, _('refresh only files already in the patch and specified files')),
2650 2651 ('U', 'currentuser', None, _('add/update author field in patch with current user')),
2651 2652 ('u', 'user', '', _('add/update author field in patch with given user')),
2652 2653 ('D', 'currentdate', None, _('add/update date field in patch with current date')),
2653 2654 ('d', 'date', '', _('add/update date field in patch with given date'))
2654 2655 ] + commands.walkopts + commands.commitopts,
2655 2656 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2656 2657 'qrename|qmv':
2657 2658 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2658 2659 "qrestore":
2659 2660 (restore,
2660 2661 [('d', 'delete', None, _('delete save entry')),
2661 2662 ('u', 'update', None, _('update queue working directory'))],
2662 2663 _('hg qrestore [-d] [-u] REV')),
2663 2664 "qsave":
2664 2665 (save,
2665 2666 [('c', 'copy', None, _('copy patch directory')),
2666 2667 ('n', 'name', '', _('copy directory name')),
2667 2668 ('e', 'empty', None, _('clear queue status file')),
2668 2669 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2669 2670 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2670 2671 "qselect":
2671 2672 (select,
2672 2673 [('n', 'none', None, _('disable all guards')),
2673 2674 ('s', 'series', None, _('list all guards in series file')),
2674 2675 ('', 'pop', None, _('pop to before first guarded applied patch')),
2675 2676 ('', 'reapply', None, _('pop, then reapply patches'))],
2676 2677 _('hg qselect [OPTION]... [GUARD]...')),
2677 2678 "qseries":
2678 2679 (series,
2679 2680 [('m', 'missing', None, _('print patches not in series')),
2680 2681 ] + seriesopts,
2681 2682 _('hg qseries [-ms]')),
2682 2683 "^strip":
2683 2684 (strip,
2684 2685 [('f', 'force', None, _('force removal with local changes')),
2685 2686 ('b', 'backup', None, _('bundle unrelated changesets')),
2686 2687 ('n', 'nobackup', None, _('no backups'))],
2687 2688 _('hg strip [-f] [-b] [-n] REV')),
2688 2689 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2689 2690 "qunapplied":
2690 2691 (unapplied,
2691 2692 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2692 2693 _('hg qunapplied [-1] [-s] [PATCH]')),
2693 2694 "qfinish":
2694 2695 (finish,
2695 2696 [('a', 'applied', None, _('finish all applied changesets'))],
2696 2697 _('hg qfinish [-a] [REV]...')),
2697 2698 }
General Comments 0
You need to be logged in to leave comments. Login now