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