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