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