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