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