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