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