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