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