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