##// END OF EJS Templates
mq: bail out if a patch appears more than once in the series file....
Brendan Cully -
r3184:87b7ae30 default
parent child Browse files
Show More
@@ -1,2111 +1,2114 b''
1 1 # queue.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
6 6 # of the GNU General Public License, 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.demandload import *
33 33 from mercurial.i18n import gettext as _
34 34 from mercurial import commands
35 35 demandload(globals(), "os sys re struct traceback errno bz2")
36 36 demandload(globals(), "mercurial:cmdutil,hg,patch,revlog,ui,util")
37 37
38 38 commands.norepo += " qclone qversion"
39 39
40 40 class statusentry:
41 41 def __init__(self, rev, name=None):
42 42 if not name:
43 43 fields = rev.split(':', 1)
44 44 if len(fields) == 2:
45 45 self.rev, self.name = fields
46 46 else:
47 47 self.rev, self.name = None, None
48 48 else:
49 49 self.rev, self.name = rev, name
50 50
51 51 def __str__(self):
52 52 return self.rev + ':' + self.name
53 53
54 54 class queue:
55 55 def __init__(self, ui, path, patchdir=None):
56 56 self.basepath = path
57 57 self.path = patchdir or os.path.join(path, "patches")
58 58 self.opener = util.opener(self.path)
59 59 self.ui = ui
60 60 self.applied = []
61 61 self.full_series = []
62 62 self.applied_dirty = 0
63 63 self.series_dirty = 0
64 64 self.series_path = "series"
65 65 self.status_path = "status"
66 66 self.guards_path = "guards"
67 67 self.active_guards = None
68 68 self.guards_dirty = False
69 69 self._diffopts = None
70 70
71 71 if os.path.exists(self.join(self.series_path)):
72 72 self.full_series = self.opener(self.series_path).read().splitlines()
73 73 self.parse_series()
74 74
75 75 if os.path.exists(self.join(self.status_path)):
76 76 lines = self.opener(self.status_path).read().splitlines()
77 77 self.applied = [statusentry(l) for l in lines]
78 78
79 79 def diffopts(self):
80 80 if self._diffopts is None:
81 81 self._diffopts = patch.diffopts(self.ui)
82 82 return self._diffopts
83 83
84 84 def join(self, *p):
85 85 return os.path.join(self.path, *p)
86 86
87 87 def find_series(self, patch):
88 88 pre = re.compile("(\s*)([^#]+)")
89 89 index = 0
90 90 for l in self.full_series:
91 91 m = pre.match(l)
92 92 if m:
93 93 s = m.group(2)
94 94 s = s.rstrip()
95 95 if s == patch:
96 96 return index
97 97 index += 1
98 98 return None
99 99
100 100 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
101 101
102 102 def parse_series(self):
103 103 self.series = []
104 104 self.series_guards = []
105 105 for l in self.full_series:
106 106 h = l.find('#')
107 107 if h == -1:
108 108 patch = l
109 109 comment = ''
110 110 elif h == 0:
111 111 continue
112 112 else:
113 113 patch = l[:h]
114 114 comment = l[h:]
115 115 patch = patch.strip()
116 116 if patch:
117 if patch in self.series:
118 raise util.Abort(_('%s appears more than once in %s') %
119 (patch, self.join(self.series_path)))
117 120 self.series.append(patch)
118 121 self.series_guards.append(self.guard_re.findall(comment))
119 122
120 123 def check_guard(self, guard):
121 124 bad_chars = '# \t\r\n\f'
122 125 first = guard[0]
123 126 for c in '-+':
124 127 if first == c:
125 128 return (_('guard %r starts with invalid character: %r') %
126 129 (guard, c))
127 130 for c in bad_chars:
128 131 if c in guard:
129 132 return _('invalid character in guard %r: %r') % (guard, c)
130 133
131 134 def set_active(self, guards):
132 135 for guard in guards:
133 136 bad = self.check_guard(guard)
134 137 if bad:
135 138 raise util.Abort(bad)
136 139 guards = dict.fromkeys(guards).keys()
137 140 guards.sort()
138 141 self.ui.debug('active guards: %s\n' % ' '.join(guards))
139 142 self.active_guards = guards
140 143 self.guards_dirty = True
141 144
142 145 def active(self):
143 146 if self.active_guards is None:
144 147 self.active_guards = []
145 148 try:
146 149 guards = self.opener(self.guards_path).read().split()
147 150 except IOError, err:
148 151 if err.errno != errno.ENOENT: raise
149 152 guards = []
150 153 for i, guard in enumerate(guards):
151 154 bad = self.check_guard(guard)
152 155 if bad:
153 156 self.ui.warn('%s:%d: %s\n' %
154 157 (self.join(self.guards_path), i + 1, bad))
155 158 else:
156 159 self.active_guards.append(guard)
157 160 return self.active_guards
158 161
159 162 def set_guards(self, idx, guards):
160 163 for g in guards:
161 164 if len(g) < 2:
162 165 raise util.Abort(_('guard %r too short') % g)
163 166 if g[0] not in '-+':
164 167 raise util.Abort(_('guard %r starts with invalid char') % g)
165 168 bad = self.check_guard(g[1:])
166 169 if bad:
167 170 raise util.Abort(bad)
168 171 drop = self.guard_re.sub('', self.full_series[idx])
169 172 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
170 173 self.parse_series()
171 174 self.series_dirty = True
172 175
173 176 def pushable(self, idx):
174 177 if isinstance(idx, str):
175 178 idx = self.series.index(idx)
176 179 patchguards = self.series_guards[idx]
177 180 if not patchguards:
178 181 return True, None
179 182 default = False
180 183 guards = self.active()
181 184 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
182 185 if exactneg:
183 186 return False, exactneg[0]
184 187 pos = [g for g in patchguards if g[0] == '+']
185 188 exactpos = [g for g in pos if g[1:] in guards]
186 189 if pos:
187 190 if exactpos:
188 191 return True, exactpos[0]
189 192 return False, pos
190 193 return True, ''
191 194
192 195 def explain_pushable(self, idx, all_patches=False):
193 196 write = all_patches and self.ui.write or self.ui.warn
194 197 if all_patches or self.ui.verbose:
195 198 if isinstance(idx, str):
196 199 idx = self.series.index(idx)
197 200 pushable, why = self.pushable(idx)
198 201 if all_patches and pushable:
199 202 if why is None:
200 203 write(_('allowing %s - no guards in effect\n') %
201 204 self.series[idx])
202 205 else:
203 206 if not why:
204 207 write(_('allowing %s - no matching negative guards\n') %
205 208 self.series[idx])
206 209 else:
207 210 write(_('allowing %s - guarded by %r\n') %
208 211 (self.series[idx], why))
209 212 if not pushable:
210 213 if why:
211 214 write(_('skipping %s - guarded by %r\n') %
212 215 (self.series[idx], ' '.join(why)))
213 216 else:
214 217 write(_('skipping %s - no matching guards\n') %
215 218 self.series[idx])
216 219
217 220 def save_dirty(self):
218 221 def write_list(items, path):
219 222 fp = self.opener(path, 'w')
220 223 for i in items:
221 224 print >> fp, i
222 225 fp.close()
223 226 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
224 227 if self.series_dirty: write_list(self.full_series, self.series_path)
225 228 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
226 229
227 230 def readheaders(self, patch):
228 231 def eatdiff(lines):
229 232 while lines:
230 233 l = lines[-1]
231 234 if (l.startswith("diff -") or
232 235 l.startswith("Index:") or
233 236 l.startswith("===========")):
234 237 del lines[-1]
235 238 else:
236 239 break
237 240 def eatempty(lines):
238 241 while lines:
239 242 l = lines[-1]
240 243 if re.match('\s*$', l):
241 244 del lines[-1]
242 245 else:
243 246 break
244 247
245 248 pf = self.join(patch)
246 249 message = []
247 250 comments = []
248 251 user = None
249 252 date = None
250 253 format = None
251 254 subject = None
252 255 diffstart = 0
253 256
254 257 for line in file(pf):
255 258 line = line.rstrip()
256 259 if line.startswith('diff --git'):
257 260 diffstart = 2
258 261 break
259 262 if diffstart:
260 263 if line.startswith('+++ '):
261 264 diffstart = 2
262 265 break
263 266 if line.startswith("--- "):
264 267 diffstart = 1
265 268 continue
266 269 elif format == "hgpatch":
267 270 # parse values when importing the result of an hg export
268 271 if line.startswith("# User "):
269 272 user = line[7:]
270 273 elif line.startswith("# Date "):
271 274 date = line[7:]
272 275 elif not line.startswith("# ") and line:
273 276 message.append(line)
274 277 format = None
275 278 elif line == '# HG changeset patch':
276 279 format = "hgpatch"
277 280 elif (format != "tagdone" and (line.startswith("Subject: ") or
278 281 line.startswith("subject: "))):
279 282 subject = line[9:]
280 283 format = "tag"
281 284 elif (format != "tagdone" and (line.startswith("From: ") or
282 285 line.startswith("from: "))):
283 286 user = line[6:]
284 287 format = "tag"
285 288 elif format == "tag" and line == "":
286 289 # when looking for tags (subject: from: etc) they
287 290 # end once you find a blank line in the source
288 291 format = "tagdone"
289 292 elif message or line:
290 293 message.append(line)
291 294 comments.append(line)
292 295
293 296 eatdiff(message)
294 297 eatdiff(comments)
295 298 eatempty(message)
296 299 eatempty(comments)
297 300
298 301 # make sure message isn't empty
299 302 if format and format.startswith("tag") and subject:
300 303 message.insert(0, "")
301 304 message.insert(0, subject)
302 305 return (message, comments, user, date, diffstart > 1)
303 306
304 307 def printdiff(self, repo, node1, node2=None, files=None,
305 308 fp=None, changes=None, opts={}):
306 309 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
307 310
308 311 patch.diff(repo, node1, node2, fns, match=matchfn,
309 312 fp=fp, changes=changes, opts=self.diffopts())
310 313
311 314 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
312 315 # first try just applying the patch
313 316 (err, n) = self.apply(repo, [ patch ], update_status=False,
314 317 strict=True, merge=rev, wlock=wlock)
315 318
316 319 if err == 0:
317 320 return (err, n)
318 321
319 322 if n is None:
320 323 raise util.Abort(_("apply failed for patch %s") % patch)
321 324
322 325 self.ui.warn("patch didn't work out, merging %s\n" % patch)
323 326
324 327 # apply failed, strip away that rev and merge.
325 328 hg.clean(repo, head, wlock=wlock)
326 329 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
327 330
328 331 c = repo.changelog.read(rev)
329 332 ret = hg.merge(repo, rev, wlock=wlock)
330 333 if ret:
331 334 raise util.Abort(_("update returned %d") % ret)
332 335 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
333 336 if n == None:
334 337 raise util.Abort(_("repo commit failed"))
335 338 try:
336 339 message, comments, user, date, patchfound = mergeq.readheaders(patch)
337 340 except:
338 341 raise util.Abort(_("unable to read %s") % patch)
339 342
340 343 patchf = self.opener(patch, "w")
341 344 if comments:
342 345 comments = "\n".join(comments) + '\n\n'
343 346 patchf.write(comments)
344 347 self.printdiff(repo, head, n, fp=patchf)
345 348 patchf.close()
346 349 return (0, n)
347 350
348 351 def qparents(self, repo, rev=None):
349 352 if rev is None:
350 353 (p1, p2) = repo.dirstate.parents()
351 354 if p2 == revlog.nullid:
352 355 return p1
353 356 if len(self.applied) == 0:
354 357 return None
355 358 return revlog.bin(self.applied[-1].rev)
356 359 pp = repo.changelog.parents(rev)
357 360 if pp[1] != revlog.nullid:
358 361 arevs = [ x.rev for x in self.applied ]
359 362 p0 = revlog.hex(pp[0])
360 363 p1 = revlog.hex(pp[1])
361 364 if p0 in arevs:
362 365 return pp[0]
363 366 if p1 in arevs:
364 367 return pp[1]
365 368 return pp[0]
366 369
367 370 def mergepatch(self, repo, mergeq, series, wlock):
368 371 if len(self.applied) == 0:
369 372 # each of the patches merged in will have two parents. This
370 373 # can confuse the qrefresh, qdiff, and strip code because it
371 374 # needs to know which parent is actually in the patch queue.
372 375 # so, we insert a merge marker with only one parent. This way
373 376 # the first patch in the queue is never a merge patch
374 377 #
375 378 pname = ".hg.patches.merge.marker"
376 379 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
377 380 wlock=wlock)
378 381 self.applied.append(statusentry(revlog.hex(n), pname))
379 382 self.applied_dirty = 1
380 383
381 384 head = self.qparents(repo)
382 385
383 386 for patch in series:
384 387 patch = mergeq.lookup(patch, strict=True)
385 388 if not patch:
386 389 self.ui.warn("patch %s does not exist\n" % patch)
387 390 return (1, None)
388 391 pushable, reason = self.pushable(patch)
389 392 if not pushable:
390 393 self.explain_pushable(patch, all_patches=True)
391 394 continue
392 395 info = mergeq.isapplied(patch)
393 396 if not info:
394 397 self.ui.warn("patch %s is not applied\n" % patch)
395 398 return (1, None)
396 399 rev = revlog.bin(info[1])
397 400 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
398 401 if head:
399 402 self.applied.append(statusentry(revlog.hex(head), patch))
400 403 self.applied_dirty = 1
401 404 if err:
402 405 return (err, head)
403 406 return (0, head)
404 407
405 408 def patch(self, repo, patchfile):
406 409 '''Apply patchfile to the working directory.
407 410 patchfile: file name of patch'''
408 411 try:
409 412 (files, fuzz) = patch.patch(patchfile, self.ui, strip=1,
410 413 cwd=repo.root)
411 414 except Exception, inst:
412 415 self.ui.note(str(inst) + '\n')
413 416 if not self.ui.verbose:
414 417 self.ui.warn("patch failed, unable to continue (try -v)\n")
415 418 return (False, [], False)
416 419
417 420 return (True, files, fuzz)
418 421
419 422 def apply(self, repo, series, list=False, update_status=True,
420 423 strict=False, patchdir=None, merge=None, wlock=None):
421 424 # TODO unify with commands.py
422 425 if not patchdir:
423 426 patchdir = self.path
424 427 err = 0
425 428 if not wlock:
426 429 wlock = repo.wlock()
427 430 lock = repo.lock()
428 431 tr = repo.transaction()
429 432 n = None
430 433 for patchname in series:
431 434 pushable, reason = self.pushable(patchname)
432 435 if not pushable:
433 436 self.explain_pushable(patchname, all_patches=True)
434 437 continue
435 438 self.ui.warn("applying %s\n" % patchname)
436 439 pf = os.path.join(patchdir, patchname)
437 440
438 441 try:
439 442 message, comments, user, date, patchfound = self.readheaders(patchname)
440 443 except:
441 444 self.ui.warn("Unable to read %s\n" % patchname)
442 445 err = 1
443 446 break
444 447
445 448 if not message:
446 449 message = "imported patch %s\n" % patchname
447 450 else:
448 451 if list:
449 452 message.append("\nimported patch %s" % patchname)
450 453 message = '\n'.join(message)
451 454
452 455 (patcherr, files, fuzz) = self.patch(repo, pf)
453 456 patcherr = not patcherr
454 457
455 458 if merge and files:
456 459 # Mark as merged and update dirstate parent info
457 460 repo.dirstate.update(repo.dirstate.filterfiles(files.keys()), 'm')
458 461 p1, p2 = repo.dirstate.parents()
459 462 repo.dirstate.setparents(p1, merge)
460 463 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
461 464 n = repo.commit(files, message, user, date, force=1, lock=lock,
462 465 wlock=wlock)
463 466
464 467 if n == None:
465 468 raise util.Abort(_("repo commit failed"))
466 469
467 470 if update_status:
468 471 self.applied.append(statusentry(revlog.hex(n), patchname))
469 472
470 473 if patcherr:
471 474 if not patchfound:
472 475 self.ui.warn("patch %s is empty\n" % patchname)
473 476 err = 0
474 477 else:
475 478 self.ui.warn("patch failed, rejects left in working dir\n")
476 479 err = 1
477 480 break
478 481
479 482 if fuzz and strict:
480 483 self.ui.warn("fuzz found when applying patch, stopping\n")
481 484 err = 1
482 485 break
483 486 tr.close()
484 487 return (err, n)
485 488
486 489 def delete(self, repo, patches, opts):
487 490 realpatches = []
488 491 appliedbase = 0
489 492 forget = opts.get('forget')
490 493 for patch in patches:
491 494 patch = self.lookup(patch, strict=True)
492 495 info = self.isapplied(patch)
493 496 if info and not forget:
494 497 raise util.Abort(_("cannot delete applied patch %s") % patch)
495 498 if patch not in self.series:
496 499 raise util.Abort(_("patch %s not in series file") % patch)
497 500 if forget:
498 501 if not info:
499 502 raise util.Abort(_("cannot forget unapplied patch %s") % patch)
500 503 if info[0] != appliedbase:
501 504 raise util.Abort(_("patch %s not at base") % patch)
502 505 appliedbase += 1
503 506 realpatches.append(patch)
504 507
505 508 if not opts.get('keep'):
506 509 r = self.qrepo()
507 510 if r:
508 511 r.remove(realpatches, True)
509 512 else:
510 513 os.unlink(self.join(patch))
511 514
512 515 if forget:
513 516 del self.applied[:appliedbase]
514 517 self.applied_dirty = 1
515 518 indices = [self.find_series(p) for p in realpatches]
516 519 indices.sort()
517 520 for i in indices[-1::-1]:
518 521 del self.full_series[i]
519 522 self.parse_series()
520 523 self.series_dirty = 1
521 524
522 525 def check_toppatch(self, repo):
523 526 if len(self.applied) > 0:
524 527 top = revlog.bin(self.applied[-1].rev)
525 528 pp = repo.dirstate.parents()
526 529 if top not in pp:
527 530 raise util.Abort(_("queue top not at same revision as working directory"))
528 531 return top
529 532 return None
530 533 def check_localchanges(self, repo, force=False, refresh=True):
531 534 m, a, r, d = repo.status()[:4]
532 535 if m or a or r or d:
533 536 if not force:
534 537 if refresh:
535 538 raise util.Abort(_("local changes found, refresh first"))
536 539 else:
537 540 raise util.Abort(_("local changes found"))
538 541 return m, a, r, d
539 542 def new(self, repo, patch, msg=None, force=None):
540 543 if os.path.exists(self.join(patch)):
541 544 raise util.Abort(_('patch "%s" already exists') % patch)
542 545 m, a, r, d = self.check_localchanges(repo, force)
543 546 commitfiles = m + a + r
544 547 self.check_toppatch(repo)
545 548 wlock = repo.wlock()
546 549 insert = self.full_series_end()
547 550 if msg:
548 551 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
549 552 wlock=wlock)
550 553 else:
551 554 n = repo.commit(commitfiles,
552 555 "New patch: %s" % patch, force=True, wlock=wlock)
553 556 if n == None:
554 557 raise util.Abort(_("repo commit failed"))
555 558 self.full_series[insert:insert] = [patch]
556 559 self.applied.append(statusentry(revlog.hex(n), patch))
557 560 self.parse_series()
558 561 self.series_dirty = 1
559 562 self.applied_dirty = 1
560 563 p = self.opener(patch, "w")
561 564 if msg:
562 565 msg = msg + "\n"
563 566 p.write(msg)
564 567 p.close()
565 568 wlock = None
566 569 r = self.qrepo()
567 570 if r: r.add([patch])
568 571 if commitfiles:
569 572 self.refresh(repo, short=True)
570 573
571 574 def strip(self, repo, rev, update=True, backup="all", wlock=None):
572 575 def limitheads(chlog, stop):
573 576 """return the list of all nodes that have no children"""
574 577 p = {}
575 578 h = []
576 579 stoprev = 0
577 580 if stop in chlog.nodemap:
578 581 stoprev = chlog.rev(stop)
579 582
580 583 for r in range(chlog.count() - 1, -1, -1):
581 584 n = chlog.node(r)
582 585 if n not in p:
583 586 h.append(n)
584 587 if n == stop:
585 588 break
586 589 if r < stoprev:
587 590 break
588 591 for pn in chlog.parents(n):
589 592 p[pn] = 1
590 593 return h
591 594
592 595 def bundle(cg):
593 596 backupdir = repo.join("strip-backup")
594 597 if not os.path.isdir(backupdir):
595 598 os.mkdir(backupdir)
596 599 name = os.path.join(backupdir, "%s" % revlog.short(rev))
597 600 name = savename(name)
598 601 self.ui.warn("saving bundle to %s\n" % name)
599 602 # TODO, exclusive open
600 603 f = open(name, "wb")
601 604 try:
602 605 f.write("HG10")
603 606 z = bz2.BZ2Compressor(9)
604 607 while 1:
605 608 chunk = cg.read(4096)
606 609 if not chunk:
607 610 break
608 611 f.write(z.compress(chunk))
609 612 f.write(z.flush())
610 613 except:
611 614 os.unlink(name)
612 615 raise
613 616 f.close()
614 617 return name
615 618
616 619 def stripall(rev, revnum):
617 620 cl = repo.changelog
618 621 c = cl.read(rev)
619 622 mm = repo.manifest.read(c[0])
620 623 seen = {}
621 624
622 625 for x in xrange(revnum, cl.count()):
623 626 c = cl.read(cl.node(x))
624 627 for f in c[3]:
625 628 if f in seen:
626 629 continue
627 630 seen[f] = 1
628 631 if f in mm:
629 632 filerev = mm[f]
630 633 else:
631 634 filerev = 0
632 635 seen[f] = filerev
633 636 # we go in two steps here so the strip loop happens in a
634 637 # sensible order. When stripping many files, this helps keep
635 638 # our disk access patterns under control.
636 639 seen_list = seen.keys()
637 640 seen_list.sort()
638 641 for f in seen_list:
639 642 ff = repo.file(f)
640 643 filerev = seen[f]
641 644 if filerev != 0:
642 645 if filerev in ff.nodemap:
643 646 filerev = ff.rev(filerev)
644 647 else:
645 648 filerev = 0
646 649 ff.strip(filerev, revnum)
647 650
648 651 if not wlock:
649 652 wlock = repo.wlock()
650 653 lock = repo.lock()
651 654 chlog = repo.changelog
652 655 # TODO delete the undo files, and handle undo of merge sets
653 656 pp = chlog.parents(rev)
654 657 revnum = chlog.rev(rev)
655 658
656 659 if update:
657 660 self.check_localchanges(repo, refresh=False)
658 661 urev = self.qparents(repo, rev)
659 662 hg.clean(repo, urev, wlock=wlock)
660 663 repo.dirstate.write()
661 664
662 665 # save is a list of all the branches we are truncating away
663 666 # that we actually want to keep. changegroup will be used
664 667 # to preserve them and add them back after the truncate
665 668 saveheads = []
666 669 savebases = {}
667 670
668 671 heads = limitheads(chlog, rev)
669 672 seen = {}
670 673
671 674 # search through all the heads, finding those where the revision
672 675 # we want to strip away is an ancestor. Also look for merges
673 676 # that might be turned into new heads by the strip.
674 677 while heads:
675 678 h = heads.pop()
676 679 n = h
677 680 while True:
678 681 seen[n] = 1
679 682 pp = chlog.parents(n)
680 683 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
681 684 if pp[1] not in seen:
682 685 heads.append(pp[1])
683 686 if pp[0] == revlog.nullid:
684 687 break
685 688 if chlog.rev(pp[0]) < revnum:
686 689 break
687 690 n = pp[0]
688 691 if n == rev:
689 692 break
690 693 r = chlog.reachable(h, rev)
691 694 if rev not in r:
692 695 saveheads.append(h)
693 696 for x in r:
694 697 if chlog.rev(x) > revnum:
695 698 savebases[x] = 1
696 699
697 700 # create a changegroup for all the branches we need to keep
698 701 if backup == "all":
699 702 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
700 703 bundle(backupch)
701 704 if saveheads:
702 705 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
703 706 chgrpfile = bundle(backupch)
704 707
705 708 stripall(rev, revnum)
706 709
707 710 change = chlog.read(rev)
708 711 chlog.strip(revnum, revnum)
709 712 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
710 713 if saveheads:
711 714 self.ui.status("adding branch\n")
712 715 commands.unbundle(self.ui, repo, chgrpfile, update=False)
713 716 if backup != "strip":
714 717 os.unlink(chgrpfile)
715 718
716 719 def isapplied(self, patch):
717 720 """returns (index, rev, patch)"""
718 721 for i in xrange(len(self.applied)):
719 722 a = self.applied[i]
720 723 if a.name == patch:
721 724 return (i, a.rev, a.name)
722 725 return None
723 726
724 727 # if the exact patch name does not exist, we try a few
725 728 # variations. If strict is passed, we try only #1
726 729 #
727 730 # 1) a number to indicate an offset in the series file
728 731 # 2) a unique substring of the patch name was given
729 732 # 3) patchname[-+]num to indicate an offset in the series file
730 733 def lookup(self, patch, strict=False):
731 734 patch = patch and str(patch)
732 735
733 736 def partial_name(s):
734 737 if s in self.series:
735 738 return s
736 739 matches = [x for x in self.series if s in x]
737 740 if len(matches) > 1:
738 741 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
739 742 for m in matches:
740 743 self.ui.warn(' %s\n' % m)
741 744 return None
742 745 if matches:
743 746 return matches[0]
744 747 if len(self.series) > 0 and len(self.applied) > 0:
745 748 if s == 'qtip':
746 749 return self.series[self.series_end()-1]
747 750 if s == 'qbase':
748 751 return self.series[0]
749 752 return None
750 753 if patch == None:
751 754 return None
752 755
753 756 # we don't want to return a partial match until we make
754 757 # sure the file name passed in does not exist (checked below)
755 758 res = partial_name(patch)
756 759 if res and res == patch:
757 760 return res
758 761
759 762 if not os.path.isfile(self.join(patch)):
760 763 try:
761 764 sno = int(patch)
762 765 except(ValueError, OverflowError):
763 766 pass
764 767 else:
765 768 if sno < len(self.series):
766 769 return self.series[sno]
767 770 if not strict:
768 771 # return any partial match made above
769 772 if res:
770 773 return res
771 774 minus = patch.rfind('-')
772 775 if minus >= 0:
773 776 res = partial_name(patch[:minus])
774 777 if res:
775 778 i = self.series.index(res)
776 779 try:
777 780 off = int(patch[minus+1:] or 1)
778 781 except(ValueError, OverflowError):
779 782 pass
780 783 else:
781 784 if i - off >= 0:
782 785 return self.series[i - off]
783 786 plus = patch.rfind('+')
784 787 if plus >= 0:
785 788 res = partial_name(patch[:plus])
786 789 if res:
787 790 i = self.series.index(res)
788 791 try:
789 792 off = int(patch[plus+1:] or 1)
790 793 except(ValueError, OverflowError):
791 794 pass
792 795 else:
793 796 if i + off < len(self.series):
794 797 return self.series[i + off]
795 798 raise util.Abort(_("patch %s not in series") % patch)
796 799
797 800 def push(self, repo, patch=None, force=False, list=False,
798 801 mergeq=None, wlock=None):
799 802 if not wlock:
800 803 wlock = repo.wlock()
801 804 patch = self.lookup(patch)
802 805 if patch and self.isapplied(patch):
803 806 self.ui.warn(_("patch %s is already applied\n") % patch)
804 807 sys.exit(1)
805 808 if self.series_end() == len(self.series):
806 809 self.ui.warn(_("patch series fully applied\n"))
807 810 sys.exit(1)
808 811 if not force:
809 812 self.check_localchanges(repo)
810 813
811 814 self.applied_dirty = 1;
812 815 start = self.series_end()
813 816 if start > 0:
814 817 self.check_toppatch(repo)
815 818 if not patch:
816 819 patch = self.series[start]
817 820 end = start + 1
818 821 else:
819 822 end = self.series.index(patch, start) + 1
820 823 s = self.series[start:end]
821 824 if mergeq:
822 825 ret = self.mergepatch(repo, mergeq, s, wlock)
823 826 else:
824 827 ret = self.apply(repo, s, list, wlock=wlock)
825 828 top = self.applied[-1].name
826 829 if ret[0]:
827 830 self.ui.write("Errors during apply, please fix and refresh %s\n" %
828 831 top)
829 832 else:
830 833 self.ui.write("Now at: %s\n" % top)
831 834 return ret[0]
832 835
833 836 def pop(self, repo, patch=None, force=False, update=True, all=False,
834 837 wlock=None):
835 838 def getfile(f, rev):
836 839 t = repo.file(f).read(rev)
837 840 try:
838 841 repo.wfile(f, "w").write(t)
839 842 except IOError:
840 843 try:
841 844 os.makedirs(os.path.dirname(repo.wjoin(f)))
842 845 except OSError, err:
843 846 if err.errno != errno.EEXIST: raise
844 847 repo.wfile(f, "w").write(t)
845 848
846 849 if not wlock:
847 850 wlock = repo.wlock()
848 851 if patch:
849 852 # index, rev, patch
850 853 info = self.isapplied(patch)
851 854 if not info:
852 855 patch = self.lookup(patch)
853 856 info = self.isapplied(patch)
854 857 if not info:
855 858 raise util.Abort(_("patch %s is not applied") % patch)
856 859 if len(self.applied) == 0:
857 860 self.ui.warn(_("no patches applied\n"))
858 861 sys.exit(1)
859 862
860 863 if not update:
861 864 parents = repo.dirstate.parents()
862 865 rr = [ revlog.bin(x.rev) for x in self.applied ]
863 866 for p in parents:
864 867 if p in rr:
865 868 self.ui.warn("qpop: forcing dirstate update\n")
866 869 update = True
867 870
868 871 if not force and update:
869 872 self.check_localchanges(repo)
870 873
871 874 self.applied_dirty = 1;
872 875 end = len(self.applied)
873 876 if not patch:
874 877 if all:
875 878 popi = 0
876 879 else:
877 880 popi = len(self.applied) - 1
878 881 else:
879 882 popi = info[0] + 1
880 883 if popi >= end:
881 884 self.ui.warn("qpop: %s is already at the top\n" % patch)
882 885 return
883 886 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
884 887
885 888 start = info[0]
886 889 rev = revlog.bin(info[1])
887 890
888 891 # we know there are no local changes, so we can make a simplified
889 892 # form of hg.update.
890 893 if update:
891 894 top = self.check_toppatch(repo)
892 895 qp = self.qparents(repo, rev)
893 896 changes = repo.changelog.read(qp)
894 897 mmap = repo.manifest.read(changes[0])
895 898 m, a, r, d, u = repo.status(qp, top)[:5]
896 899 if d:
897 900 raise util.Abort("deletions found between repo revs")
898 901 for f in m:
899 902 getfile(f, mmap[f])
900 903 for f in r:
901 904 getfile(f, mmap[f])
902 905 util.set_exec(repo.wjoin(f), mmap.execf(f))
903 906 repo.dirstate.update(m + r, 'n')
904 907 for f in a:
905 908 try: os.unlink(repo.wjoin(f))
906 909 except: raise
907 910 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
908 911 except: pass
909 912 if a:
910 913 repo.dirstate.forget(a)
911 914 repo.dirstate.setparents(qp, revlog.nullid)
912 915 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
913 916 del self.applied[start:end]
914 917 if len(self.applied):
915 918 self.ui.write("Now at: %s\n" % self.applied[-1].name)
916 919 else:
917 920 self.ui.write("Patch queue now empty\n")
918 921
919 922 def diff(self, repo, pats, opts):
920 923 top = self.check_toppatch(repo)
921 924 if not top:
922 925 self.ui.write("No patches applied\n")
923 926 return
924 927 qp = self.qparents(repo, top)
925 928 self.printdiff(repo, qp, files=pats, opts=opts)
926 929
927 930 def refresh(self, repo, pats=None, **opts):
928 931 if len(self.applied) == 0:
929 932 self.ui.write("No patches applied\n")
930 933 return 1
931 934 wlock = repo.wlock()
932 935 self.check_toppatch(repo)
933 936 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
934 937 top = revlog.bin(top)
935 938 cparents = repo.changelog.parents(top)
936 939 patchparent = self.qparents(repo, top)
937 940 message, comments, user, date, patchfound = self.readheaders(patchfn)
938 941
939 942 patchf = self.opener(patchfn, "w")
940 943 msg = opts.get('msg', '').rstrip()
941 944 if msg:
942 945 if comments:
943 946 # Remove existing message.
944 947 ci = 0
945 948 for mi in range(len(message)):
946 949 while message[mi] != comments[ci]:
947 950 ci += 1
948 951 del comments[ci]
949 952 comments.append(msg)
950 953 if comments:
951 954 comments = "\n".join(comments) + '\n\n'
952 955 patchf.write(comments)
953 956
954 957 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
955 958 tip = repo.changelog.tip()
956 959 if top == tip:
957 960 # if the top of our patch queue is also the tip, there is an
958 961 # optimization here. We update the dirstate in place and strip
959 962 # off the tip commit. Then just commit the current directory
960 963 # tree. We can also send repo.commit the list of files
961 964 # changed to speed up the diff
962 965 #
963 966 # in short mode, we only diff the files included in the
964 967 # patch already
965 968 #
966 969 # this should really read:
967 970 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
968 971 # but we do it backwards to take advantage of manifest/chlog
969 972 # caching against the next repo.status call
970 973 #
971 974 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
972 975 if opts.get('short'):
973 976 filelist = mm + aa + dd
974 977 else:
975 978 filelist = None
976 979 m, a, r, d, u = repo.status(files=filelist)[:5]
977 980
978 981 # we might end up with files that were added between tip and
979 982 # the dirstate parent, but then changed in the local dirstate.
980 983 # in this case, we want them to only show up in the added section
981 984 for x in m:
982 985 if x not in aa:
983 986 mm.append(x)
984 987 # we might end up with files added by the local dirstate that
985 988 # were deleted by the patch. In this case, they should only
986 989 # show up in the changed section.
987 990 for x in a:
988 991 if x in dd:
989 992 del dd[dd.index(x)]
990 993 mm.append(x)
991 994 else:
992 995 aa.append(x)
993 996 # make sure any files deleted in the local dirstate
994 997 # are not in the add or change column of the patch
995 998 forget = []
996 999 for x in d + r:
997 1000 if x in aa:
998 1001 del aa[aa.index(x)]
999 1002 forget.append(x)
1000 1003 continue
1001 1004 elif x in mm:
1002 1005 del mm[mm.index(x)]
1003 1006 dd.append(x)
1004 1007
1005 1008 m = list(util.unique(mm))
1006 1009 r = list(util.unique(dd))
1007 1010 a = list(util.unique(aa))
1008 1011 filelist = filter(matchfn, util.unique(m + r + a))
1009 1012 if opts.get('git'):
1010 1013 self.diffopts().git = True
1011 1014 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1012 1015 fp=patchf, changes=(m, a, r, [], u),
1013 1016 opts=self.diffopts())
1014 1017 patchf.close()
1015 1018
1016 1019 changes = repo.changelog.read(tip)
1017 1020 repo.dirstate.setparents(*cparents)
1018 1021 copies = [(f, repo.dirstate.copied(f)) for f in a]
1019 1022 repo.dirstate.update(a, 'a')
1020 1023 for dst, src in copies:
1021 1024 repo.dirstate.copy(src, dst)
1022 1025 repo.dirstate.update(r, 'r')
1023 1026 # if the patch excludes a modified file, mark that file with mtime=0
1024 1027 # so status can see it.
1025 1028 mm = []
1026 1029 for i in range(len(m)-1, -1, -1):
1027 1030 if not matchfn(m[i]):
1028 1031 mm.append(m[i])
1029 1032 del m[i]
1030 1033 repo.dirstate.update(m, 'n')
1031 1034 repo.dirstate.update(mm, 'n', st_mtime=0)
1032 1035 repo.dirstate.forget(forget)
1033 1036
1034 1037 if not msg:
1035 1038 if not message:
1036 1039 message = "patch queue: %s\n" % patchfn
1037 1040 else:
1038 1041 message = "\n".join(message)
1039 1042 else:
1040 1043 message = msg
1041 1044
1042 1045 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1043 1046 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
1044 1047 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1045 1048 self.applied_dirty = 1
1046 1049 else:
1047 1050 self.printdiff(repo, patchparent, fp=patchf)
1048 1051 patchf.close()
1049 1052 self.pop(repo, force=True, wlock=wlock)
1050 1053 self.push(repo, force=True, wlock=wlock)
1051 1054
1052 1055 def init(self, repo, create=False):
1053 1056 if os.path.isdir(self.path):
1054 1057 raise util.Abort(_("patch queue directory already exists"))
1055 1058 os.mkdir(self.path)
1056 1059 if create:
1057 1060 return self.qrepo(create=True)
1058 1061
1059 1062 def unapplied(self, repo, patch=None):
1060 1063 if patch and patch not in self.series:
1061 1064 raise util.Abort(_("patch %s is not in series file") % patch)
1062 1065 if not patch:
1063 1066 start = self.series_end()
1064 1067 else:
1065 1068 start = self.series.index(patch) + 1
1066 1069 unapplied = []
1067 1070 for i in xrange(start, len(self.series)):
1068 1071 pushable, reason = self.pushable(i)
1069 1072 if pushable:
1070 1073 unapplied.append((i, self.series[i]))
1071 1074 self.explain_pushable(i)
1072 1075 return unapplied
1073 1076
1074 1077 def qseries(self, repo, missing=None, start=0, length=0, status=None,
1075 1078 summary=False):
1076 1079 def displayname(patchname):
1077 1080 if summary:
1078 1081 msg = self.readheaders(patchname)[0]
1079 1082 msg = msg and ': ' + msg[0] or ': '
1080 1083 else:
1081 1084 msg = ''
1082 1085 return '%s%s' % (patchname, msg)
1083 1086
1084 1087 def pname(i):
1085 1088 if status == 'A':
1086 1089 return self.applied[i].name
1087 1090 else:
1088 1091 return self.series[i]
1089 1092
1090 1093 unapplied = self.series_end(all_patches=True)
1091 1094 if not length:
1092 1095 length = len(self.series) - start
1093 1096 if not missing:
1094 1097 for i in range(start, start+length):
1095 1098 pfx = ''
1096 1099 patch = pname(i)
1097 1100 if self.ui.verbose:
1098 1101 if i < unapplied:
1099 1102 status = 'A'
1100 1103 elif self.pushable(i)[0]:
1101 1104 status = 'U'
1102 1105 else:
1103 1106 status = 'G'
1104 1107 pfx = '%d %s ' % (i, status)
1105 1108 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1106 1109 else:
1107 1110 msng_list = []
1108 1111 for root, dirs, files in os.walk(self.path):
1109 1112 d = root[len(self.path) + 1:]
1110 1113 for f in files:
1111 1114 fl = os.path.join(d, f)
1112 1115 if (fl not in self.series and
1113 1116 fl not in (self.status_path, self.series_path)
1114 1117 and not fl.startswith('.')):
1115 1118 msng_list.append(fl)
1116 1119 msng_list.sort()
1117 1120 for x in msng_list:
1118 1121 pfx = self.ui.verbose and ('D ') or ''
1119 1122 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1120 1123
1121 1124 def issaveline(self, l):
1122 1125 if l.name == '.hg.patches.save.line':
1123 1126 return True
1124 1127
1125 1128 def qrepo(self, create=False):
1126 1129 if create or os.path.isdir(self.join(".hg")):
1127 1130 return hg.repository(self.ui, path=self.path, create=create)
1128 1131
1129 1132 def restore(self, repo, rev, delete=None, qupdate=None):
1130 1133 c = repo.changelog.read(rev)
1131 1134 desc = c[4].strip()
1132 1135 lines = desc.splitlines()
1133 1136 i = 0
1134 1137 datastart = None
1135 1138 series = []
1136 1139 applied = []
1137 1140 qpp = None
1138 1141 for i in xrange(0, len(lines)):
1139 1142 if lines[i] == 'Patch Data:':
1140 1143 datastart = i + 1
1141 1144 elif lines[i].startswith('Dirstate:'):
1142 1145 l = lines[i].rstrip()
1143 1146 l = l[10:].split(' ')
1144 1147 qpp = [ hg.bin(x) for x in l ]
1145 1148 elif datastart != None:
1146 1149 l = lines[i].rstrip()
1147 1150 se = statusentry(l)
1148 1151 file_ = se.name
1149 1152 if se.rev:
1150 1153 applied.append(se)
1151 1154 series.append(file_)
1152 1155 if datastart == None:
1153 1156 self.ui.warn("No saved patch data found\n")
1154 1157 return 1
1155 1158 self.ui.warn("restoring status: %s\n" % lines[0])
1156 1159 self.full_series = series
1157 1160 self.applied = applied
1158 1161 self.parse_series()
1159 1162 self.series_dirty = 1
1160 1163 self.applied_dirty = 1
1161 1164 heads = repo.changelog.heads()
1162 1165 if delete:
1163 1166 if rev not in heads:
1164 1167 self.ui.warn("save entry has children, leaving it alone\n")
1165 1168 else:
1166 1169 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1167 1170 pp = repo.dirstate.parents()
1168 1171 if rev in pp:
1169 1172 update = True
1170 1173 else:
1171 1174 update = False
1172 1175 self.strip(repo, rev, update=update, backup='strip')
1173 1176 if qpp:
1174 1177 self.ui.warn("saved queue repository parents: %s %s\n" %
1175 1178 (hg.short(qpp[0]), hg.short(qpp[1])))
1176 1179 if qupdate:
1177 1180 print "queue directory updating"
1178 1181 r = self.qrepo()
1179 1182 if not r:
1180 1183 self.ui.warn("Unable to load queue repository\n")
1181 1184 return 1
1182 1185 hg.clean(r, qpp[0])
1183 1186
1184 1187 def save(self, repo, msg=None):
1185 1188 if len(self.applied) == 0:
1186 1189 self.ui.warn("save: no patches applied, exiting\n")
1187 1190 return 1
1188 1191 if self.issaveline(self.applied[-1]):
1189 1192 self.ui.warn("status is already saved\n")
1190 1193 return 1
1191 1194
1192 1195 ar = [ ':' + x for x in self.full_series ]
1193 1196 if not msg:
1194 1197 msg = "hg patches saved state"
1195 1198 else:
1196 1199 msg = "hg patches: " + msg.rstrip('\r\n')
1197 1200 r = self.qrepo()
1198 1201 if r:
1199 1202 pp = r.dirstate.parents()
1200 1203 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1201 1204 msg += "\n\nPatch Data:\n"
1202 1205 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1203 1206 "\n".join(ar) + '\n' or "")
1204 1207 n = repo.commit(None, text, user=None, force=1)
1205 1208 if not n:
1206 1209 self.ui.warn("repo commit failed\n")
1207 1210 return 1
1208 1211 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1209 1212 self.applied_dirty = 1
1210 1213
1211 1214 def full_series_end(self):
1212 1215 if len(self.applied) > 0:
1213 1216 p = self.applied[-1].name
1214 1217 end = self.find_series(p)
1215 1218 if end == None:
1216 1219 return len(self.full_series)
1217 1220 return end + 1
1218 1221 return 0
1219 1222
1220 1223 def series_end(self, all_patches=False):
1221 1224 end = 0
1222 1225 def next(start):
1223 1226 if all_patches:
1224 1227 return start
1225 1228 i = start
1226 1229 while i < len(self.series):
1227 1230 p, reason = self.pushable(i)
1228 1231 if p:
1229 1232 break
1230 1233 self.explain_pushable(i)
1231 1234 i += 1
1232 1235 return i
1233 1236 if len(self.applied) > 0:
1234 1237 p = self.applied[-1].name
1235 1238 try:
1236 1239 end = self.series.index(p)
1237 1240 except ValueError:
1238 1241 return 0
1239 1242 return next(end + 1)
1240 1243 return next(end)
1241 1244
1242 1245 def appliedname(self, index):
1243 1246 pname = self.applied[index].name
1244 1247 if not self.ui.verbose:
1245 1248 p = pname
1246 1249 else:
1247 1250 p = str(self.series.index(pname)) + " " + pname
1248 1251 return p
1249 1252
1250 1253 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1251 1254 force=None):
1252 1255 def checkseries(patchname):
1253 1256 if patchname in self.series:
1254 1257 raise util.Abort(_('patch %s is already in the series file')
1255 1258 % patchname)
1256 1259 def checkfile(patchname):
1257 1260 if not force and os.path.exists(self.join(patchname)):
1258 1261 raise util.Abort(_('patch "%s" already exists')
1259 1262 % patchname)
1260 1263
1261 1264 if rev:
1262 1265 if files:
1263 1266 raise util.Abort(_('option "-r" not valid when importing '
1264 1267 'files'))
1265 1268 rev = [int(r) for r in cmdutil.revrange(self.ui, repo, rev)]
1266 1269 rev.sort(lambda x, y: cmp(y, x))
1267 1270 if (len(files) > 1 or len(rev) > 1) and patchname:
1268 1271 raise util.Abort(_('option "-n" not valid when importing multiple '
1269 1272 'patches'))
1270 1273 i = 0
1271 1274 added = []
1272 1275 if rev:
1273 1276 # If mq patches are applied, we can only import revisions
1274 1277 # that form a linear path to qbase.
1275 1278 # Otherwise, they should form a linear path to a head.
1276 1279 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1277 1280 if len(heads) > 1:
1278 1281 raise util.Abort(_('revision %d is the root of more than one '
1279 1282 'branch') % rev[-1])
1280 1283 if self.applied:
1281 1284 base = revlog.hex(repo.changelog.node(rev[0]))
1282 1285 if base in [n.rev for n in self.applied]:
1283 1286 raise util.Abort(_('revision %d is already managed')
1284 1287 % rev[0])
1285 1288 if heads != [revlog.bin(self.applied[-1].rev)]:
1286 1289 raise util.Abort(_('revision %d is not the parent of '
1287 1290 'the queue') % rev[0])
1288 1291 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1289 1292 lastparent = repo.changelog.parentrevs(base)[0]
1290 1293 else:
1291 1294 if heads != [repo.changelog.node(rev[0])]:
1292 1295 raise util.Abort(_('revision %d has unmanaged children')
1293 1296 % rev[0])
1294 1297 lastparent = None
1295 1298
1296 1299 for r in rev:
1297 1300 p1, p2 = repo.changelog.parentrevs(r)
1298 1301 n = repo.changelog.node(r)
1299 1302 if p2 != -1:
1300 1303 raise util.Abort(_('cannot import merge revision %d') % r)
1301 1304 if lastparent and lastparent != r:
1302 1305 raise util.Abort(_('revision %d is not the parent of %d')
1303 1306 % (r, lastparent))
1304 1307 lastparent = p1
1305 1308
1306 1309 if not patchname:
1307 1310 patchname = '%d.diff' % r
1308 1311 checkseries(patchname)
1309 1312 checkfile(patchname)
1310 1313 self.full_series.insert(0, patchname)
1311 1314
1312 1315 patchf = self.opener(patchname, "w")
1313 1316 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1314 1317 patchf.close()
1315 1318
1316 1319 se = statusentry(revlog.hex(n), patchname)
1317 1320 self.applied.insert(0, se)
1318 1321
1319 1322 added.append(patchname)
1320 1323 patchname = None
1321 1324 self.parse_series()
1322 1325 self.applied_dirty = 1
1323 1326
1324 1327 for filename in files:
1325 1328 if existing:
1326 1329 if not patchname:
1327 1330 patchname = filename
1328 1331 if not os.path.isfile(self.join(patchname)):
1329 1332 raise util.Abort(_("patch %s does not exist") % patchname)
1330 1333 else:
1331 1334 try:
1332 1335 text = file(filename).read()
1333 1336 except IOError:
1334 1337 raise util.Abort(_("unable to read %s") % patchname)
1335 1338 if not patchname:
1336 1339 patchname = os.path.basename(filename)
1337 1340 checkfile(patchname)
1338 1341 patchf = self.opener(patchname, "w")
1339 1342 patchf.write(text)
1340 1343 checkseries(patchname)
1341 1344 index = self.full_series_end() + i
1342 1345 self.full_series[index:index] = [patchname]
1343 1346 self.parse_series()
1344 1347 self.ui.warn("adding %s to series file\n" % patchname)
1345 1348 i += 1
1346 1349 added.append(patchname)
1347 1350 patchname = None
1348 1351 self.series_dirty = 1
1349 1352 qrepo = self.qrepo()
1350 1353 if qrepo:
1351 1354 qrepo.add(added)
1352 1355
1353 1356 def delete(ui, repo, patch, *patches, **opts):
1354 1357 """remove patches from queue
1355 1358
1356 1359 With --forget, mq will stop managing the named patches. The
1357 1360 patches must be applied and at the base of the stack. This option
1358 1361 is useful when the patches have been applied upstream.
1359 1362
1360 1363 Otherwise, the patches must not be applied.
1361 1364
1362 1365 With --keep, the patch files are preserved in the patch directory."""
1363 1366 q = repo.mq
1364 1367 q.delete(repo, (patch,) + patches, opts)
1365 1368 q.save_dirty()
1366 1369 return 0
1367 1370
1368 1371 def applied(ui, repo, patch=None, **opts):
1369 1372 """print the patches already applied"""
1370 1373 q = repo.mq
1371 1374 if patch:
1372 1375 if patch not in q.series:
1373 1376 raise util.Abort(_("patch %s is not in series file") % patch)
1374 1377 end = q.series.index(patch) + 1
1375 1378 else:
1376 1379 end = len(q.applied)
1377 1380 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1378 1381
1379 1382 def unapplied(ui, repo, patch=None, **opts):
1380 1383 """print the patches not yet applied"""
1381 1384 q = repo.mq
1382 1385 if patch:
1383 1386 if patch not in q.series:
1384 1387 raise util.Abort(_("patch %s is not in series file") % patch)
1385 1388 start = q.series.index(patch) + 1
1386 1389 else:
1387 1390 start = q.series_end()
1388 1391 q.qseries(repo, start=start, summary=opts.get('summary'))
1389 1392
1390 1393 def qimport(ui, repo, *filename, **opts):
1391 1394 """import a patch
1392 1395
1393 1396 The patch will have the same name as its source file unless you
1394 1397 give it a new one with --name.
1395 1398
1396 1399 You can register an existing patch inside the patch directory
1397 1400 with the --existing flag.
1398 1401
1399 1402 With --force, an existing patch of the same name will be overwritten.
1400 1403
1401 1404 An existing changeset may be placed under mq control with --rev
1402 1405 (e.g. qimport --rev tip -n patch will place tip under mq control).
1403 1406 """
1404 1407 q = repo.mq
1405 1408 q.qimport(repo, filename, patchname=opts['name'],
1406 1409 existing=opts['existing'], force=opts['force'], rev=opts['rev'])
1407 1410 q.save_dirty()
1408 1411 return 0
1409 1412
1410 1413 def init(ui, repo, **opts):
1411 1414 """init a new queue repository
1412 1415
1413 1416 The queue repository is unversioned by default. If -c is
1414 1417 specified, qinit will create a separate nested repository
1415 1418 for patches. Use qcommit to commit changes to this queue
1416 1419 repository."""
1417 1420 q = repo.mq
1418 1421 r = q.init(repo, create=opts['create_repo'])
1419 1422 q.save_dirty()
1420 1423 if r:
1421 1424 fp = r.wopener('.hgignore', 'w')
1422 1425 print >> fp, 'syntax: glob'
1423 1426 print >> fp, 'status'
1424 1427 fp.close()
1425 1428 r.wopener('series', 'w').close()
1426 1429 r.add(['.hgignore', 'series'])
1427 1430 return 0
1428 1431
1429 1432 def clone(ui, source, dest=None, **opts):
1430 1433 '''clone main and patch repository at same time
1431 1434
1432 1435 If source is local, destination will have no patches applied. If
1433 1436 source is remote, this command can not check if patches are
1434 1437 applied in source, so cannot guarantee that patches are not
1435 1438 applied in destination. If you clone remote repository, be sure
1436 1439 before that it has no patches applied.
1437 1440
1438 1441 Source patch repository is looked for in <src>/.hg/patches by
1439 1442 default. Use -p <url> to change.
1440 1443 '''
1441 1444 commands.setremoteconfig(ui, opts)
1442 1445 if dest is None:
1443 1446 dest = hg.defaultdest(source)
1444 1447 sr = hg.repository(ui, ui.expandpath(source))
1445 1448 qbase, destrev = None, None
1446 1449 if sr.local():
1447 1450 reposetup(ui, sr)
1448 1451 if sr.mq.applied:
1449 1452 qbase = revlog.bin(sr.mq.applied[0].rev)
1450 1453 if not hg.islocal(dest):
1451 1454 destrev = sr.parents(qbase)[0]
1452 1455 ui.note(_('cloning main repo\n'))
1453 1456 sr, dr = hg.clone(ui, sr, dest,
1454 1457 pull=opts['pull'],
1455 1458 rev=destrev,
1456 1459 update=False,
1457 1460 stream=opts['uncompressed'])
1458 1461 ui.note(_('cloning patch repo\n'))
1459 1462 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1460 1463 dr.url() + '/.hg/patches',
1461 1464 pull=opts['pull'],
1462 1465 update=not opts['noupdate'],
1463 1466 stream=opts['uncompressed'])
1464 1467 if dr.local():
1465 1468 if qbase:
1466 1469 ui.note(_('stripping applied patches from destination repo\n'))
1467 1470 reposetup(ui, dr)
1468 1471 dr.mq.strip(dr, qbase, update=False, backup=None)
1469 1472 if not opts['noupdate']:
1470 1473 ui.note(_('updating destination repo\n'))
1471 1474 hg.update(dr, dr.changelog.tip())
1472 1475
1473 1476 def commit(ui, repo, *pats, **opts):
1474 1477 """commit changes in the queue repository"""
1475 1478 q = repo.mq
1476 1479 r = q.qrepo()
1477 1480 if not r: raise util.Abort('no queue repository')
1478 1481 commands.commit(r.ui, r, *pats, **opts)
1479 1482
1480 1483 def series(ui, repo, **opts):
1481 1484 """print the entire series file"""
1482 1485 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1483 1486 return 0
1484 1487
1485 1488 def top(ui, repo, **opts):
1486 1489 """print the name of the current patch"""
1487 1490 q = repo.mq
1488 1491 t = len(q.applied)
1489 1492 if t:
1490 1493 return q.qseries(repo, start=t-1, length=1, status='A',
1491 1494 summary=opts.get('summary'))
1492 1495 else:
1493 1496 ui.write("No patches applied\n")
1494 1497 return 1
1495 1498
1496 1499 def next(ui, repo, **opts):
1497 1500 """print the name of the next patch"""
1498 1501 q = repo.mq
1499 1502 end = q.series_end()
1500 1503 if end == len(q.series):
1501 1504 ui.write("All patches applied\n")
1502 1505 return 1
1503 1506 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1504 1507
1505 1508 def prev(ui, repo, **opts):
1506 1509 """print the name of the previous patch"""
1507 1510 q = repo.mq
1508 1511 l = len(q.applied)
1509 1512 if l == 1:
1510 1513 ui.write("Only one patch applied\n")
1511 1514 return 1
1512 1515 if not l:
1513 1516 ui.write("No patches applied\n")
1514 1517 return 1
1515 1518 return q.qseries(repo, start=l-2, length=1, status='A',
1516 1519 summary=opts.get('summary'))
1517 1520
1518 1521 def new(ui, repo, patch, **opts):
1519 1522 """create a new patch
1520 1523
1521 1524 qnew creates a new patch on top of the currently-applied patch
1522 1525 (if any). It will refuse to run if there are any outstanding
1523 1526 changes unless -f is specified, in which case the patch will
1524 1527 be initialised with them.
1525 1528
1526 1529 -e, -m or -l set the patch header as well as the commit message.
1527 1530 If none is specified, the patch header is empty and the
1528 1531 commit message is 'New patch: PATCH'"""
1529 1532 q = repo.mq
1530 1533 message = commands.logmessage(opts)
1531 1534 if opts['edit']:
1532 1535 message = ui.edit(message, ui.username())
1533 1536 q.new(repo, patch, msg=message, force=opts['force'])
1534 1537 q.save_dirty()
1535 1538 return 0
1536 1539
1537 1540 def refresh(ui, repo, *pats, **opts):
1538 1541 """update the current patch
1539 1542
1540 1543 If any file patterns are provided, the refreshed patch will contain only
1541 1544 the modifications that match those patterns; the remaining modifications
1542 1545 will remain in the working directory.
1543 1546 """
1544 1547 q = repo.mq
1545 1548 message = commands.logmessage(opts)
1546 1549 if opts['edit']:
1547 1550 if message:
1548 1551 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1549 1552 patch = q.applied[-1].name
1550 1553 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1551 1554 message = ui.edit('\n'.join(message), user or ui.username())
1552 1555 ret = q.refresh(repo, pats, msg=message, **opts)
1553 1556 q.save_dirty()
1554 1557 return ret
1555 1558
1556 1559 def diff(ui, repo, *pats, **opts):
1557 1560 """diff of the current patch"""
1558 1561 repo.mq.diff(repo, pats, opts)
1559 1562 return 0
1560 1563
1561 1564 def fold(ui, repo, *files, **opts):
1562 1565 """fold the named patches into the current patch
1563 1566
1564 1567 Patches must not yet be applied. Each patch will be successively
1565 1568 applied to the current patch in the order given. If all the
1566 1569 patches apply successfully, the current patch will be refreshed
1567 1570 with the new cumulative patch, and the folded patches will
1568 1571 be deleted. With -k/--keep, the folded patch files will not
1569 1572 be removed afterwards.
1570 1573
1571 1574 The header for each folded patch will be concatenated with
1572 1575 the current patch header, separated by a line of '* * *'."""
1573 1576
1574 1577 q = repo.mq
1575 1578
1576 1579 if not files:
1577 1580 raise util.Abort(_('qfold requires at least one patch name'))
1578 1581 if not q.check_toppatch(repo):
1579 1582 raise util.Abort(_('No patches applied'))
1580 1583
1581 1584 message = commands.logmessage(opts)
1582 1585 if opts['edit']:
1583 1586 if message:
1584 1587 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1585 1588
1586 1589 parent = q.lookup('qtip')
1587 1590 patches = []
1588 1591 messages = []
1589 1592 for f in files:
1590 1593 p = q.lookup(f)
1591 1594 if p in patches or p == parent:
1592 1595 ui.warn(_('Skipping already folded patch %s') % p)
1593 1596 if q.isapplied(p):
1594 1597 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1595 1598 patches.append(p)
1596 1599
1597 1600 for p in patches:
1598 1601 if not message:
1599 1602 messages.append(q.readheaders(p)[0])
1600 1603 pf = q.join(p)
1601 1604 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1602 1605 if not patchsuccess:
1603 1606 raise util.Abort(_('Error folding patch %s') % p)
1604 1607 patch.updatedir(ui, repo, files)
1605 1608
1606 1609 if not message:
1607 1610 message, comments, user = q.readheaders(parent)[0:3]
1608 1611 for msg in messages:
1609 1612 message.append('* * *')
1610 1613 message.extend(msg)
1611 1614 message = '\n'.join(message)
1612 1615
1613 1616 if opts['edit']:
1614 1617 message = ui.edit(message, user or ui.username())
1615 1618
1616 1619 q.refresh(repo, msg=message)
1617 1620 q.delete(repo, patches, keep=opts['keep'])
1618 1621 q.save_dirty()
1619 1622
1620 1623 def guard(ui, repo, *args, **opts):
1621 1624 '''set or print guards for a patch
1622 1625
1623 1626 Guards control whether a patch can be pushed. A patch with no
1624 1627 guards is always pushed. A patch with a positive guard ("+foo") is
1625 1628 pushed only if the qselect command has activated it. A patch with
1626 1629 a negative guard ("-foo") is never pushed if the qselect command
1627 1630 has activated it.
1628 1631
1629 1632 With no arguments, print the currently active guards.
1630 1633 With arguments, set guards for the named patch.
1631 1634
1632 1635 To set a negative guard "-foo" on topmost patch ("--" is needed so
1633 1636 hg will not interpret "-foo" as an option):
1634 1637 hg qguard -- -foo
1635 1638
1636 1639 To set guards on another patch:
1637 1640 hg qguard other.patch +2.6.17 -stable
1638 1641 '''
1639 1642 def status(idx):
1640 1643 guards = q.series_guards[idx] or ['unguarded']
1641 1644 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1642 1645 q = repo.mq
1643 1646 patch = None
1644 1647 args = list(args)
1645 1648 if opts['list']:
1646 1649 if args or opts['none']:
1647 1650 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1648 1651 for i in xrange(len(q.series)):
1649 1652 status(i)
1650 1653 return
1651 1654 if not args or args[0][0:1] in '-+':
1652 1655 if not q.applied:
1653 1656 raise util.Abort(_('no patches applied'))
1654 1657 patch = q.applied[-1].name
1655 1658 if patch is None and args[0][0:1] not in '-+':
1656 1659 patch = args.pop(0)
1657 1660 if patch is None:
1658 1661 raise util.Abort(_('no patch to work with'))
1659 1662 if args or opts['none']:
1660 1663 q.set_guards(q.find_series(patch), args)
1661 1664 q.save_dirty()
1662 1665 else:
1663 1666 status(q.series.index(q.lookup(patch)))
1664 1667
1665 1668 def header(ui, repo, patch=None):
1666 1669 """Print the header of the topmost or specified patch"""
1667 1670 q = repo.mq
1668 1671
1669 1672 if patch:
1670 1673 patch = q.lookup(patch)
1671 1674 else:
1672 1675 if not q.applied:
1673 1676 ui.write('No patches applied\n')
1674 1677 return 1
1675 1678 patch = q.lookup('qtip')
1676 1679 message = repo.mq.readheaders(patch)[0]
1677 1680
1678 1681 ui.write('\n'.join(message) + '\n')
1679 1682
1680 1683 def lastsavename(path):
1681 1684 (directory, base) = os.path.split(path)
1682 1685 names = os.listdir(directory)
1683 1686 namere = re.compile("%s.([0-9]+)" % base)
1684 1687 maxindex = None
1685 1688 maxname = None
1686 1689 for f in names:
1687 1690 m = namere.match(f)
1688 1691 if m:
1689 1692 index = int(m.group(1))
1690 1693 if maxindex == None or index > maxindex:
1691 1694 maxindex = index
1692 1695 maxname = f
1693 1696 if maxname:
1694 1697 return (os.path.join(directory, maxname), maxindex)
1695 1698 return (None, None)
1696 1699
1697 1700 def savename(path):
1698 1701 (last, index) = lastsavename(path)
1699 1702 if last is None:
1700 1703 index = 0
1701 1704 newpath = path + ".%d" % (index + 1)
1702 1705 return newpath
1703 1706
1704 1707 def push(ui, repo, patch=None, **opts):
1705 1708 """push the next patch onto the stack"""
1706 1709 q = repo.mq
1707 1710 mergeq = None
1708 1711
1709 1712 if opts['all']:
1710 1713 patch = q.series[-1]
1711 1714 if opts['merge']:
1712 1715 if opts['name']:
1713 1716 newpath = opts['name']
1714 1717 else:
1715 1718 newpath, i = lastsavename(q.path)
1716 1719 if not newpath:
1717 1720 ui.warn("no saved queues found, please use -n\n")
1718 1721 return 1
1719 1722 mergeq = queue(ui, repo.join(""), newpath)
1720 1723 ui.warn("merging with queue at: %s\n" % mergeq.path)
1721 1724 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1722 1725 mergeq=mergeq)
1723 1726 q.save_dirty()
1724 1727 return ret
1725 1728
1726 1729 def pop(ui, repo, patch=None, **opts):
1727 1730 """pop the current patch off the stack"""
1728 1731 localupdate = True
1729 1732 if opts['name']:
1730 1733 q = queue(ui, repo.join(""), repo.join(opts['name']))
1731 1734 ui.warn('using patch queue: %s\n' % q.path)
1732 1735 localupdate = False
1733 1736 else:
1734 1737 q = repo.mq
1735 1738 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1736 1739 q.save_dirty()
1737 1740 return 0
1738 1741
1739 1742 def rename(ui, repo, patch, name=None, **opts):
1740 1743 """rename a patch
1741 1744
1742 1745 With one argument, renames the current patch to PATCH1.
1743 1746 With two arguments, renames PATCH1 to PATCH2."""
1744 1747
1745 1748 q = repo.mq
1746 1749
1747 1750 if not name:
1748 1751 name = patch
1749 1752 patch = None
1750 1753
1751 1754 if patch:
1752 1755 patch = q.lookup(patch)
1753 1756 else:
1754 1757 if not q.applied:
1755 1758 ui.write(_('No patches applied\n'))
1756 1759 return
1757 1760 patch = q.lookup('qtip')
1758 1761 absdest = q.join(name)
1759 1762 if os.path.isdir(absdest):
1760 1763 name = os.path.join(name, os.path.basename(patch))
1761 1764 absdest = q.join(name)
1762 1765 if os.path.exists(absdest):
1763 1766 raise util.Abort(_('%s already exists') % absdest)
1764 1767
1765 1768 if name in q.series:
1766 1769 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1767 1770
1768 1771 if ui.verbose:
1769 1772 ui.write('Renaming %s to %s\n' % (patch, name))
1770 1773 i = q.find_series(patch)
1771 1774 q.full_series[i] = name
1772 1775 q.parse_series()
1773 1776 q.series_dirty = 1
1774 1777
1775 1778 info = q.isapplied(patch)
1776 1779 if info:
1777 1780 q.applied[info[0]] = statusentry(info[1], name)
1778 1781 q.applied_dirty = 1
1779 1782
1780 1783 util.rename(q.join(patch), absdest)
1781 1784 r = q.qrepo()
1782 1785 if r:
1783 1786 wlock = r.wlock()
1784 1787 if r.dirstate.state(name) == 'r':
1785 1788 r.undelete([name], wlock)
1786 1789 r.copy(patch, name, wlock)
1787 1790 r.remove([patch], False, wlock)
1788 1791
1789 1792 q.save_dirty()
1790 1793
1791 1794 def restore(ui, repo, rev, **opts):
1792 1795 """restore the queue state saved by a rev"""
1793 1796 rev = repo.lookup(rev)
1794 1797 q = repo.mq
1795 1798 q.restore(repo, rev, delete=opts['delete'],
1796 1799 qupdate=opts['update'])
1797 1800 q.save_dirty()
1798 1801 return 0
1799 1802
1800 1803 def save(ui, repo, **opts):
1801 1804 """save current queue state"""
1802 1805 q = repo.mq
1803 1806 message = commands.logmessage(opts)
1804 1807 ret = q.save(repo, msg=message)
1805 1808 if ret:
1806 1809 return ret
1807 1810 q.save_dirty()
1808 1811 if opts['copy']:
1809 1812 path = q.path
1810 1813 if opts['name']:
1811 1814 newpath = os.path.join(q.basepath, opts['name'])
1812 1815 if os.path.exists(newpath):
1813 1816 if not os.path.isdir(newpath):
1814 1817 raise util.Abort(_('destination %s exists and is not '
1815 1818 'a directory') % newpath)
1816 1819 if not opts['force']:
1817 1820 raise util.Abort(_('destination %s exists, '
1818 1821 'use -f to force') % newpath)
1819 1822 else:
1820 1823 newpath = savename(path)
1821 1824 ui.warn("copy %s to %s\n" % (path, newpath))
1822 1825 util.copyfiles(path, newpath)
1823 1826 if opts['empty']:
1824 1827 try:
1825 1828 os.unlink(q.join(q.status_path))
1826 1829 except:
1827 1830 pass
1828 1831 return 0
1829 1832
1830 1833 def strip(ui, repo, rev, **opts):
1831 1834 """strip a revision and all later revs on the same branch"""
1832 1835 rev = repo.lookup(rev)
1833 1836 backup = 'all'
1834 1837 if opts['backup']:
1835 1838 backup = 'strip'
1836 1839 elif opts['nobackup']:
1837 1840 backup = 'none'
1838 1841 update = repo.dirstate.parents()[0] != revlog.nullid
1839 1842 repo.mq.strip(repo, rev, backup=backup, update=update)
1840 1843 return 0
1841 1844
1842 1845 def select(ui, repo, *args, **opts):
1843 1846 '''set or print guarded patches to push
1844 1847
1845 1848 Use the qguard command to set or print guards on patch, then use
1846 1849 qselect to tell mq which guards to use. A patch will be pushed if it
1847 1850 has no guards or any positive guards match the currently selected guard,
1848 1851 but will not be pushed if any negative guards match the current guard.
1849 1852 For example:
1850 1853
1851 1854 qguard foo.patch -stable (negative guard)
1852 1855 qguard bar.patch +stable (positive guard)
1853 1856 qselect stable
1854 1857
1855 1858 This activates the "stable" guard. mq will skip foo.patch (because
1856 1859 it has a negative match) but push bar.patch (because it
1857 1860 has a positive match).
1858 1861
1859 1862 With no arguments, prints the currently active guards.
1860 1863 With one argument, sets the active guard.
1861 1864
1862 1865 Use -n/--none to deactivate guards (no other arguments needed).
1863 1866 When no guards are active, patches with positive guards are skipped
1864 1867 and patches with negative guards are pushed.
1865 1868
1866 1869 qselect can change the guards on applied patches. It does not pop
1867 1870 guarded patches by default. Use --pop to pop back to the last applied
1868 1871 patch that is not guarded. Use --reapply (which implies --pop) to push
1869 1872 back to the current patch afterwards, but skip guarded patches.
1870 1873
1871 1874 Use -s/--series to print a list of all guards in the series file (no
1872 1875 other arguments needed). Use -v for more information.'''
1873 1876
1874 1877 q = repo.mq
1875 1878 guards = q.active()
1876 1879 if args or opts['none']:
1877 1880 old_unapplied = q.unapplied(repo)
1878 1881 old_guarded = [i for i in xrange(len(q.applied)) if
1879 1882 not q.pushable(i)[0]]
1880 1883 q.set_active(args)
1881 1884 q.save_dirty()
1882 1885 if not args:
1883 1886 ui.status(_('guards deactivated\n'))
1884 1887 if not opts['pop'] and not opts['reapply']:
1885 1888 unapplied = q.unapplied(repo)
1886 1889 guarded = [i for i in xrange(len(q.applied))
1887 1890 if not q.pushable(i)[0]]
1888 1891 if len(unapplied) != len(old_unapplied):
1889 1892 ui.status(_('number of unguarded, unapplied patches has '
1890 1893 'changed from %d to %d\n') %
1891 1894 (len(old_unapplied), len(unapplied)))
1892 1895 if len(guarded) != len(old_guarded):
1893 1896 ui.status(_('number of guarded, applied patches has changed '
1894 1897 'from %d to %d\n') %
1895 1898 (len(old_guarded), len(guarded)))
1896 1899 elif opts['series']:
1897 1900 guards = {}
1898 1901 noguards = 0
1899 1902 for gs in q.series_guards:
1900 1903 if not gs:
1901 1904 noguards += 1
1902 1905 for g in gs:
1903 1906 guards.setdefault(g, 0)
1904 1907 guards[g] += 1
1905 1908 if ui.verbose:
1906 1909 guards['NONE'] = noguards
1907 1910 guards = guards.items()
1908 1911 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
1909 1912 if guards:
1910 1913 ui.note(_('guards in series file:\n'))
1911 1914 for guard, count in guards:
1912 1915 ui.note('%2d ' % count)
1913 1916 ui.write(guard, '\n')
1914 1917 else:
1915 1918 ui.note(_('no guards in series file\n'))
1916 1919 else:
1917 1920 if guards:
1918 1921 ui.note(_('active guards:\n'))
1919 1922 for g in guards:
1920 1923 ui.write(g, '\n')
1921 1924 else:
1922 1925 ui.write(_('no active guards\n'))
1923 1926 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
1924 1927 popped = False
1925 1928 if opts['pop'] or opts['reapply']:
1926 1929 for i in xrange(len(q.applied)):
1927 1930 pushable, reason = q.pushable(i)
1928 1931 if not pushable:
1929 1932 ui.status(_('popping guarded patches\n'))
1930 1933 popped = True
1931 1934 if i == 0:
1932 1935 q.pop(repo, all=True)
1933 1936 else:
1934 1937 q.pop(repo, i-1)
1935 1938 break
1936 1939 if popped:
1937 1940 try:
1938 1941 if reapply:
1939 1942 ui.status(_('reapplying unguarded patches\n'))
1940 1943 q.push(repo, reapply)
1941 1944 finally:
1942 1945 q.save_dirty()
1943 1946
1944 1947 def reposetup(ui, repo):
1945 1948 class mqrepo(repo.__class__):
1946 1949 def abort_if_wdir_patched(self, errmsg, force=False):
1947 1950 if self.mq.applied and not force:
1948 1951 parent = revlog.hex(self.dirstate.parents()[0])
1949 1952 if parent in [s.rev for s in self.mq.applied]:
1950 1953 raise util.Abort(errmsg)
1951 1954
1952 1955 def commit(self, *args, **opts):
1953 1956 if len(args) >= 6:
1954 1957 force = args[5]
1955 1958 else:
1956 1959 force = opts.get('force')
1957 1960 self.abort_if_wdir_patched(
1958 1961 _('cannot commit over an applied mq patch'),
1959 1962 force)
1960 1963
1961 1964 return super(mqrepo, self).commit(*args, **opts)
1962 1965
1963 1966 def push(self, remote, force=False, revs=None):
1964 1967 if self.mq.applied and not force:
1965 1968 raise util.Abort(_('source has mq patches applied'))
1966 1969 return super(mqrepo, self).push(remote, force, revs)
1967 1970
1968 1971 def tags(self):
1969 1972 if self.tagscache:
1970 1973 return self.tagscache
1971 1974
1972 1975 tagscache = super(mqrepo, self).tags()
1973 1976
1974 1977 q = self.mq
1975 1978 if not q.applied:
1976 1979 return tagscache
1977 1980
1978 1981 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1979 1982 mqtags.append((mqtags[-1][0], 'qtip'))
1980 1983 mqtags.append((mqtags[0][0], 'qbase'))
1981 1984 for patch in mqtags:
1982 1985 if patch[1] in tagscache:
1983 1986 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1984 1987 else:
1985 1988 tagscache[patch[1]] = revlog.bin(patch[0])
1986 1989
1987 1990 return tagscache
1988 1991
1989 1992 if repo.local():
1990 1993 repo.__class__ = mqrepo
1991 1994 repo.mq = queue(ui, repo.join(""))
1992 1995
1993 1996 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
1994 1997
1995 1998 cmdtable = {
1996 1999 "qapplied": (applied, [] + seriesopts, 'hg qapplied [-s] [PATCH]'),
1997 2000 "qclone": (clone,
1998 2001 [('', 'pull', None, _('use pull protocol to copy metadata')),
1999 2002 ('U', 'noupdate', None, _('do not update the new working directories')),
2000 2003 ('', 'uncompressed', None,
2001 2004 _('use uncompressed transfer (fast over LAN)')),
2002 2005 ('e', 'ssh', '', _('specify ssh command to use')),
2003 2006 ('p', 'patches', '', _('location of source patch repo')),
2004 2007 ('', 'remotecmd', '',
2005 2008 _('specify hg command to run on the remote side'))],
2006 2009 'hg qclone [OPTION]... SOURCE [DEST]'),
2007 2010 "qcommit|qci":
2008 2011 (commit,
2009 2012 commands.table["^commit|ci"][1],
2010 2013 'hg qcommit [OPTION]... [FILE]...'),
2011 2014 "^qdiff": (diff,
2012 2015 [('I', 'include', [], _('include names matching the given patterns')),
2013 2016 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2014 2017 'hg qdiff [-I] [-X] [FILE]...'),
2015 2018 "qdelete|qremove|qrm":
2016 2019 (delete,
2017 2020 [('f', 'forget', None, _('stop managing an applied patch')),
2018 2021 ('k', 'keep', None, _('keep patch file'))],
2019 2022 'hg qdelete [-f] [-k] PATCH'),
2020 2023 'qfold':
2021 2024 (fold,
2022 2025 [('e', 'edit', None, _('edit patch header')),
2023 2026 ('k', 'keep', None, _('keep folded patch files')),
2024 2027 ('m', 'message', '', _('set patch header to <text>')),
2025 2028 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
2026 2029 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
2027 2030 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
2028 2031 ('n', 'none', None, _('drop all guards'))],
2029 2032 'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
2030 2033 'qheader': (header, [],
2031 2034 _('hg qheader [PATCH]')),
2032 2035 "^qimport":
2033 2036 (qimport,
2034 2037 [('e', 'existing', None, 'import file in patch dir'),
2035 2038 ('n', 'name', '', 'patch file name'),
2036 2039 ('f', 'force', None, 'overwrite existing files'),
2037 2040 ('r', 'rev', [], 'place existing revisions under mq control')],
2038 2041 'hg qimport [-e] [-n NAME] [-f] [-r REV]... FILE...'),
2039 2042 "^qinit":
2040 2043 (init,
2041 2044 [('c', 'create-repo', None, 'create queue repository')],
2042 2045 'hg qinit [-c]'),
2043 2046 "qnew":
2044 2047 (new,
2045 2048 [('e', 'edit', None, _('edit commit message')),
2046 2049 ('m', 'message', '', _('use <text> as commit message')),
2047 2050 ('l', 'logfile', '', _('read the commit message from <file>')),
2048 2051 ('f', 'force', None, _('import uncommitted changes into patch'))],
2049 2052 'hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH'),
2050 2053 "qnext": (next, [] + seriesopts, 'hg qnext [-s]'),
2051 2054 "qprev": (prev, [] + seriesopts, 'hg qprev [-s]'),
2052 2055 "^qpop":
2053 2056 (pop,
2054 2057 [('a', 'all', None, 'pop all patches'),
2055 2058 ('n', 'name', '', 'queue name to pop'),
2056 2059 ('f', 'force', None, 'forget any local changes')],
2057 2060 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
2058 2061 "^qpush":
2059 2062 (push,
2060 2063 [('f', 'force', None, 'apply if the patch has rejects'),
2061 2064 ('l', 'list', None, 'list patch name in commit text'),
2062 2065 ('a', 'all', None, 'apply all patches'),
2063 2066 ('m', 'merge', None, 'merge from another queue'),
2064 2067 ('n', 'name', '', 'merge queue name')],
2065 2068 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
2066 2069 "^qrefresh":
2067 2070 (refresh,
2068 2071 [('e', 'edit', None, _('edit commit message')),
2069 2072 ('m', 'message', '', _('change commit message with <text>')),
2070 2073 ('l', 'logfile', '', _('change commit message with <file> content')),
2071 2074 ('g', 'git', None, _('use git extended diff format')),
2072 2075 ('s', 'short', None, 'short refresh'),
2073 2076 ('I', 'include', [], _('include names matching the given patterns')),
2074 2077 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2075 2078 'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] FILES...'),
2076 2079 'qrename|qmv':
2077 2080 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
2078 2081 "qrestore":
2079 2082 (restore,
2080 2083 [('d', 'delete', None, 'delete save entry'),
2081 2084 ('u', 'update', None, 'update queue working dir')],
2082 2085 'hg qrestore [-d] [-u] REV'),
2083 2086 "qsave":
2084 2087 (save,
2085 2088 [('m', 'message', '', _('use <text> as commit message')),
2086 2089 ('l', 'logfile', '', _('read the commit message from <file>')),
2087 2090 ('c', 'copy', None, 'copy patch directory'),
2088 2091 ('n', 'name', '', 'copy directory name'),
2089 2092 ('e', 'empty', None, 'clear queue status file'),
2090 2093 ('f', 'force', None, 'force copy')],
2091 2094 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
2092 2095 "qselect": (select,
2093 2096 [('n', 'none', None, _('disable all guards')),
2094 2097 ('s', 'series', None, _('list all guards in series file')),
2095 2098 ('', 'pop', None,
2096 2099 _('pop to before first guarded applied patch')),
2097 2100 ('', 'reapply', None, _('pop, then reapply patches'))],
2098 2101 'hg qselect [OPTION...] [GUARD...]'),
2099 2102 "qseries":
2100 2103 (series,
2101 2104 [('m', 'missing', None, 'print patches not in series')] + seriesopts,
2102 2105 'hg qseries [-ms]'),
2103 2106 "^strip":
2104 2107 (strip,
2105 2108 [('f', 'force', None, 'force multi-head removal'),
2106 2109 ('b', 'backup', None, 'bundle unrelated changesets'),
2107 2110 ('n', 'nobackup', None, 'no backups')],
2108 2111 'hg strip [-f] [-b] [-n] REV'),
2109 2112 "qtop": (top, [] + seriesopts, 'hg qtop [-s]'),
2110 2113 "qunapplied": (unapplied, [] + seriesopts, 'hg qunapplied [-s] [PATCH]'),
2111 2114 }
General Comments 0
You need to be logged in to leave comments. Login now