##// END OF EJS Templates
mq: do not call ui.username unless it is necessary...
Martin Geisler -
r9733:f16ec85f default
parent child Browse files
Show More
@@ -1,2698 +1,2697
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 def do(opt, val):
1918 if not opts[opt] and opts['current' + opt]:
1919 opts[opt] = val
1920 do('user', ui.username())
1921 do('date', "%d %d" % util.makedate())
1917 if not opts.get('user') and opts.get('currentuser'):
1918 opts['user'] = ui.username()
1919 if not opts.get('date') and opts.get('currentdate'):
1920 opts['date'] = "%d %d" % util.makedate()
1922 1921
1923 1922 def new(ui, repo, patch, *args, **opts):
1924 1923 """create a new patch
1925 1924
1926 1925 qnew creates a new patch on top of the currently-applied patch (if
1927 1926 any). It will refuse to run if there are any outstanding changes
1928 1927 unless -f/--force is specified, in which case the patch will be
1929 1928 initialized with them. You may also use -I/--include,
1930 1929 -X/--exclude, and/or a list of files after the patch name to add
1931 1930 only changes to matching files to the new patch, leaving the rest
1932 1931 as uncommitted modifications.
1933 1932
1934 1933 -u/--user and -d/--date can be used to set the (given) user and
1935 1934 date, respectively. -U/--currentuser and -D/--currentdate set user
1936 1935 to current user and date to current date.
1937 1936
1938 1937 -e/--edit, -m/--message or -l/--logfile set the patch header as
1939 1938 well as the commit message. If none is specified, the header is
1940 1939 empty and the commit message is '[mq]: PATCH'.
1941 1940
1942 1941 Use the -g/--git option to keep the patch in the git extended diff
1943 1942 format. Read the diffs help topic for more information on why this
1944 1943 is important for preserving permission changes and copy/rename
1945 1944 information.
1946 1945 """
1947 1946 msg = cmdutil.logmessage(opts)
1948 1947 def getmsg(): return ui.edit(msg, ui.username())
1949 1948 q = repo.mq
1950 1949 opts['msg'] = msg
1951 1950 if opts.get('edit'):
1952 1951 opts['msg'] = getmsg
1953 1952 else:
1954 1953 opts['msg'] = msg
1955 1954 setupheaderopts(ui, opts)
1956 1955 q.new(repo, patch, *args, **opts)
1957 1956 q.save_dirty()
1958 1957 return 0
1959 1958
1960 1959 def refresh(ui, repo, *pats, **opts):
1961 1960 """update the current patch
1962 1961
1963 1962 If any file patterns are provided, the refreshed patch will
1964 1963 contain only the modifications that match those patterns; the
1965 1964 remaining modifications will remain in the working directory.
1966 1965
1967 1966 If -s/--short is specified, files currently included in the patch
1968 1967 will be refreshed just like matched files and remain in the patch.
1969 1968
1970 1969 hg add/remove/copy/rename work as usual, though you might want to
1971 1970 use git-style patches (-g/--git or [diff] git=1) to track copies
1972 1971 and renames. See the diffs help topic for more information on the
1973 1972 git diff format.
1974 1973 """
1975 1974 q = repo.mq
1976 1975 message = cmdutil.logmessage(opts)
1977 1976 if opts['edit']:
1978 1977 if not q.applied:
1979 1978 ui.write(_("no patches applied\n"))
1980 1979 return 1
1981 1980 if message:
1982 1981 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1983 1982 patch = q.applied[-1].name
1984 1983 ph = patchheader(q.join(patch))
1985 1984 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
1986 1985 setupheaderopts(ui, opts)
1987 1986 ret = q.refresh(repo, pats, msg=message, **opts)
1988 1987 q.save_dirty()
1989 1988 return ret
1990 1989
1991 1990 def diff(ui, repo, *pats, **opts):
1992 1991 """diff of the current patch and subsequent modifications
1993 1992
1994 1993 Shows a diff which includes the current patch as well as any
1995 1994 changes which have been made in the working directory since the
1996 1995 last refresh (thus showing what the current patch would become
1997 1996 after a qrefresh).
1998 1997
1999 1998 Use 'hg diff' if you only want to see the changes made since the
2000 1999 last qrefresh, or 'hg export qtip' if you want to see changes made
2001 2000 by the current patch without including changes made since the
2002 2001 qrefresh.
2003 2002 """
2004 2003 repo.mq.diff(repo, pats, opts)
2005 2004 return 0
2006 2005
2007 2006 def fold(ui, repo, *files, **opts):
2008 2007 """fold the named patches into the current patch
2009 2008
2010 2009 Patches must not yet be applied. Each patch will be successively
2011 2010 applied to the current patch in the order given. If all the
2012 2011 patches apply successfully, the current patch will be refreshed
2013 2012 with the new cumulative patch, and the folded patches will be
2014 2013 deleted. With -k/--keep, the folded patch files will not be
2015 2014 removed afterwards.
2016 2015
2017 2016 The header for each folded patch will be concatenated with the
2018 2017 current patch header, separated by a line of '* * *'."""
2019 2018
2020 2019 q = repo.mq
2021 2020
2022 2021 if not files:
2023 2022 raise util.Abort(_('qfold requires at least one patch name'))
2024 2023 if not q.check_toppatch(repo):
2025 2024 raise util.Abort(_('No patches applied'))
2026 2025 q.check_localchanges(repo)
2027 2026
2028 2027 message = cmdutil.logmessage(opts)
2029 2028 if opts['edit']:
2030 2029 if message:
2031 2030 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2032 2031
2033 2032 parent = q.lookup('qtip')
2034 2033 patches = []
2035 2034 messages = []
2036 2035 for f in files:
2037 2036 p = q.lookup(f)
2038 2037 if p in patches or p == parent:
2039 2038 ui.warn(_('Skipping already folded patch %s') % p)
2040 2039 if q.isapplied(p):
2041 2040 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2042 2041 patches.append(p)
2043 2042
2044 2043 for p in patches:
2045 2044 if not message:
2046 2045 ph = patchheader(q.join(p))
2047 2046 if ph.message:
2048 2047 messages.append(ph.message)
2049 2048 pf = q.join(p)
2050 2049 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2051 2050 if not patchsuccess:
2052 2051 raise util.Abort(_('Error folding patch %s') % p)
2053 2052 patch.updatedir(ui, repo, files)
2054 2053
2055 2054 if not message:
2056 2055 ph = patchheader(q.join(parent))
2057 2056 message, user = ph.message, ph.user
2058 2057 for msg in messages:
2059 2058 message.append('* * *')
2060 2059 message.extend(msg)
2061 2060 message = '\n'.join(message)
2062 2061
2063 2062 if opts['edit']:
2064 2063 message = ui.edit(message, user or ui.username())
2065 2064
2066 2065 q.refresh(repo, msg=message)
2067 2066 q.delete(repo, patches, opts)
2068 2067 q.save_dirty()
2069 2068
2070 2069 def goto(ui, repo, patch, **opts):
2071 2070 '''push or pop patches until named patch is at top of stack'''
2072 2071 q = repo.mq
2073 2072 patch = q.lookup(patch)
2074 2073 if q.isapplied(patch):
2075 2074 ret = q.pop(repo, patch, force=opts['force'])
2076 2075 else:
2077 2076 ret = q.push(repo, patch, force=opts['force'])
2078 2077 q.save_dirty()
2079 2078 return ret
2080 2079
2081 2080 def guard(ui, repo, *args, **opts):
2082 2081 '''set or print guards for a patch
2083 2082
2084 2083 Guards control whether a patch can be pushed. A patch with no
2085 2084 guards is always pushed. A patch with a positive guard ("+foo") is
2086 2085 pushed only if the qselect command has activated it. A patch with
2087 2086 a negative guard ("-foo") is never pushed if the qselect command
2088 2087 has activated it.
2089 2088
2090 2089 With no arguments, print the currently active guards.
2091 2090 With arguments, set guards for the named patch.
2092 2091 NOTE: Specifying negative guards now requires '--'.
2093 2092
2094 2093 To set guards on another patch:
2095 2094 hg qguard -- other.patch +2.6.17 -stable
2096 2095 '''
2097 2096 def status(idx):
2098 2097 guards = q.series_guards[idx] or ['unguarded']
2099 2098 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
2100 2099 q = repo.mq
2101 2100 patch = None
2102 2101 args = list(args)
2103 2102 if opts['list']:
2104 2103 if args or opts['none']:
2105 2104 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2106 2105 for i in xrange(len(q.series)):
2107 2106 status(i)
2108 2107 return
2109 2108 if not args or args[0][0:1] in '-+':
2110 2109 if not q.applied:
2111 2110 raise util.Abort(_('no patches applied'))
2112 2111 patch = q.applied[-1].name
2113 2112 if patch is None and args[0][0:1] not in '-+':
2114 2113 patch = args.pop(0)
2115 2114 if patch is None:
2116 2115 raise util.Abort(_('no patch to work with'))
2117 2116 if args or opts['none']:
2118 2117 idx = q.find_series(patch)
2119 2118 if idx is None:
2120 2119 raise util.Abort(_('no patch named %s') % patch)
2121 2120 q.set_guards(idx, args)
2122 2121 q.save_dirty()
2123 2122 else:
2124 2123 status(q.series.index(q.lookup(patch)))
2125 2124
2126 2125 def header(ui, repo, patch=None):
2127 2126 """print the header of the topmost or specified patch"""
2128 2127 q = repo.mq
2129 2128
2130 2129 if patch:
2131 2130 patch = q.lookup(patch)
2132 2131 else:
2133 2132 if not q.applied:
2134 2133 ui.write('no patches applied\n')
2135 2134 return 1
2136 2135 patch = q.lookup('qtip')
2137 2136 ph = patchheader(repo.mq.join(patch))
2138 2137
2139 2138 ui.write('\n'.join(ph.message) + '\n')
2140 2139
2141 2140 def lastsavename(path):
2142 2141 (directory, base) = os.path.split(path)
2143 2142 names = os.listdir(directory)
2144 2143 namere = re.compile("%s.([0-9]+)" % base)
2145 2144 maxindex = None
2146 2145 maxname = None
2147 2146 for f in names:
2148 2147 m = namere.match(f)
2149 2148 if m:
2150 2149 index = int(m.group(1))
2151 2150 if maxindex is None or index > maxindex:
2152 2151 maxindex = index
2153 2152 maxname = f
2154 2153 if maxname:
2155 2154 return (os.path.join(directory, maxname), maxindex)
2156 2155 return (None, None)
2157 2156
2158 2157 def savename(path):
2159 2158 (last, index) = lastsavename(path)
2160 2159 if last is None:
2161 2160 index = 0
2162 2161 newpath = path + ".%d" % (index + 1)
2163 2162 return newpath
2164 2163
2165 2164 def push(ui, repo, patch=None, **opts):
2166 2165 """push the next patch onto the stack
2167 2166
2168 2167 When -f/--force is applied, all local changes in patched files
2169 2168 will be lost.
2170 2169 """
2171 2170 q = repo.mq
2172 2171 mergeq = None
2173 2172
2174 2173 if opts['merge']:
2175 2174 if opts['name']:
2176 2175 newpath = repo.join(opts['name'])
2177 2176 else:
2178 2177 newpath, i = lastsavename(q.path)
2179 2178 if not newpath:
2180 2179 ui.warn(_("no saved queues found, please use -n\n"))
2181 2180 return 1
2182 2181 mergeq = queue(ui, repo.join(""), newpath)
2183 2182 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2184 2183 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2185 2184 mergeq=mergeq, all=opts.get('all'))
2186 2185 return ret
2187 2186
2188 2187 def pop(ui, repo, patch=None, **opts):
2189 2188 """pop the current patch off the stack
2190 2189
2191 2190 By default, pops off the top of the patch stack. If given a patch
2192 2191 name, keeps popping off patches until the named patch is at the
2193 2192 top of the stack.
2194 2193 """
2195 2194 localupdate = True
2196 2195 if opts['name']:
2197 2196 q = queue(ui, repo.join(""), repo.join(opts['name']))
2198 2197 ui.warn(_('using patch queue: %s\n') % q.path)
2199 2198 localupdate = False
2200 2199 else:
2201 2200 q = repo.mq
2202 2201 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2203 2202 all=opts['all'])
2204 2203 q.save_dirty()
2205 2204 return ret
2206 2205
2207 2206 def rename(ui, repo, patch, name=None, **opts):
2208 2207 """rename a patch
2209 2208
2210 2209 With one argument, renames the current patch to PATCH1.
2211 2210 With two arguments, renames PATCH1 to PATCH2."""
2212 2211
2213 2212 q = repo.mq
2214 2213
2215 2214 if not name:
2216 2215 name = patch
2217 2216 patch = None
2218 2217
2219 2218 if patch:
2220 2219 patch = q.lookup(patch)
2221 2220 else:
2222 2221 if not q.applied:
2223 2222 ui.write(_('no patches applied\n'))
2224 2223 return
2225 2224 patch = q.lookup('qtip')
2226 2225 absdest = q.join(name)
2227 2226 if os.path.isdir(absdest):
2228 2227 name = normname(os.path.join(name, os.path.basename(patch)))
2229 2228 absdest = q.join(name)
2230 2229 if os.path.exists(absdest):
2231 2230 raise util.Abort(_('%s already exists') % absdest)
2232 2231
2233 2232 if name in q.series:
2234 2233 raise util.Abort(_('A patch named %s already exists in the series file') % name)
2235 2234
2236 2235 if ui.verbose:
2237 2236 ui.write('renaming %s to %s\n' % (patch, name))
2238 2237 i = q.find_series(patch)
2239 2238 guards = q.guard_re.findall(q.full_series[i])
2240 2239 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2241 2240 q.parse_series()
2242 2241 q.series_dirty = 1
2243 2242
2244 2243 info = q.isapplied(patch)
2245 2244 if info:
2246 2245 q.applied[info[0]] = statusentry(info[1], name)
2247 2246 q.applied_dirty = 1
2248 2247
2249 2248 util.rename(q.join(patch), absdest)
2250 2249 r = q.qrepo()
2251 2250 if r:
2252 2251 wlock = r.wlock()
2253 2252 try:
2254 2253 if r.dirstate[patch] == 'a':
2255 2254 r.dirstate.forget(patch)
2256 2255 r.dirstate.add(name)
2257 2256 else:
2258 2257 if r.dirstate[name] == 'r':
2259 2258 r.undelete([name])
2260 2259 r.copy(patch, name)
2261 2260 r.remove([patch], False)
2262 2261 finally:
2263 2262 wlock.release()
2264 2263
2265 2264 q.save_dirty()
2266 2265
2267 2266 def restore(ui, repo, rev, **opts):
2268 2267 """restore the queue state saved by a revision"""
2269 2268 rev = repo.lookup(rev)
2270 2269 q = repo.mq
2271 2270 q.restore(repo, rev, delete=opts['delete'],
2272 2271 qupdate=opts['update'])
2273 2272 q.save_dirty()
2274 2273 return 0
2275 2274
2276 2275 def save(ui, repo, **opts):
2277 2276 """save current queue state"""
2278 2277 q = repo.mq
2279 2278 message = cmdutil.logmessage(opts)
2280 2279 ret = q.save(repo, msg=message)
2281 2280 if ret:
2282 2281 return ret
2283 2282 q.save_dirty()
2284 2283 if opts['copy']:
2285 2284 path = q.path
2286 2285 if opts['name']:
2287 2286 newpath = os.path.join(q.basepath, opts['name'])
2288 2287 if os.path.exists(newpath):
2289 2288 if not os.path.isdir(newpath):
2290 2289 raise util.Abort(_('destination %s exists and is not '
2291 2290 'a directory') % newpath)
2292 2291 if not opts['force']:
2293 2292 raise util.Abort(_('destination %s exists, '
2294 2293 'use -f to force') % newpath)
2295 2294 else:
2296 2295 newpath = savename(path)
2297 2296 ui.warn(_("copy %s to %s\n") % (path, newpath))
2298 2297 util.copyfiles(path, newpath)
2299 2298 if opts['empty']:
2300 2299 try:
2301 2300 os.unlink(q.join(q.status_path))
2302 2301 except:
2303 2302 pass
2304 2303 return 0
2305 2304
2306 2305 def strip(ui, repo, rev, **opts):
2307 2306 """strip a revision and all its descendants from the repository
2308 2307
2309 2308 If one of the working directory's parent revisions is stripped, the
2310 2309 working directory will be updated to the parent of the stripped
2311 2310 revision.
2312 2311 """
2313 2312 backup = 'all'
2314 2313 if opts['backup']:
2315 2314 backup = 'strip'
2316 2315 elif opts['nobackup']:
2317 2316 backup = 'none'
2318 2317
2319 2318 rev = repo.lookup(rev)
2320 2319 p = repo.dirstate.parents()
2321 2320 cl = repo.changelog
2322 2321 update = True
2323 2322 if p[0] == nullid:
2324 2323 update = False
2325 2324 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2326 2325 update = False
2327 2326 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2328 2327 update = False
2329 2328
2330 2329 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2331 2330 return 0
2332 2331
2333 2332 def select(ui, repo, *args, **opts):
2334 2333 '''set or print guarded patches to push
2335 2334
2336 2335 Use the qguard command to set or print guards on patch, then use
2337 2336 qselect to tell mq which guards to use. A patch will be pushed if
2338 2337 it has no guards or any positive guards match the currently
2339 2338 selected guard, but will not be pushed if any negative guards
2340 2339 match the current guard. For example:
2341 2340
2342 2341 qguard foo.patch -stable (negative guard)
2343 2342 qguard bar.patch +stable (positive guard)
2344 2343 qselect stable
2345 2344
2346 2345 This activates the "stable" guard. mq will skip foo.patch (because
2347 2346 it has a negative match) but push bar.patch (because it has a
2348 2347 positive match).
2349 2348
2350 2349 With no arguments, prints the currently active guards.
2351 2350 With one argument, sets the active guard.
2352 2351
2353 2352 Use -n/--none to deactivate guards (no other arguments needed).
2354 2353 When no guards are active, patches with positive guards are
2355 2354 skipped and patches with negative guards are pushed.
2356 2355
2357 2356 qselect can change the guards on applied patches. It does not pop
2358 2357 guarded patches by default. Use --pop to pop back to the last
2359 2358 applied patch that is not guarded. Use --reapply (which implies
2360 2359 --pop) to push back to the current patch afterwards, but skip
2361 2360 guarded patches.
2362 2361
2363 2362 Use -s/--series to print a list of all guards in the series file
2364 2363 (no other arguments needed). Use -v for more information.'''
2365 2364
2366 2365 q = repo.mq
2367 2366 guards = q.active()
2368 2367 if args or opts['none']:
2369 2368 old_unapplied = q.unapplied(repo)
2370 2369 old_guarded = [i for i in xrange(len(q.applied)) if
2371 2370 not q.pushable(i)[0]]
2372 2371 q.set_active(args)
2373 2372 q.save_dirty()
2374 2373 if not args:
2375 2374 ui.status(_('guards deactivated\n'))
2376 2375 if not opts['pop'] and not opts['reapply']:
2377 2376 unapplied = q.unapplied(repo)
2378 2377 guarded = [i for i in xrange(len(q.applied))
2379 2378 if not q.pushable(i)[0]]
2380 2379 if len(unapplied) != len(old_unapplied):
2381 2380 ui.status(_('number of unguarded, unapplied patches has '
2382 2381 'changed from %d to %d\n') %
2383 2382 (len(old_unapplied), len(unapplied)))
2384 2383 if len(guarded) != len(old_guarded):
2385 2384 ui.status(_('number of guarded, applied patches has changed '
2386 2385 'from %d to %d\n') %
2387 2386 (len(old_guarded), len(guarded)))
2388 2387 elif opts['series']:
2389 2388 guards = {}
2390 2389 noguards = 0
2391 2390 for gs in q.series_guards:
2392 2391 if not gs:
2393 2392 noguards += 1
2394 2393 for g in gs:
2395 2394 guards.setdefault(g, 0)
2396 2395 guards[g] += 1
2397 2396 if ui.verbose:
2398 2397 guards['NONE'] = noguards
2399 2398 guards = guards.items()
2400 2399 guards.sort(key=lambda x: x[0][1:])
2401 2400 if guards:
2402 2401 ui.note(_('guards in series file:\n'))
2403 2402 for guard, count in guards:
2404 2403 ui.note('%2d ' % count)
2405 2404 ui.write(guard, '\n')
2406 2405 else:
2407 2406 ui.note(_('no guards in series file\n'))
2408 2407 else:
2409 2408 if guards:
2410 2409 ui.note(_('active guards:\n'))
2411 2410 for g in guards:
2412 2411 ui.write(g, '\n')
2413 2412 else:
2414 2413 ui.write(_('no active guards\n'))
2415 2414 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2416 2415 popped = False
2417 2416 if opts['pop'] or opts['reapply']:
2418 2417 for i in xrange(len(q.applied)):
2419 2418 pushable, reason = q.pushable(i)
2420 2419 if not pushable:
2421 2420 ui.status(_('popping guarded patches\n'))
2422 2421 popped = True
2423 2422 if i == 0:
2424 2423 q.pop(repo, all=True)
2425 2424 else:
2426 2425 q.pop(repo, i-1)
2427 2426 break
2428 2427 if popped:
2429 2428 try:
2430 2429 if reapply:
2431 2430 ui.status(_('reapplying unguarded patches\n'))
2432 2431 q.push(repo, reapply)
2433 2432 finally:
2434 2433 q.save_dirty()
2435 2434
2436 2435 def finish(ui, repo, *revrange, **opts):
2437 2436 """move applied patches into repository history
2438 2437
2439 2438 Finishes the specified revisions (corresponding to applied
2440 2439 patches) by moving them out of mq control into regular repository
2441 2440 history.
2442 2441
2443 2442 Accepts a revision range or the -a/--applied option. If --applied
2444 2443 is specified, all applied mq revisions are removed from mq
2445 2444 control. Otherwise, the given revisions must be at the base of the
2446 2445 stack of applied patches.
2447 2446
2448 2447 This can be especially useful if your changes have been applied to
2449 2448 an upstream repository, or if you are about to push your changes
2450 2449 to upstream.
2451 2450 """
2452 2451 if not opts['applied'] and not revrange:
2453 2452 raise util.Abort(_('no revisions specified'))
2454 2453 elif opts['applied']:
2455 2454 revrange = ('qbase:qtip',) + revrange
2456 2455
2457 2456 q = repo.mq
2458 2457 if not q.applied:
2459 2458 ui.status(_('no patches applied\n'))
2460 2459 return 0
2461 2460
2462 2461 revs = cmdutil.revrange(repo, revrange)
2463 2462 q.finish(repo, revs)
2464 2463 q.save_dirty()
2465 2464 return 0
2466 2465
2467 2466 def reposetup(ui, repo):
2468 2467 class mqrepo(repo.__class__):
2469 2468 @util.propertycache
2470 2469 def mq(self):
2471 2470 return queue(self.ui, self.join(""))
2472 2471
2473 2472 def abort_if_wdir_patched(self, errmsg, force=False):
2474 2473 if self.mq.applied and not force:
2475 2474 parent = hex(self.dirstate.parents()[0])
2476 2475 if parent in [s.rev for s in self.mq.applied]:
2477 2476 raise util.Abort(errmsg)
2478 2477
2479 2478 def commit(self, text="", user=None, date=None, match=None,
2480 2479 force=False, editor=False, extra={}):
2481 2480 self.abort_if_wdir_patched(
2482 2481 _('cannot commit over an applied mq patch'),
2483 2482 force)
2484 2483
2485 2484 return super(mqrepo, self).commit(text, user, date, match, force,
2486 2485 editor, extra)
2487 2486
2488 2487 def push(self, remote, force=False, revs=None):
2489 2488 if self.mq.applied and not force and not revs:
2490 2489 raise util.Abort(_('source has mq patches applied'))
2491 2490 return super(mqrepo, self).push(remote, force, revs)
2492 2491
2493 2492 def _findtags(self):
2494 2493 '''augment tags from base class with patch tags'''
2495 2494 result = super(mqrepo, self)._findtags()
2496 2495
2497 2496 q = self.mq
2498 2497 if not q.applied:
2499 2498 return result
2500 2499
2501 2500 mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
2502 2501
2503 2502 if mqtags[-1][0] not in self.changelog.nodemap:
2504 2503 self.ui.warn(_('mq status file refers to unknown node %s\n')
2505 2504 % short(mqtags[-1][0]))
2506 2505 return result
2507 2506
2508 2507 mqtags.append((mqtags[-1][0], 'qtip'))
2509 2508 mqtags.append((mqtags[0][0], 'qbase'))
2510 2509 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2511 2510 tags = result[0]
2512 2511 for patch in mqtags:
2513 2512 if patch[1] in tags:
2514 2513 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2515 2514 % patch[1])
2516 2515 else:
2517 2516 tags[patch[1]] = patch[0]
2518 2517
2519 2518 return result
2520 2519
2521 2520 def _branchtags(self, partial, lrev):
2522 2521 q = self.mq
2523 2522 if not q.applied:
2524 2523 return super(mqrepo, self)._branchtags(partial, lrev)
2525 2524
2526 2525 cl = self.changelog
2527 2526 qbasenode = bin(q.applied[0].rev)
2528 2527 if qbasenode not in cl.nodemap:
2529 2528 self.ui.warn(_('mq status file refers to unknown node %s\n')
2530 2529 % short(qbasenode))
2531 2530 return super(mqrepo, self)._branchtags(partial, lrev)
2532 2531
2533 2532 qbase = cl.rev(qbasenode)
2534 2533 start = lrev + 1
2535 2534 if start < qbase:
2536 2535 # update the cache (excluding the patches) and save it
2537 2536 self._updatebranchcache(partial, lrev+1, qbase)
2538 2537 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2539 2538 start = qbase
2540 2539 # if start = qbase, the cache is as updated as it should be.
2541 2540 # if start > qbase, the cache includes (part of) the patches.
2542 2541 # we might as well use it, but we won't save it.
2543 2542
2544 2543 # update the cache up to the tip
2545 2544 self._updatebranchcache(partial, start, len(cl))
2546 2545
2547 2546 return partial
2548 2547
2549 2548 if repo.local():
2550 2549 repo.__class__ = mqrepo
2551 2550
2552 2551 def mqimport(orig, ui, repo, *args, **kwargs):
2553 2552 if hasattr(repo, 'abort_if_wdir_patched'):
2554 2553 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2555 2554 kwargs.get('force'))
2556 2555 return orig(ui, repo, *args, **kwargs)
2557 2556
2558 2557 def uisetup(ui):
2559 2558 extensions.wrapcommand(commands.table, 'import', mqimport)
2560 2559
2561 2560 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2562 2561
2563 2562 cmdtable = {
2564 2563 "qapplied":
2565 2564 (applied,
2566 2565 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2567 2566 _('hg qapplied [-1] [-s] [PATCH]')),
2568 2567 "qclone":
2569 2568 (clone,
2570 2569 [('', 'pull', None, _('use pull protocol to copy metadata')),
2571 2570 ('U', 'noupdate', None, _('do not update the new working directories')),
2572 2571 ('', 'uncompressed', None,
2573 2572 _('use uncompressed transfer (fast over LAN)')),
2574 2573 ('p', 'patches', '', _('location of source patch repository')),
2575 2574 ] + commands.remoteopts,
2576 2575 _('hg qclone [OPTION]... SOURCE [DEST]')),
2577 2576 "qcommit|qci":
2578 2577 (commit,
2579 2578 commands.table["^commit|ci"][1],
2580 2579 _('hg qcommit [OPTION]... [FILE]...')),
2581 2580 "^qdiff":
2582 2581 (diff,
2583 2582 commands.diffopts + commands.diffopts2 + commands.walkopts,
2584 2583 _('hg qdiff [OPTION]... [FILE]...')),
2585 2584 "qdelete|qremove|qrm":
2586 2585 (delete,
2587 2586 [('k', 'keep', None, _('keep patch file')),
2588 2587 ('r', 'rev', [], _('stop managing a revision (DEPRECATED)'))],
2589 2588 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2590 2589 'qfold':
2591 2590 (fold,
2592 2591 [('e', 'edit', None, _('edit patch header')),
2593 2592 ('k', 'keep', None, _('keep folded patch files')),
2594 2593 ] + commands.commitopts,
2595 2594 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2596 2595 'qgoto':
2597 2596 (goto,
2598 2597 [('f', 'force', None, _('overwrite any local changes'))],
2599 2598 _('hg qgoto [OPTION]... PATCH')),
2600 2599 'qguard':
2601 2600 (guard,
2602 2601 [('l', 'list', None, _('list all patches and guards')),
2603 2602 ('n', 'none', None, _('drop all guards'))],
2604 2603 _('hg qguard [-l] [-n] -- [PATCH] [+GUARD]... [-GUARD]...')),
2605 2604 'qheader': (header, [], _('hg qheader [PATCH]')),
2606 2605 "^qimport":
2607 2606 (qimport,
2608 2607 [('e', 'existing', None, _('import file in patch directory')),
2609 2608 ('n', 'name', '', _('name of patch file')),
2610 2609 ('f', 'force', None, _('overwrite existing files')),
2611 2610 ('r', 'rev', [], _('place existing revisions under mq control')),
2612 2611 ('g', 'git', None, _('use git extended diff format')),
2613 2612 ('P', 'push', None, _('qpush after importing'))],
2614 2613 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2615 2614 "^qinit":
2616 2615 (init,
2617 2616 [('c', 'create-repo', None, _('create queue repository'))],
2618 2617 _('hg qinit [-c]')),
2619 2618 "qnew":
2620 2619 (new,
2621 2620 [('e', 'edit', None, _('edit commit message')),
2622 2621 ('f', 'force', None, _('import uncommitted changes into patch')),
2623 2622 ('g', 'git', None, _('use git extended diff format')),
2624 2623 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2625 2624 ('u', 'user', '', _('add "From: <given user>" to patch')),
2626 2625 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2627 2626 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2628 2627 ] + commands.walkopts + commands.commitopts,
2629 2628 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2630 2629 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2631 2630 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2632 2631 "^qpop":
2633 2632 (pop,
2634 2633 [('a', 'all', None, _('pop all patches')),
2635 2634 ('n', 'name', '', _('queue name to pop')),
2636 2635 ('f', 'force', None, _('forget any local changes to patched files'))],
2637 2636 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2638 2637 "^qpush":
2639 2638 (push,
2640 2639 [('f', 'force', None, _('apply if the patch has rejects')),
2641 2640 ('l', 'list', None, _('list patch name in commit text')),
2642 2641 ('a', 'all', None, _('apply all patches')),
2643 2642 ('m', 'merge', None, _('merge from another queue')),
2644 2643 ('n', 'name', '', _('merge queue name'))],
2645 2644 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2646 2645 "^qrefresh":
2647 2646 (refresh,
2648 2647 [('e', 'edit', None, _('edit commit message')),
2649 2648 ('g', 'git', None, _('use git extended diff format')),
2650 2649 ('s', 'short', None, _('refresh only files already in the patch and specified files')),
2651 2650 ('U', 'currentuser', None, _('add/update author field in patch with current user')),
2652 2651 ('u', 'user', '', _('add/update author field in patch with given user')),
2653 2652 ('D', 'currentdate', None, _('add/update date field in patch with current date')),
2654 2653 ('d', 'date', '', _('add/update date field in patch with given date'))
2655 2654 ] + commands.walkopts + commands.commitopts,
2656 2655 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2657 2656 'qrename|qmv':
2658 2657 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2659 2658 "qrestore":
2660 2659 (restore,
2661 2660 [('d', 'delete', None, _('delete save entry')),
2662 2661 ('u', 'update', None, _('update queue working directory'))],
2663 2662 _('hg qrestore [-d] [-u] REV')),
2664 2663 "qsave":
2665 2664 (save,
2666 2665 [('c', 'copy', None, _('copy patch directory')),
2667 2666 ('n', 'name', '', _('copy directory name')),
2668 2667 ('e', 'empty', None, _('clear queue status file')),
2669 2668 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2670 2669 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2671 2670 "qselect":
2672 2671 (select,
2673 2672 [('n', 'none', None, _('disable all guards')),
2674 2673 ('s', 'series', None, _('list all guards in series file')),
2675 2674 ('', 'pop', None, _('pop to before first guarded applied patch')),
2676 2675 ('', 'reapply', None, _('pop, then reapply patches'))],
2677 2676 _('hg qselect [OPTION]... [GUARD]...')),
2678 2677 "qseries":
2679 2678 (series,
2680 2679 [('m', 'missing', None, _('print patches not in series')),
2681 2680 ] + seriesopts,
2682 2681 _('hg qseries [-ms]')),
2683 2682 "^strip":
2684 2683 (strip,
2685 2684 [('f', 'force', None, _('force removal with local changes')),
2686 2685 ('b', 'backup', None, _('bundle unrelated changesets')),
2687 2686 ('n', 'nobackup', None, _('no backups'))],
2688 2687 _('hg strip [-f] [-b] [-n] REV')),
2689 2688 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2690 2689 "qunapplied":
2691 2690 (unapplied,
2692 2691 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2693 2692 _('hg qunapplied [-1] [-s] [PATCH]')),
2694 2693 "qfinish":
2695 2694 (finish,
2696 2695 [('a', 'applied', None, _('finish all applied changesets'))],
2697 2696 _('hg qfinish [-a] [REV]...')),
2698 2697 }
@@ -1,56 +1,60
1 1 #!/bin/sh
2 2
3 3 echo "[extensions]" >> $HGRCPATH
4 4 echo "mq=" >> $HGRCPATH
5 5
6 6 hg init mq
7 7 cd mq
8 8
9 9 echo a > a
10 10 hg ci -Ama
11 11
12 12 echo '% qnew should refuse bad patch names'
13 13 hg qnew series
14 14 hg qnew status
15 15 hg qnew guards
16 16 hg qnew .hgignore
17 17
18 18 hg qinit -c
19 19
20 20 echo '% qnew with uncommitted changes'
21 21 echo a > somefile
22 22 hg add somefile
23 23 hg qnew uncommitted.patch
24 24 hg st
25 25 hg qseries
26 26 hg revert --no-backup somefile
27 27 rm somefile
28 28
29 29 echo '% qnew implies add'
30 30 hg qnew test.patch
31 31 hg -R .hg/patches st
32 32
33 33 echo '% qnew missing'
34 34 hg qnew missing.patch missing
35 35
36 36 echo '% qnew -m'
37 37 hg qnew -m 'foo bar' mtest.patch
38 38 cat .hg/patches/mtest.patch
39 39
40 40 echo '% qnew twice'
41 41 hg qnew first.patch
42 42 hg qnew first.patch
43 43
44 44 touch ../first.patch
45 45 hg qimport ../first.patch
46 46
47 47 echo '% qnew -f from a subdirectory'
48 48 hg qpop -a
49 49 mkdir d
50 50 cd d
51 51 echo b > b
52 52 hg ci -Am t
53 53 echo b >> b
54 54 hg st
55 55 hg qnew -g -f p
56 56 cat ../.hg/patches/p
57
58 echo '% qnew -u with no username configured'
59 HGUSER= hg qnew -u blue red
60 cat ../.hg/patches/red
@@ -1,34 +1,37
1 1 adding a
2 2 % qnew should refuse bad patch names
3 3 abort: "series" cannot be used as the name of a patch
4 4 abort: "status" cannot be used as the name of a patch
5 5 abort: "guards" cannot be used as the name of a patch
6 6 abort: ".hgignore" cannot be used as the name of a patch
7 7 % qnew with uncommitted changes
8 8 abort: local changes found, refresh first
9 9 A somefile
10 10 % qnew implies add
11 11 A .hgignore
12 12 A series
13 13 A test.patch
14 14 % qnew missing
15 15 abort: missing: No such file or directory
16 16 % qnew -m
17 17 foo bar
18 18
19 19 % qnew twice
20 20 abort: patch "first.patch" already exists
21 21 abort: patch "first.patch" already exists
22 22 % qnew -f from a subdirectory
23 23 popping first.patch
24 24 popping mtest.patch
25 25 popping test.patch
26 26 patch queue now empty
27 27 adding d/b
28 28 M d/b
29 29 diff --git a/d/b b/d/b
30 30 --- a/d/b
31 31 +++ b/d/b
32 32 @@ -1,1 +1,2 @@
33 33 b
34 34 +b
35 % qnew -u with no username configured
36 From: blue
37
General Comments 0
You need to be logged in to leave comments. Login now