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