##// END OF EJS Templates
mq: do not allow to push from repo with patches applied
Vadim Gelfer -
r2848:307439d6 default
parent child Browse files
Show More
@@ -1,1980 +1,1990 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 179 nonpos = [g for g in pos if g[1:] not in guards]
180 180 if pos:
181 181 if not nonpos:
182 182 return True, ''
183 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 204 if why:
205 205 write(_('skipping %s - guarded by %r\n') %
206 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.clean(repo, head, 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.merge(repo, rev, 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.clean(repo, urev, 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 patch = patch and str(patch)
729 729
730 730 def partial_name(s):
731 731 if s in self.series:
732 732 return s
733 733 matches = [x for x in self.series if s in x]
734 734 if len(matches) > 1:
735 735 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
736 736 for m in matches:
737 737 self.ui.warn(' %s\n' % m)
738 738 return None
739 739 if matches:
740 740 return matches[0]
741 741 if len(self.series) > 0 and len(self.applied) > 0:
742 742 if s == 'qtip':
743 743 return self.series[self.series_end()-1]
744 744 if s == 'qbase':
745 745 return self.series[0]
746 746 return None
747 747 if patch == None:
748 748 return None
749 749
750 750 # we don't want to return a partial match until we make
751 751 # sure the file name passed in does not exist (checked below)
752 752 res = partial_name(patch)
753 753 if res and res == patch:
754 754 return res
755 755
756 756 if not os.path.isfile(self.join(patch)):
757 757 try:
758 758 sno = int(patch)
759 759 except(ValueError, OverflowError):
760 760 pass
761 761 else:
762 762 if sno < len(self.series):
763 763 return self.series[sno]
764 764 if not strict:
765 765 # return any partial match made above
766 766 if res:
767 767 return res
768 768 minus = patch.rsplit('-', 1)
769 769 if len(minus) > 1:
770 770 res = partial_name(minus[0])
771 771 if res:
772 772 i = self.series.index(res)
773 773 try:
774 774 off = int(minus[1] or 1)
775 775 except(ValueError, OverflowError):
776 776 pass
777 777 else:
778 778 if i - off >= 0:
779 779 return self.series[i - off]
780 780 plus = patch.rsplit('+', 1)
781 781 if len(plus) > 1:
782 782 res = partial_name(plus[0])
783 783 if res:
784 784 i = self.series.index(res)
785 785 try:
786 786 off = int(plus[1] or 1)
787 787 except(ValueError, OverflowError):
788 788 pass
789 789 else:
790 790 if i + off < len(self.series):
791 791 return self.series[i + off]
792 792 raise util.Abort(_("patch %s not in series") % patch)
793 793
794 794 def push(self, repo, patch=None, force=False, list=False,
795 795 mergeq=None, wlock=None):
796 796 if not wlock:
797 797 wlock = repo.wlock()
798 798 patch = self.lookup(patch)
799 799 if patch and self.isapplied(patch):
800 800 self.ui.warn(_("patch %s is already applied\n") % patch)
801 801 sys.exit(1)
802 802 if self.series_end() == len(self.series):
803 803 self.ui.warn(_("patch series fully applied\n"))
804 804 sys.exit(1)
805 805 if not force:
806 806 self.check_localchanges(repo)
807 807
808 808 self.applied_dirty = 1;
809 809 start = self.series_end()
810 810 if start > 0:
811 811 self.check_toppatch(repo)
812 812 if not patch:
813 813 patch = self.series[start]
814 814 end = start + 1
815 815 else:
816 816 end = self.series.index(patch, start) + 1
817 817 s = self.series[start:end]
818 818 if mergeq:
819 819 ret = self.mergepatch(repo, mergeq, s, wlock)
820 820 else:
821 821 ret = self.apply(repo, s, list, wlock=wlock)
822 822 top = self.applied[-1].name
823 823 if ret[0]:
824 824 self.ui.write("Errors during apply, please fix and refresh %s\n" %
825 825 top)
826 826 else:
827 827 self.ui.write("Now at: %s\n" % top)
828 828 return ret[0]
829 829
830 830 def pop(self, repo, patch=None, force=False, update=True, all=False,
831 831 wlock=None):
832 832 def getfile(f, rev):
833 833 t = repo.file(f).read(rev)
834 834 try:
835 835 repo.wfile(f, "w").write(t)
836 836 except IOError:
837 837 try:
838 838 os.makedirs(os.path.dirname(repo.wjoin(f)))
839 839 except OSError, err:
840 840 if err.errno != errno.EEXIST: raise
841 841 repo.wfile(f, "w").write(t)
842 842
843 843 if not wlock:
844 844 wlock = repo.wlock()
845 845 if patch:
846 846 # index, rev, patch
847 847 info = self.isapplied(patch)
848 848 if not info:
849 849 patch = self.lookup(patch)
850 850 info = self.isapplied(patch)
851 851 if not info:
852 852 raise util.Abort(_("patch %s is not applied") % patch)
853 853 if len(self.applied) == 0:
854 854 self.ui.warn(_("no patches applied\n"))
855 855 sys.exit(1)
856 856
857 857 if not update:
858 858 parents = repo.dirstate.parents()
859 859 rr = [ revlog.bin(x.rev) for x in self.applied ]
860 860 for p in parents:
861 861 if p in rr:
862 862 self.ui.warn("qpop: forcing dirstate update\n")
863 863 update = True
864 864
865 865 if not force and update:
866 866 self.check_localchanges(repo)
867 867
868 868 self.applied_dirty = 1;
869 869 end = len(self.applied)
870 870 if not patch:
871 871 if all:
872 872 popi = 0
873 873 else:
874 874 popi = len(self.applied) - 1
875 875 else:
876 876 popi = info[0] + 1
877 877 if popi >= end:
878 878 self.ui.warn("qpop: %s is already at the top\n" % patch)
879 879 return
880 880 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
881 881
882 882 start = info[0]
883 883 rev = revlog.bin(info[1])
884 884
885 885 # we know there are no local changes, so we can make a simplified
886 886 # form of hg.update.
887 887 if update:
888 888 top = self.check_toppatch(repo)
889 889 qp = self.qparents(repo, rev)
890 890 changes = repo.changelog.read(qp)
891 891 mf1 = repo.manifest.readflags(changes[0])
892 892 mmap = repo.manifest.read(changes[0])
893 893 (c, a, r, d, u) = repo.changes(qp, top)
894 894 if d:
895 895 raise util.Abort("deletions found between repo revs")
896 896 for f in c:
897 897 getfile(f, mmap[f])
898 898 for f in r:
899 899 getfile(f, mmap[f])
900 900 util.set_exec(repo.wjoin(f), mf1[f])
901 901 repo.dirstate.update(c + r, 'n')
902 902 for f in a:
903 903 try: os.unlink(repo.wjoin(f))
904 904 except: raise
905 905 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
906 906 except: pass
907 907 if a:
908 908 repo.dirstate.forget(a)
909 909 repo.dirstate.setparents(qp, revlog.nullid)
910 910 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
911 911 del self.applied[start:end]
912 912 if len(self.applied):
913 913 self.ui.write("Now at: %s\n" % self.applied[-1].name)
914 914 else:
915 915 self.ui.write("Patch queue now empty\n")
916 916
917 917 def diff(self, repo, files):
918 918 top = self.check_toppatch(repo)
919 919 if not top:
920 920 self.ui.write("No patches applied\n")
921 921 return
922 922 qp = self.qparents(repo, top)
923 923 commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
924 924
925 925 def refresh(self, repo, msg=None, short=False):
926 926 if len(self.applied) == 0:
927 927 self.ui.write("No patches applied\n")
928 928 return
929 929 wlock = repo.wlock()
930 930 self.check_toppatch(repo)
931 931 (top, patch) = (self.applied[-1].rev, self.applied[-1].name)
932 932 top = revlog.bin(top)
933 933 cparents = repo.changelog.parents(top)
934 934 patchparent = self.qparents(repo, top)
935 935 message, comments, user, date, patchfound = self.readheaders(patch)
936 936
937 937 patchf = self.opener(patch, "w")
938 938 msg = msg.rstrip()
939 939 if msg:
940 940 if comments:
941 941 # Remove existing message.
942 942 ci = 0
943 943 for mi in range(len(message)):
944 944 while message[mi] != comments[ci]:
945 945 ci += 1
946 946 del comments[ci]
947 947 comments.append(msg)
948 948 if comments:
949 949 comments = "\n".join(comments) + '\n\n'
950 950 patchf.write(comments)
951 951
952 952 tip = repo.changelog.tip()
953 953 if top == tip:
954 954 # if the top of our patch queue is also the tip, there is an
955 955 # optimization here. We update the dirstate in place and strip
956 956 # off the tip commit. Then just commit the current directory
957 957 # tree. We can also send repo.commit the list of files
958 958 # changed to speed up the diff
959 959 #
960 960 # in short mode, we only diff the files included in the
961 961 # patch already
962 962 #
963 963 # this should really read:
964 964 #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
965 965 # but we do it backwards to take advantage of manifest/chlog
966 966 # caching against the next repo.changes call
967 967 #
968 968 (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
969 969 if short:
970 970 filelist = cc + aa + dd
971 971 else:
972 972 filelist = None
973 973 (c, a, r, d, u) = repo.changes(None, None, filelist)
974 974
975 975 # we might end up with files that were added between tip and
976 976 # the dirstate parent, but then changed in the local dirstate.
977 977 # in this case, we want them to only show up in the added section
978 978 for x in c:
979 979 if x not in aa:
980 980 cc.append(x)
981 981 # we might end up with files added by the local dirstate that
982 982 # were deleted by the patch. In this case, they should only
983 983 # show up in the changed section.
984 984 for x in a:
985 985 if x in dd:
986 986 del dd[dd.index(x)]
987 987 cc.append(x)
988 988 else:
989 989 aa.append(x)
990 990 # make sure any files deleted in the local dirstate
991 991 # are not in the add or change column of the patch
992 992 forget = []
993 993 for x in d + r:
994 994 if x in aa:
995 995 del aa[aa.index(x)]
996 996 forget.append(x)
997 997 continue
998 998 elif x in cc:
999 999 del cc[cc.index(x)]
1000 1000 dd.append(x)
1001 1001
1002 1002 c = list(util.unique(cc))
1003 1003 r = list(util.unique(dd))
1004 1004 a = list(util.unique(aa))
1005 1005 filelist = list(util.unique(c + r + a ))
1006 1006 commands.dodiff(patchf, self.ui, repo, patchparent, None,
1007 1007 filelist, changes=(c, a, r, [], u))
1008 1008 patchf.close()
1009 1009
1010 1010 changes = repo.changelog.read(tip)
1011 1011 repo.dirstate.setparents(*cparents)
1012 1012 repo.dirstate.update(a, 'a')
1013 1013 repo.dirstate.update(r, 'r')
1014 1014 repo.dirstate.update(c, 'n')
1015 1015 repo.dirstate.forget(forget)
1016 1016
1017 1017 if not msg:
1018 1018 if not message:
1019 1019 message = "patch queue: %s\n" % patch
1020 1020 else:
1021 1021 message = "\n".join(message)
1022 1022 else:
1023 1023 message = msg
1024 1024
1025 1025 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1026 1026 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
1027 1027 self.applied[-1] = statusentry(revlog.hex(n), patch)
1028 1028 self.applied_dirty = 1
1029 1029 else:
1030 1030 commands.dodiff(patchf, self.ui, repo, patchparent, None)
1031 1031 patchf.close()
1032 1032 self.pop(repo, force=True, wlock=wlock)
1033 1033 self.push(repo, force=True, wlock=wlock)
1034 1034
1035 1035 def init(self, repo, create=False):
1036 1036 if os.path.isdir(self.path):
1037 1037 raise util.Abort(_("patch queue directory already exists"))
1038 1038 os.mkdir(self.path)
1039 1039 if create:
1040 1040 return self.qrepo(create=True)
1041 1041
1042 1042 def unapplied(self, repo, patch=None):
1043 1043 if patch and patch not in self.series:
1044 1044 raise util.Abort(_("patch %s is not in series file") % patch)
1045 1045 if not patch:
1046 1046 start = self.series_end()
1047 1047 else:
1048 1048 start = self.series.index(patch) + 1
1049 1049 unapplied = []
1050 1050 for i in xrange(start, len(self.series)):
1051 1051 pushable, reason = self.pushable(i)
1052 1052 if pushable:
1053 1053 unapplied.append((i, self.series[i]))
1054 1054 self.explain_pushable(i)
1055 1055 return unapplied
1056 1056
1057 1057 def qseries(self, repo, missing=None, summary=False):
1058 1058 start = self.series_end(all_patches=True)
1059 1059 if not missing:
1060 1060 for i in range(len(self.series)):
1061 1061 patch = self.series[i]
1062 1062 if self.ui.verbose:
1063 1063 if i < start:
1064 1064 status = 'A'
1065 1065 elif self.pushable(i)[0]:
1066 1066 status = 'U'
1067 1067 else:
1068 1068 status = 'G'
1069 1069 self.ui.write('%d %s ' % (i, status))
1070 1070 if summary:
1071 1071 msg = self.readheaders(patch)[0]
1072 1072 msg = msg and ': ' + msg[0] or ': '
1073 1073 else:
1074 1074 msg = ''
1075 1075 self.ui.write('%s%s\n' % (patch, msg))
1076 1076 else:
1077 1077 msng_list = []
1078 1078 for root, dirs, files in os.walk(self.path):
1079 1079 d = root[len(self.path) + 1:]
1080 1080 for f in files:
1081 1081 fl = os.path.join(d, f)
1082 1082 if (fl not in self.series and
1083 1083 fl not in (self.status_path, self.series_path)
1084 1084 and not fl.startswith('.')):
1085 1085 msng_list.append(fl)
1086 1086 msng_list.sort()
1087 1087 for x in msng_list:
1088 1088 if self.ui.verbose:
1089 1089 self.ui.write("D ")
1090 1090 self.ui.write("%s\n" % x)
1091 1091
1092 1092 def issaveline(self, l):
1093 1093 if l.name == '.hg.patches.save.line':
1094 1094 return True
1095 1095
1096 1096 def qrepo(self, create=False):
1097 1097 if create or os.path.isdir(self.join(".hg")):
1098 1098 return hg.repository(self.ui, path=self.path, create=create)
1099 1099
1100 1100 def restore(self, repo, rev, delete=None, qupdate=None):
1101 1101 c = repo.changelog.read(rev)
1102 1102 desc = c[4].strip()
1103 1103 lines = desc.splitlines()
1104 1104 i = 0
1105 1105 datastart = None
1106 1106 series = []
1107 1107 applied = []
1108 1108 qpp = None
1109 1109 for i in xrange(0, len(lines)):
1110 1110 if lines[i] == 'Patch Data:':
1111 1111 datastart = i + 1
1112 1112 elif lines[i].startswith('Dirstate:'):
1113 1113 l = lines[i].rstrip()
1114 1114 l = l[10:].split(' ')
1115 1115 qpp = [ hg.bin(x) for x in l ]
1116 1116 elif datastart != None:
1117 1117 l = lines[i].rstrip()
1118 1118 se = statusentry(l)
1119 1119 file_ = se.name
1120 1120 if se.rev:
1121 1121 applied.append(se)
1122 1122 series.append(file_)
1123 1123 if datastart == None:
1124 1124 self.ui.warn("No saved patch data found\n")
1125 1125 return 1
1126 1126 self.ui.warn("restoring status: %s\n" % lines[0])
1127 1127 self.full_series = series
1128 1128 self.applied = applied
1129 1129 self.parse_series()
1130 1130 self.series_dirty = 1
1131 1131 self.applied_dirty = 1
1132 1132 heads = repo.changelog.heads()
1133 1133 if delete:
1134 1134 if rev not in heads:
1135 1135 self.ui.warn("save entry has children, leaving it alone\n")
1136 1136 else:
1137 1137 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1138 1138 pp = repo.dirstate.parents()
1139 1139 if rev in pp:
1140 1140 update = True
1141 1141 else:
1142 1142 update = False
1143 1143 self.strip(repo, rev, update=update, backup='strip')
1144 1144 if qpp:
1145 1145 self.ui.warn("saved queue repository parents: %s %s\n" %
1146 1146 (hg.short(qpp[0]), hg.short(qpp[1])))
1147 1147 if qupdate:
1148 1148 print "queue directory updating"
1149 1149 r = self.qrepo()
1150 1150 if not r:
1151 1151 self.ui.warn("Unable to load queue repository\n")
1152 1152 return 1
1153 1153 hg.clean(r, qpp[0])
1154 1154
1155 1155 def save(self, repo, msg=None):
1156 1156 if len(self.applied) == 0:
1157 1157 self.ui.warn("save: no patches applied, exiting\n")
1158 1158 return 1
1159 1159 if self.issaveline(self.applied[-1]):
1160 1160 self.ui.warn("status is already saved\n")
1161 1161 return 1
1162 1162
1163 1163 ar = [ ':' + x for x in self.full_series ]
1164 1164 if not msg:
1165 1165 msg = "hg patches saved state"
1166 1166 else:
1167 1167 msg = "hg patches: " + msg.rstrip('\r\n')
1168 1168 r = self.qrepo()
1169 1169 if r:
1170 1170 pp = r.dirstate.parents()
1171 1171 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1172 1172 msg += "\n\nPatch Data:\n"
1173 1173 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1174 1174 "\n".join(ar) + '\n' or "")
1175 1175 n = repo.commit(None, text, user=None, force=1)
1176 1176 if not n:
1177 1177 self.ui.warn("repo commit failed\n")
1178 1178 return 1
1179 1179 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1180 1180 self.applied_dirty = 1
1181 1181
1182 1182 def full_series_end(self):
1183 1183 if len(self.applied) > 0:
1184 1184 p = self.applied[-1].name
1185 1185 end = self.find_series(p)
1186 1186 if end == None:
1187 1187 return len(self.full_series)
1188 1188 return end + 1
1189 1189 return 0
1190 1190
1191 1191 def series_end(self, all_patches=False):
1192 1192 end = 0
1193 1193 def next(start):
1194 1194 if all_patches:
1195 1195 return start
1196 1196 i = start
1197 1197 while i < len(self.series):
1198 1198 p, reason = self.pushable(i)
1199 1199 if p:
1200 1200 break
1201 1201 self.explain_pushable(i)
1202 1202 i += 1
1203 1203 return i
1204 1204 if len(self.applied) > 0:
1205 1205 p = self.applied[-1].name
1206 1206 try:
1207 1207 end = self.series.index(p)
1208 1208 except ValueError:
1209 1209 return 0
1210 1210 return next(end + 1)
1211 1211 return next(end)
1212 1212
1213 1213 def qapplied(self, repo, patch=None):
1214 1214 if patch and patch not in self.series:
1215 1215 raise util.Abort(_("patch %s is not in series file") % patch)
1216 1216 if not patch:
1217 1217 end = len(self.applied)
1218 1218 else:
1219 1219 end = self.series.index(patch) + 1
1220 1220 for x in xrange(end):
1221 1221 p = self.appliedname(x)
1222 1222 self.ui.write("%s\n" % p)
1223 1223
1224 1224 def appliedname(self, index):
1225 1225 pname = self.applied[index].name
1226 1226 if not self.ui.verbose:
1227 1227 p = pname
1228 1228 else:
1229 1229 p = str(self.series.index(pname)) + " " + p
1230 1230 return p
1231 1231
1232 1232 def top(self, repo):
1233 1233 if len(self.applied):
1234 1234 p = self.appliedname(-1)
1235 1235 self.ui.write(p + '\n')
1236 1236 else:
1237 1237 self.ui.write("No patches applied\n")
1238 1238
1239 1239 def next(self, repo):
1240 1240 end = self.series_end()
1241 1241 if end == len(self.series):
1242 1242 self.ui.write("All patches applied\n")
1243 1243 else:
1244 1244 p = self.series[end]
1245 1245 if self.ui.verbose:
1246 1246 self.ui.write("%d " % self.series.index(p))
1247 1247 self.ui.write(p + '\n')
1248 1248
1249 1249 def prev(self, repo):
1250 1250 if len(self.applied) > 1:
1251 1251 p = self.appliedname(-2)
1252 1252 self.ui.write(p + '\n')
1253 1253 elif len(self.applied) == 1:
1254 1254 self.ui.write("Only one patch applied\n")
1255 1255 else:
1256 1256 self.ui.write("No patches applied\n")
1257 1257
1258 1258 def qimport(self, repo, files, patch=None, existing=None, force=None):
1259 1259 if len(files) > 1 and patch:
1260 1260 raise util.Abort(_('option "-n" not valid when importing multiple '
1261 1261 'files'))
1262 1262 i = 0
1263 1263 added = []
1264 1264 for filename in files:
1265 1265 if existing:
1266 1266 if not patch:
1267 1267 patch = filename
1268 1268 if not os.path.isfile(self.join(patch)):
1269 1269 raise util.Abort(_("patch %s does not exist") % patch)
1270 1270 else:
1271 1271 try:
1272 1272 text = file(filename).read()
1273 1273 except IOError:
1274 1274 raise util.Abort(_("unable to read %s") % patch)
1275 1275 if not patch:
1276 1276 patch = os.path.split(filename)[1]
1277 1277 if not force and os.path.exists(self.join(patch)):
1278 1278 raise util.Abort(_('patch "%s" already exists') % patch)
1279 1279 patchf = self.opener(patch, "w")
1280 1280 patchf.write(text)
1281 1281 if patch in self.series:
1282 1282 raise util.Abort(_('patch %s is already in the series file')
1283 1283 % patch)
1284 1284 index = self.full_series_end() + i
1285 1285 self.full_series[index:index] = [patch]
1286 1286 self.parse_series()
1287 1287 self.ui.warn("adding %s to series file\n" % patch)
1288 1288 i += 1
1289 1289 added.append(patch)
1290 1290 patch = None
1291 1291 self.series_dirty = 1
1292 1292 qrepo = self.qrepo()
1293 1293 if qrepo:
1294 1294 qrepo.add(added)
1295 1295
1296 1296 def delete(ui, repo, patch, **opts):
1297 1297 """remove a patch from the series file
1298 1298
1299 1299 The patch must not be applied.
1300 1300 With -f, deletes the patch file as well as the series entry."""
1301 1301 q = repo.mq
1302 1302 q.delete(repo, patch, force=opts.get('force'))
1303 1303 q.save_dirty()
1304 1304 return 0
1305 1305
1306 1306 def applied(ui, repo, patch=None, **opts):
1307 1307 """print the patches already applied"""
1308 1308 repo.mq.qapplied(repo, patch)
1309 1309 return 0
1310 1310
1311 1311 def unapplied(ui, repo, patch=None, **opts):
1312 1312 """print the patches not yet applied"""
1313 1313 for i, p in repo.mq.unapplied(repo, patch):
1314 1314 if ui.verbose:
1315 1315 ui.write("%d " % i)
1316 1316 ui.write("%s\n" % p)
1317 1317
1318 1318 def qimport(ui, repo, *filename, **opts):
1319 1319 """import a patch"""
1320 1320 q = repo.mq
1321 1321 q.qimport(repo, filename, patch=opts['name'],
1322 1322 existing=opts['existing'], force=opts['force'])
1323 1323 q.save_dirty()
1324 1324 return 0
1325 1325
1326 1326 def init(ui, repo, **opts):
1327 1327 """init a new queue repository
1328 1328
1329 1329 The queue repository is unversioned by default. If -c is
1330 1330 specified, qinit will create a separate nested repository
1331 1331 for patches. Use qcommit to commit changes to this queue
1332 1332 repository."""
1333 1333 q = repo.mq
1334 1334 r = q.init(repo, create=opts['create_repo'])
1335 1335 q.save_dirty()
1336 1336 if r:
1337 1337 fp = r.wopener('.hgignore', 'w')
1338 1338 print >> fp, 'syntax: glob'
1339 1339 print >> fp, 'status'
1340 1340 fp.close()
1341 1341 r.wopener('series', 'w').close()
1342 1342 r.add(['.hgignore', 'series'])
1343 1343 return 0
1344 1344
1345 1345 def clone(ui, source, dest=None, **opts):
1346 1346 '''clone main and patch repository at same time
1347 1347
1348 1348 If source is local, destination will have no patches applied. If
1349 1349 source is remote, this command can not check if patches are
1350 1350 applied in source, so cannot guarantee that patches are not
1351 1351 applied in destination. If you clone remote repository, be sure
1352 1352 before that it has no patches applied.
1353 1353
1354 1354 Source patch repository is looked for in <src>/.hg/patches by
1355 1355 default. Use -p <url> to change.
1356 1356 '''
1357 1357 commands.setremoteconfig(ui, opts)
1358 1358 if dest is None:
1359 1359 dest = hg.defaultdest(source)
1360 1360 sr = hg.repository(ui, ui.expandpath(source))
1361 1361 qbase, destrev = None, None
1362 1362 if sr.local():
1363 1363 reposetup(ui, sr)
1364 1364 if sr.mq.applied:
1365 1365 qbase = revlog.bin(sr.mq.applied[0].rev)
1366 1366 if not hg.islocal(dest):
1367 1367 destrev = sr.parents(qbase)[0]
1368 1368 ui.note(_('cloning main repo\n'))
1369 1369 sr, dr = hg.clone(ui, sr, dest,
1370 1370 pull=opts['pull'],
1371 1371 rev=destrev,
1372 1372 update=False,
1373 1373 stream=opts['uncompressed'])
1374 1374 ui.note(_('cloning patch repo\n'))
1375 1375 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1376 1376 dr.url() + '/.hg/patches',
1377 1377 pull=opts['pull'],
1378 1378 update=not opts['noupdate'],
1379 1379 stream=opts['uncompressed'])
1380 1380 if dr.local():
1381 1381 if qbase:
1382 1382 ui.note(_('stripping applied patches from destination repo\n'))
1383 1383 reposetup(ui, dr)
1384 1384 dr.mq.strip(dr, qbase, update=False, backup=None)
1385 1385 if not opts['noupdate']:
1386 1386 ui.note(_('updating destination repo\n'))
1387 1387 hg.update(dr, dr.changelog.tip())
1388 1388
1389 1389 def commit(ui, repo, *pats, **opts):
1390 1390 """commit changes in the queue repository"""
1391 1391 q = repo.mq
1392 1392 r = q.qrepo()
1393 1393 if not r: raise util.Abort('no queue repository')
1394 1394 commands.commit(r.ui, r, *pats, **opts)
1395 1395
1396 1396 def series(ui, repo, **opts):
1397 1397 """print the entire series file"""
1398 1398 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1399 1399 return 0
1400 1400
1401 1401 def top(ui, repo, **opts):
1402 1402 """print the name of the current patch"""
1403 1403 repo.mq.top(repo)
1404 1404 return 0
1405 1405
1406 1406 def next(ui, repo, **opts):
1407 1407 """print the name of the next patch"""
1408 1408 repo.mq.next(repo)
1409 1409 return 0
1410 1410
1411 1411 def prev(ui, repo, **opts):
1412 1412 """print the name of the previous patch"""
1413 1413 repo.mq.prev(repo)
1414 1414 return 0
1415 1415
1416 1416 def new(ui, repo, patch, **opts):
1417 1417 """create a new patch
1418 1418
1419 1419 qnew creates a new patch on top of the currently-applied patch
1420 1420 (if any). It will refuse to run if there are any outstanding
1421 1421 changes unless -f is specified, in which case the patch will
1422 1422 be initialised with them.
1423 1423
1424 1424 -m or -l set the patch header as well as the commit message.
1425 1425 If neither is specified, the patch header is empty and the
1426 1426 commit message is 'New patch: PATCH'"""
1427 1427 q = repo.mq
1428 1428 message = commands.logmessage(opts)
1429 1429 q.new(repo, patch, msg=message, force=opts['force'])
1430 1430 q.save_dirty()
1431 1431 return 0
1432 1432
1433 1433 def refresh(ui, repo, **opts):
1434 1434 """update the current patch"""
1435 1435 q = repo.mq
1436 1436 message = commands.logmessage(opts)
1437 1437 if opts['edit']:
1438 1438 if message:
1439 1439 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1440 1440 patch = q.applied[-1].name
1441 1441 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1442 1442 message = ui.edit('\n'.join(message), user or ui.username())
1443 1443 q.refresh(repo, msg=message, short=opts['short'])
1444 1444 q.save_dirty()
1445 1445 return 0
1446 1446
1447 1447 def diff(ui, repo, *files, **opts):
1448 1448 """diff of the current patch"""
1449 1449 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1450 1450 repo.mq.diff(repo, list(files))
1451 1451 return 0
1452 1452
1453 1453 def fold(ui, repo, *files, **opts):
1454 1454 """fold the named patches into the current patch
1455 1455
1456 1456 Patches must not yet be applied. Each patch will be successively
1457 1457 applied to the current patch in the order given. If all the
1458 1458 patches apply successfully, the current patch will be refreshed
1459 1459 with the new cumulative patch, and the folded patches will
1460 1460 be deleted. With -f/--force, the folded patch files will
1461 1461 be removed afterwards.
1462 1462
1463 1463 The header for each folded patch will be concatenated with
1464 1464 the current patch header, separated by a line of '* * *'."""
1465 1465
1466 1466 q = repo.mq
1467 1467
1468 1468 if not files:
1469 1469 raise util.Abort(_('qfold requires at least one patch name'))
1470 1470 if not q.check_toppatch(repo):
1471 1471 raise util.Abort(_('No patches applied\n'))
1472 1472
1473 1473 message = commands.logmessage(opts)
1474 1474 if opts['edit']:
1475 1475 if message:
1476 1476 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1477 1477
1478 1478 parent = q.lookup('qtip')
1479 1479 patches = []
1480 1480 messages = []
1481 1481 for f in files:
1482 1482 patch = q.lookup(f)
1483 1483 if patch in patches or patch == parent:
1484 1484 ui.warn(_('Skipping already folded patch %s') % patch)
1485 1485 if q.isapplied(patch):
1486 1486 raise util.Abort(_('qfold cannot fold already applied patch %s') % patch)
1487 1487 patches.append(patch)
1488 1488
1489 1489 for patch in patches:
1490 1490 if not message:
1491 1491 messages.append(q.readheaders(patch)[0])
1492 1492 pf = q.join(patch)
1493 1493 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1494 1494 if not patchsuccess:
1495 1495 raise util.Abort(_('Error folding patch %s') % patch)
1496 1496
1497 1497 if not message:
1498 1498 message, comments, user = q.readheaders(parent)[0:3]
1499 1499 for msg in messages:
1500 1500 message.append('* * *')
1501 1501 message.extend(msg)
1502 1502 message = '\n'.join(message)
1503 1503
1504 1504 if opts['edit']:
1505 1505 message = ui.edit(message, user or ui.username())
1506 1506
1507 1507 q.refresh(repo, msg=message)
1508 1508
1509 1509 for patch in patches:
1510 1510 q.delete(repo, patch, force=opts['force'])
1511 1511
1512 1512 q.save_dirty()
1513 1513
1514 1514 def guard(ui, repo, *args, **opts):
1515 1515 '''set or print guards for a patch
1516 1516
1517 1517 guards control whether a patch can be pushed. a patch with no
1518 1518 guards is aways pushed. a patch with posative guard ("+foo") is
1519 1519 pushed only if qselect command enables guard "foo". a patch with
1520 1520 nagative guard ("-foo") is never pushed if qselect command enables
1521 1521 guard "foo".
1522 1522
1523 1523 with no arguments, default is to print current active guards.
1524 1524 with arguments, set active guards for patch.
1525 1525
1526 1526 to set nagative guard "-foo" on topmost patch ("--" is needed so
1527 1527 hg will not interpret "-foo" as argument):
1528 1528 hg qguard -- -foo
1529 1529
1530 1530 to set guards on other patch:
1531 1531 hg qguard other.patch +2.6.17 -stable
1532 1532 '''
1533 1533 def status(idx):
1534 1534 guards = q.series_guards[idx] or ['unguarded']
1535 1535 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1536 1536 q = repo.mq
1537 1537 patch = None
1538 1538 args = list(args)
1539 1539 if opts['list']:
1540 1540 if args or opts['none']:
1541 1541 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1542 1542 for i in xrange(len(q.series)):
1543 1543 status(i)
1544 1544 return
1545 1545 if not args or args[0][0:1] in '-+':
1546 1546 if not q.applied:
1547 1547 raise util.Abort(_('no patches applied'))
1548 1548 patch = q.applied[-1].name
1549 1549 if patch is None and args[0][0:1] not in '-+':
1550 1550 patch = args.pop(0)
1551 1551 if patch is None:
1552 1552 raise util.Abort(_('no patch to work with'))
1553 1553 if args or opts['none']:
1554 1554 q.set_guards(q.find_series(patch), args)
1555 1555 q.save_dirty()
1556 1556 else:
1557 1557 status(q.series.index(q.lookup(patch)))
1558 1558
1559 1559 def header(ui, repo, patch=None):
1560 1560 """Print the header of the topmost or specified patch"""
1561 1561 q = repo.mq
1562 1562
1563 1563 if patch:
1564 1564 patch = q.lookup(patch)
1565 1565 else:
1566 1566 if not q.applied:
1567 1567 ui.write('No patches applied\n')
1568 1568 return
1569 1569 patch = q.lookup('qtip')
1570 1570 message = repo.mq.readheaders(patch)[0]
1571 1571
1572 1572 ui.write('\n'.join(message) + '\n')
1573 1573
1574 1574 def lastsavename(path):
1575 1575 (directory, base) = os.path.split(path)
1576 1576 names = os.listdir(directory)
1577 1577 namere = re.compile("%s.([0-9]+)" % base)
1578 1578 maxindex = None
1579 1579 maxname = None
1580 1580 for f in names:
1581 1581 m = namere.match(f)
1582 1582 if m:
1583 1583 index = int(m.group(1))
1584 1584 if maxindex == None or index > maxindex:
1585 1585 maxindex = index
1586 1586 maxname = f
1587 1587 if maxname:
1588 1588 return (os.path.join(directory, maxname), maxindex)
1589 1589 return (None, None)
1590 1590
1591 1591 def savename(path):
1592 1592 (last, index) = lastsavename(path)
1593 1593 if last is None:
1594 1594 index = 0
1595 1595 newpath = path + ".%d" % (index + 1)
1596 1596 return newpath
1597 1597
1598 1598 def push(ui, repo, patch=None, **opts):
1599 1599 """push the next patch onto the stack"""
1600 1600 q = repo.mq
1601 1601 mergeq = None
1602 1602
1603 1603 if opts['all']:
1604 1604 patch = q.series[-1]
1605 1605 if opts['merge']:
1606 1606 if opts['name']:
1607 1607 newpath = opts['name']
1608 1608 else:
1609 1609 newpath, i = lastsavename(q.path)
1610 1610 if not newpath:
1611 1611 ui.warn("no saved queues found, please use -n\n")
1612 1612 return 1
1613 1613 mergeq = queue(ui, repo.join(""), newpath)
1614 1614 ui.warn("merging with queue at: %s\n" % mergeq.path)
1615 1615 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1616 1616 mergeq=mergeq)
1617 1617 q.save_dirty()
1618 1618 return ret
1619 1619
1620 1620 def pop(ui, repo, patch=None, **opts):
1621 1621 """pop the current patch off the stack"""
1622 1622 localupdate = True
1623 1623 if opts['name']:
1624 1624 q = queue(ui, repo.join(""), repo.join(opts['name']))
1625 1625 ui.warn('using patch queue: %s\n' % q.path)
1626 1626 localupdate = False
1627 1627 else:
1628 1628 q = repo.mq
1629 1629 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1630 1630 q.save_dirty()
1631 1631 return 0
1632 1632
1633 1633 def rename(ui, repo, patch, name=None, **opts):
1634 1634 """rename a patch
1635 1635
1636 1636 With one argument, renames the current patch to PATCH1.
1637 1637 With two arguments, renames PATCH1 to PATCH2."""
1638 1638
1639 1639 q = repo.mq
1640 1640
1641 1641 if not name:
1642 1642 name = patch
1643 1643 patch = None
1644 1644
1645 1645 if name in q.series:
1646 1646 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1647 1647
1648 1648 absdest = q.join(name)
1649 1649 if os.path.exists(absdest):
1650 1650 raise util.Abort(_('%s already exists') % absdest)
1651 1651
1652 1652 if patch:
1653 1653 patch = q.lookup(patch)
1654 1654 else:
1655 1655 if not q.applied:
1656 1656 ui.write(_('No patches applied\n'))
1657 1657 return
1658 1658 patch = q.lookup('qtip')
1659 1659
1660 1660 if ui.verbose:
1661 1661 ui.write('Renaming %s to %s\n' % (patch, name))
1662 1662 i = q.find_series(patch)
1663 1663 q.full_series[i] = name
1664 1664 q.parse_series()
1665 1665 q.series_dirty = 1
1666 1666
1667 1667 info = q.isapplied(patch)
1668 1668 if info:
1669 1669 q.applied[info[0]] = statusentry(info[1], name)
1670 1670 q.applied_dirty = 1
1671 1671
1672 1672 util.rename(q.join(patch), absdest)
1673 1673 r = q.qrepo()
1674 1674 if r:
1675 1675 wlock = r.wlock()
1676 1676 if r.dirstate.state(name) == 'r':
1677 1677 r.undelete([name], wlock)
1678 1678 r.copy(patch, name, wlock)
1679 1679 r.remove([patch], False, wlock)
1680 1680
1681 1681 q.save_dirty()
1682 1682
1683 1683 def restore(ui, repo, rev, **opts):
1684 1684 """restore the queue state saved by a rev"""
1685 1685 rev = repo.lookup(rev)
1686 1686 q = repo.mq
1687 1687 q.restore(repo, rev, delete=opts['delete'],
1688 1688 qupdate=opts['update'])
1689 1689 q.save_dirty()
1690 1690 return 0
1691 1691
1692 1692 def save(ui, repo, **opts):
1693 1693 """save current queue state"""
1694 1694 q = repo.mq
1695 1695 message = commands.logmessage(opts)
1696 1696 ret = q.save(repo, msg=message)
1697 1697 if ret:
1698 1698 return ret
1699 1699 q.save_dirty()
1700 1700 if opts['copy']:
1701 1701 path = q.path
1702 1702 if opts['name']:
1703 1703 newpath = os.path.join(q.basepath, opts['name'])
1704 1704 if os.path.exists(newpath):
1705 1705 if not os.path.isdir(newpath):
1706 1706 raise util.Abort(_('destination %s exists and is not '
1707 1707 'a directory') % newpath)
1708 1708 if not opts['force']:
1709 1709 raise util.Abort(_('destination %s exists, '
1710 1710 'use -f to force') % newpath)
1711 1711 else:
1712 1712 newpath = savename(path)
1713 1713 ui.warn("copy %s to %s\n" % (path, newpath))
1714 1714 util.copyfiles(path, newpath)
1715 1715 if opts['empty']:
1716 1716 try:
1717 1717 os.unlink(q.join(q.status_path))
1718 1718 except:
1719 1719 pass
1720 1720 return 0
1721 1721
1722 1722 def strip(ui, repo, rev, **opts):
1723 1723 """strip a revision and all later revs on the same branch"""
1724 1724 rev = repo.lookup(rev)
1725 1725 backup = 'all'
1726 1726 if opts['backup']:
1727 1727 backup = 'strip'
1728 1728 elif opts['nobackup']:
1729 1729 backup = 'none'
1730 1730 repo.mq.strip(repo, rev, backup=backup)
1731 1731 return 0
1732 1732
1733 1733 def select(ui, repo, *args, **opts):
1734 1734 '''set or print guarded patches to push
1735 1735
1736 1736 use qguard command to set or print guards on patch. then use
1737 1737 qselect to tell mq which guards to use. example:
1738 1738
1739 1739 qguard foo.patch -stable (nagative guard)
1740 1740 qguard bar.patch +stable (posative guard)
1741 1741 qselect stable
1742 1742
1743 1743 this sets "stable" guard. mq will skip foo.patch (because it has
1744 1744 nagative match) but push bar.patch (because it has posative
1745 1745 match). patch is pushed only if all posative guards match and no
1746 1746 nagative guards match.
1747 1747
1748 1748 with no arguments, default is to print current active guards.
1749 1749 with arguments, set active guards as given.
1750 1750
1751 1751 use -n/--none to deactivate guards (no other arguments needed).
1752 1752 when no guards active, patches with posative guards are skipped,
1753 1753 patches with nagative guards are pushed.
1754 1754
1755 1755 qselect can change guards of applied patches. it does not pop
1756 1756 guarded patches by default. use --pop to pop back to last applied
1757 1757 patch that is not guarded. use --reapply (implies --pop) to push
1758 1758 back to current patch afterwards, but skip guarded patches.
1759 1759
1760 1760 use -s/--series to print list of all guards in series file (no
1761 1761 other arguments needed). use -v for more information.'''
1762 1762
1763 1763 q = repo.mq
1764 1764 guards = q.active()
1765 1765 if args or opts['none']:
1766 1766 old_unapplied = q.unapplied(repo)
1767 1767 old_guarded = [i for i in xrange(len(q.applied)) if
1768 1768 not q.pushable(i)[0]]
1769 1769 q.set_active(args)
1770 1770 q.save_dirty()
1771 1771 if not args:
1772 1772 ui.status(_('guards deactivated\n'))
1773 1773 if not opts['pop'] and not opts['reapply']:
1774 1774 unapplied = q.unapplied(repo)
1775 1775 guarded = [i for i in xrange(len(q.applied))
1776 1776 if not q.pushable(i)[0]]
1777 1777 if len(unapplied) != len(old_unapplied):
1778 1778 ui.status(_('number of unguarded, unapplied patches has '
1779 1779 'changed from %d to %d\n') %
1780 1780 (len(old_unapplied), len(unapplied)))
1781 1781 if len(guarded) != len(old_guarded):
1782 1782 ui.status(_('number of guarded, applied patches has changed '
1783 1783 'from %d to %d\n') %
1784 1784 (len(old_guarded), len(guarded)))
1785 1785 elif opts['series']:
1786 1786 guards = {}
1787 1787 noguards = 0
1788 1788 for gs in q.series_guards:
1789 1789 if not gs:
1790 1790 noguards += 1
1791 1791 for g in gs:
1792 1792 guards.setdefault(g, 0)
1793 1793 guards[g] += 1
1794 1794 if ui.verbose:
1795 1795 guards['NONE'] = noguards
1796 1796 guards = guards.items()
1797 1797 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
1798 1798 if guards:
1799 1799 ui.note(_('guards in series file:\n'))
1800 1800 for guard, count in guards:
1801 1801 ui.note('%2d ' % count)
1802 1802 ui.write(guard, '\n')
1803 1803 else:
1804 1804 ui.note(_('no guards in series file\n'))
1805 1805 else:
1806 1806 if guards:
1807 1807 ui.note(_('active guards:\n'))
1808 1808 for g in guards:
1809 1809 ui.write(g, '\n')
1810 1810 else:
1811 1811 ui.write(_('no active guards\n'))
1812 1812 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
1813 1813 popped = False
1814 1814 if opts['pop'] or opts['reapply']:
1815 1815 for i in xrange(len(q.applied)):
1816 1816 pushable, reason = q.pushable(i)
1817 1817 if not pushable:
1818 1818 ui.status(_('popping guarded patches\n'))
1819 1819 popped = True
1820 1820 if i == 0:
1821 1821 q.pop(repo, all=True)
1822 1822 else:
1823 1823 q.pop(repo, i-1)
1824 1824 break
1825 1825 if popped:
1826 1826 try:
1827 1827 if reapply:
1828 1828 ui.status(_('reapplying unguarded patches\n'))
1829 1829 q.push(repo, reapply)
1830 1830 finally:
1831 1831 q.save_dirty()
1832 1832
1833 1833 def reposetup(ui, repo):
1834 1834 class mqrepo(repo.__class__):
1835 def abort_if_wdir_patched(self, errmsg, force=False):
1836 if self.mq.applied and not force:
1837 parent = revlog.hex(self.dirstate.parents()[0])
1838 if parent in [s.rev for s in self.mq.applied]:
1839 raise util.Abort(errmsg)
1840
1835 1841 def commit(self, *args, **opts):
1836 1842 if len(args) >= 6:
1837 1843 force = args[5]
1838 1844 else:
1839 1845 force = opts.get('force')
1840 if self.mq.applied and not force:
1841 parent = revlog.hex(self.dirstate.parents()[0])
1842 if parent in [s.rev for s in self.mq.applied]:
1843 raise util.Abort(_('cannot commit over an applied mq patch'))
1846 self.abort_if_wdir_patched(
1847 _('cannot commit over an applied mq patch'),
1848 force)
1844 1849
1845 1850 return super(mqrepo, self).commit(*args, **opts)
1846 1851
1852 def push(self, remote, force=False, revs=None):
1853 if self.mq.applied and not force:
1854 raise util.Abort(_('source has mq patches applied'))
1855 return super(mqrepo, self).push(remote, force, revs)
1856
1847 1857 def tags(self):
1848 1858 if self.tagscache:
1849 1859 return self.tagscache
1850 1860
1851 1861 tagscache = super(mqrepo, self).tags()
1852 1862
1853 1863 q = self.mq
1854 1864 if not q.applied:
1855 1865 return tagscache
1856 1866
1857 1867 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1858 1868 mqtags.append((mqtags[-1][0], 'qtip'))
1859 1869 mqtags.append((mqtags[0][0], 'qbase'))
1860 1870 for patch in mqtags:
1861 1871 if patch[1] in tagscache:
1862 1872 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1863 1873 else:
1864 1874 tagscache[patch[1]] = revlog.bin(patch[0])
1865 1875
1866 1876 return tagscache
1867 1877
1868 1878 repo.__class__ = mqrepo
1869 1879 repo.mq = queue(ui, repo.join(""))
1870 1880
1871 1881 cmdtable = {
1872 1882 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1873 1883 "qclone": (clone,
1874 1884 [('', 'pull', None, _('use pull protocol to copy metadata')),
1875 1885 ('U', 'noupdate', None, _('do not update the new working directories')),
1876 1886 ('', 'uncompressed', None,
1877 1887 _('use uncompressed transfer (fast over LAN)')),
1878 1888 ('e', 'ssh', '', _('specify ssh command to use')),
1879 1889 ('p', 'patches', '', _('location of source patch repo')),
1880 1890 ('', 'remotecmd', '',
1881 1891 _('specify hg command to run on the remote side'))],
1882 1892 'hg qclone [OPTION]... SOURCE [DEST]'),
1883 1893 "qcommit|qci":
1884 1894 (commit,
1885 1895 commands.table["^commit|ci"][1],
1886 1896 'hg qcommit [OPTION]... [FILE]...'),
1887 1897 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1888 1898 "qdelete":
1889 1899 (delete,
1890 1900 [('f', 'force', None, _('delete patch file'))],
1891 1901 'hg qdelete [-f] PATCH'),
1892 1902 'qfold':
1893 1903 (fold,
1894 1904 [('e', 'edit', None, _('edit patch header')),
1895 1905 ('f', 'force', None, _('delete folded patch files')),
1896 1906 ('m', 'message', '', _('set patch header to <text>')),
1897 1907 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
1898 1908 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
1899 1909 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
1900 1910 ('n', 'none', None, _('drop all guards'))],
1901 1911 'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
1902 1912 'qheader': (header, [],
1903 1913 _('hg qheader [PATCH]')),
1904 1914 "^qimport":
1905 1915 (qimport,
1906 1916 [('e', 'existing', None, 'import file in patch dir'),
1907 1917 ('n', 'name', '', 'patch file name'),
1908 1918 ('f', 'force', None, 'overwrite existing files')],
1909 1919 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1910 1920 "^qinit":
1911 1921 (init,
1912 1922 [('c', 'create-repo', None, 'create queue repository')],
1913 1923 'hg qinit [-c]'),
1914 1924 "qnew":
1915 1925 (new,
1916 1926 [('m', 'message', '', _('use <text> as commit message')),
1917 1927 ('l', 'logfile', '', _('read the commit message from <file>')),
1918 1928 ('f', 'force', None, _('import uncommitted changes into patch'))],
1919 1929 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1920 1930 "qnext": (next, [], 'hg qnext'),
1921 1931 "qprev": (prev, [], 'hg qprev'),
1922 1932 "^qpop":
1923 1933 (pop,
1924 1934 [('a', 'all', None, 'pop all patches'),
1925 1935 ('n', 'name', '', 'queue name to pop'),
1926 1936 ('f', 'force', None, 'forget any local changes')],
1927 1937 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1928 1938 "^qpush":
1929 1939 (push,
1930 1940 [('f', 'force', None, 'apply if the patch has rejects'),
1931 1941 ('l', 'list', None, 'list patch name in commit text'),
1932 1942 ('a', 'all', None, 'apply all patches'),
1933 1943 ('m', 'merge', None, 'merge from another queue'),
1934 1944 ('n', 'name', '', 'merge queue name')],
1935 1945 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1936 1946 "^qrefresh":
1937 1947 (refresh,
1938 1948 [('e', 'edit', None, _('edit commit message')),
1939 1949 ('m', 'message', '', _('change commit message with <text>')),
1940 1950 ('l', 'logfile', '', _('change commit message with <file> content')),
1941 1951 ('s', 'short', None, 'short refresh')],
1942 1952 'hg qrefresh [-e] [-m TEXT] [-l FILE] [-s]'),
1943 1953 'qrename|qmv':
1944 1954 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
1945 1955 "qrestore":
1946 1956 (restore,
1947 1957 [('d', 'delete', None, 'delete save entry'),
1948 1958 ('u', 'update', None, 'update queue working dir')],
1949 1959 'hg qrestore [-d] [-u] REV'),
1950 1960 "qsave":
1951 1961 (save,
1952 1962 [('m', 'message', '', _('use <text> as commit message')),
1953 1963 ('l', 'logfile', '', _('read the commit message from <file>')),
1954 1964 ('c', 'copy', None, 'copy patch directory'),
1955 1965 ('n', 'name', '', 'copy directory name'),
1956 1966 ('e', 'empty', None, 'clear queue status file'),
1957 1967 ('f', 'force', None, 'force copy')],
1958 1968 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1959 1969 "qselect": (select,
1960 1970 [('n', 'none', None, _('disable all guards')),
1961 1971 ('s', 'series', None, _('list all guards in series file')),
1962 1972 ('', 'pop', None,
1963 1973 _('pop to before first guarded applied patch')),
1964 1974 ('', 'reapply', None, _('pop, then reapply patches'))],
1965 1975 'hg qselect [OPTION...] [GUARD...]'),
1966 1976 "qseries":
1967 1977 (series,
1968 1978 [('m', 'missing', None, 'print patches not in series'),
1969 1979 ('s', 'summary', None, _('print first line of patch header'))],
1970 1980 'hg qseries [-m]'),
1971 1981 "^strip":
1972 1982 (strip,
1973 1983 [('f', 'force', None, 'force multi-head removal'),
1974 1984 ('b', 'backup', None, 'bundle unrelated changesets'),
1975 1985 ('n', 'nobackup', None, 'no backups')],
1976 1986 'hg strip [-f] [-b] [-n] REV'),
1977 1987 "qtop": (top, [], 'hg qtop'),
1978 1988 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1979 1989 }
1980 1990
@@ -1,117 +1,128 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 echo % help
8 8 hg help mq
9 9
10 10 hg init a
11 11 cd a
12 12 echo a > a
13 hg ci -Ama
14
15 hg clone . ../k
16
13 17 mkdir b
14 18 echo z > b/z
15 19 hg ci -Ama
16 20
17 21 echo % qinit
18 22
19 23 hg qinit
20 24
21 25 cd ..
22 26 hg init b
23 27
24 28 echo % -R qinit
25 29
26 30 hg -R b qinit
27 31
28 32 hg init c
29 33
30 34 echo % qinit -c
31 35
32 36 hg --cwd c qinit -c
33 37 hg -R c/.hg/patches st
34 38
35 39 echo % qnew implies add
36 40
37 41 hg -R c qnew test.patch
38 42 hg -R c/.hg/patches st
39 43
40 44 cd a
41 45
42 46 echo % qnew -m
43 47
44 48 hg qnew -m 'foo bar' test.patch
45 49 cat .hg/patches/test.patch
46 50
47 51 echo % qrefresh
48 52
49 53 echo a >> a
50 54 hg qrefresh
51 55 sed -e "s/^\(diff -r \)\([a-f0-9]* \)/\1 x/" \
52 56 -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
53 57 -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" .hg/patches/test.patch
54 58
55 59 echo % qpop
56 60
57 61 hg qpop
58 62
59 63 echo % qpush
60 64
61 65 hg qpush
62 66
63 67 cd ..
64 68
65 69 echo % pop/push outside repo
66 70
67 71 hg -R a qpop
68 72 hg -R a qpush
69 73
70 74 cd a
71 75 hg qnew test2.patch
72 76
73 77 echo % qrefresh in subdir
74 78
75 79 cd b
76 80 echo a > a
77 81 hg add a
78 82 hg qrefresh
79 83
80 84 echo % pop/push -a in subdir
81 85
82 86 hg qpop -a
83 87 hg --traceback qpush -a
84 88
85 89 echo % qseries
86 90 hg qseries
87 91
88 92 echo % qapplied
89 93 hg qapplied
90 94
91 95 echo % qtop
92 96 hg qtop
93 97
94 98 echo % qprev
95 99 hg qprev
96 100
97 101 echo % qnext
98 102 hg qnext
99 103
100 104 echo % pop, qnext, qprev, qapplied
101 105 hg qpop
102 106 hg qnext
103 107 hg qprev
104 108 hg qapplied
105 109
106 110 echo % commit should fail
107 111 hg commit
108 112
113 echo % push should fail
114 hg push ../../k
115
109 116 echo % qunapplied
110 117 hg qunapplied
111 118
119 echo % push should succeed
120 hg qpop -a
121 hg push ../../k
122
112 123 echo % strip
113 124 cd ../../b
114 125 echo x>x
115 126 hg ci -Ama
116 127 hg strip tip 2>&1 | sed 's/\(saving bundle to \).*/\1/'
117 128 hg unbundle .hg/strip-backup/*
@@ -1,117 +1,121 b''
1 1 % help
2 2 mq extension - patch management and development
3 3
4 4 This extension lets you work with a stack of patches in a Mercurial
5 5 repository. It manages two stacks of patches - all known patches, and
6 6 applied patches (subset of known patches).
7 7
8 8 Known patches are represented as patch files in the .hg/patches
9 9 directory. Applied patches are both patch files and changesets.
10 10
11 11 Common tasks (use "hg help command" for more details):
12 12
13 13 prepare repository to work with patches qinit
14 14 create new patch qnew
15 15 import existing patch qimport
16 16
17 17 print patch series qseries
18 18 print applied patches qapplied
19 19 print name of top applied patch qtop
20 20
21 21 add known patch to applied stack qpush
22 22 remove patch from applied stack qpop
23 23 refresh contents of top applied patch qrefresh
24 24
25 25 list of commands (use "hg help -v mq" to show aliases and global options):
26 26
27 27 qapplied print the patches already applied
28 28 qclone clone main and patch repository at same time
29 29 qcommit commit changes in the queue repository
30 30 qdelete remove a patch from the series file
31 31 qdiff diff of the current patch
32 32 qfold fold the named patches into the current patch
33 33 qguard set or print guards for a patch
34 34 qheader Print the header of the topmost or specified patch
35 35 qimport import a patch
36 36 qinit init a new queue repository
37 37 qnew create a new patch
38 38 qnext print the name of the next patch
39 39 qpop pop the current patch off the stack
40 40 qprev print the name of the previous patch
41 41 qpush push the next patch onto the stack
42 42 qrefresh update the current patch
43 43 qrename rename a patch
44 44 qrestore restore the queue state saved by a rev
45 45 qsave save current queue state
46 46 qselect set or print guarded patches to push
47 47 qseries print the entire series file
48 48 qtop print the name of the current patch
49 49 qunapplied print the patches not yet applied
50 50 strip strip a revision and all later revs on the same branch
51 51 adding a
52 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
52 53 adding b/z
53 54 % qinit
54 55 % -R qinit
55 56 % qinit -c
56 57 A .hgignore
57 58 A series
58 59 % qnew implies add
59 60 A .hgignore
60 61 A series
61 62 A test.patch
62 63 % qnew -m
63 64 foo bar
64 65 % qrefresh
65 66 foo bar
66 67
67 68 diff -r xa
68 69 --- a/a
69 70 +++ b/a
70 71 @@ -1,1 +1,2 @@ a
71 72 a
72 73 +a
73 74 % qpop
74 75 Patch queue now empty
75 76 % qpush
76 77 applying test.patch
77 78 Now at: test.patch
78 79 % pop/push outside repo
79 80 Patch queue now empty
80 81 applying test.patch
81 82 Now at: test.patch
82 83 % qrefresh in subdir
83 84 % pop/push -a in subdir
84 85 Patch queue now empty
85 86 applying test.patch
86 87 applying test2.patch
87 88 Now at: test2.patch
88 89 % qseries
89 90 test.patch
90 91 test2.patch
91 92 % qapplied
92 93 test.patch
93 94 test2.patch
94 95 % qtop
95 96 test2.patch
96 97 % qprev
97 98 test.patch
98 99 % qnext
99 100 All patches applied
100 101 % pop, qnext, qprev, qapplied
101 102 Now at: test.patch
102 103 test2.patch
103 104 Only one patch applied
104 105 test.patch
105 106 % commit should fail
106 107 abort: cannot commit over an applied mq patch
108 % push should fail
109 pushing to ../../k
110 abort: source has mq patches applied
107 111 % qunapplied
108 112 test2.patch
109 113 % strip
110 114 adding x
111 115 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
112 116 saving bundle to
113 117 adding changesets
114 118 adding manifests
115 119 adding file changes
116 120 added 1 changesets with 1 changes to 1 files
117 121 (run 'hg update' to get a working copy)
General Comments 0
You need to be logged in to leave comments. Login now