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