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