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