hgext/mq.py
changeset 1808 7036cd7f770d
child 1810 7596611ab3d5
equal deleted inserted replaced
1807:f1f43ea22cbf 1808:7036cd7f770d
       
     1 #!/usr/bin/env python
       
     2 # queue.py - patch queues for mercurial
       
     3 #
       
     4 # Copyright 2005 Chris Mason <mason@suse.com>
       
     5 #
       
     6 # This software may be used and distributed according to the terms
       
     7 # of the GNU General Public License, incorporated herein by reference.
       
     8 
       
     9 from mercurial.demandload import *
       
    10 demandload(globals(), "os sys re struct traceback errno bz2")
       
    11 from mercurial.i18n import gettext as _
       
    12 from mercurial import ui, hg, revlog, commands, util
       
    13 
       
    14 versionstr = "0.45"
       
    15 
       
    16 repomap = {}
       
    17 
       
    18 class queue:
       
    19     def __init__(self, ui, path, patchdir=None):
       
    20         self.opener = util.opener(path)
       
    21         self.basepath = path
       
    22         if patchdir:
       
    23             self.path = patchdir
       
    24         else:
       
    25             self.path = os.path.join(path, "patches")
       
    26         self.ui = ui
       
    27         self.applied = []
       
    28         self.full_series = []
       
    29         self.applied_dirty = 0
       
    30         self.series_dirty = 0
       
    31         self.series_path = os.path.join(self.path, "series")
       
    32         self.status_path = os.path.join(self.path, "status")
       
    33         
       
    34         s = self.series_path
       
    35         if os.path.exists(s):
       
    36             self.full_series = self.opener(s).read().splitlines()
       
    37         self.read_series(self.full_series)
       
    38 
       
    39         s = self.status_path
       
    40         if os.path.exists(s):
       
    41             self.applied = self.opener(s).read().splitlines()
       
    42 
       
    43     def find_series(self, patch):
       
    44         pre = re.compile("(\s*)([^#]+)")
       
    45         index = 0
       
    46         for l in self.full_series:
       
    47             m = pre.match(l)
       
    48             if m:
       
    49                 s = m.group(2)
       
    50                 s = s.rstrip()
       
    51                 if s == patch:
       
    52                     return index
       
    53             index += 1
       
    54         return None
       
    55 
       
    56     def read_series(self, list):
       
    57         def matcher(list):
       
    58             pre = re.compile("(\s*)([^#]+)")
       
    59             for l in list:
       
    60                 m = pre.match(l)
       
    61                 if m:
       
    62                     s = m.group(2)
       
    63                     s = s.rstrip()
       
    64                     if len(s) > 0:
       
    65                         yield s
       
    66         self.series = []
       
    67         self.series = [ x for x in matcher(list) ]
       
    68         
       
    69     def save_dirty(self):
       
    70         if self.applied_dirty:
       
    71             if len(self.applied) > 0:
       
    72                 nl = "\n"
       
    73             else:
       
    74                 nl = ""
       
    75             f = self.opener(self.status_path, "w")
       
    76             f.write("\n".join(self.applied) + nl)
       
    77         if self.series_dirty:
       
    78             if len(self.full_series) > 0:
       
    79                 nl = "\n"
       
    80             else:
       
    81                 nl = ""
       
    82             f = self.opener(self.series_path, "w")
       
    83             f.write("\n".join(self.full_series) + nl)
       
    84 
       
    85     def readheaders(self, patch):
       
    86         def eatdiff(lines):
       
    87             while lines:
       
    88                 l = lines[-1]
       
    89                 if l.startswith("diff -") or \
       
    90                    l.startswith("Index:") or \
       
    91                    l.startswith("==========="):
       
    92                     del lines[-1]
       
    93                 else:
       
    94                     break
       
    95         def eatempty(lines):
       
    96             while lines:
       
    97                 l = lines[-1]
       
    98                 if re.match('\s*$', l):
       
    99                     del lines[-1]
       
   100                 else:
       
   101                     break
       
   102 
       
   103         pf = os.path.join(self.path, patch)
       
   104         message = []
       
   105         comments = []
       
   106         user = None
       
   107         format = None
       
   108         subject = None
       
   109         diffstart = 0
       
   110 
       
   111         for line in file(pf):
       
   112             line = line.rstrip()
       
   113             if diffstart:
       
   114                 if line.startswith('+++ '):
       
   115                     diffstart = 2
       
   116                 break
       
   117             if line.startswith("--- "):
       
   118                 diffstart = 1
       
   119                 continue
       
   120             elif format == "hgpatch":
       
   121                 # parse values when importing the result of an hg export
       
   122                 if line.startswith("# User "):
       
   123                     user = line[7:]
       
   124                 elif not line.startswith("# ") and line:
       
   125                     message.append(line)
       
   126                     format = None
       
   127             elif line == '# HG changeset patch':
       
   128                 format = "hgpatch"
       
   129             elif format != "tagdone" and \
       
   130                  (line.startswith("Subject: ") or \
       
   131                  line.startswith("subject: ")):
       
   132                 subject = line[9:]
       
   133                 format = "tag"
       
   134             elif format != "tagdone" and \
       
   135                  (line.startswith("From: ") or \
       
   136                  line.startswith("from: ")):
       
   137                 user = line[6:]
       
   138                 format = "tag"
       
   139             elif format == "tag" and line == "":
       
   140                 # when looking for tags (subject: from: etc) they
       
   141                 # end once you find a blank line in the source
       
   142                 format = "tagdone"
       
   143             else:
       
   144                 message.append(line)
       
   145             comments.append(line)
       
   146 
       
   147         eatdiff(message)
       
   148         eatdiff(comments)
       
   149         eatempty(message)
       
   150         eatempty(comments)
       
   151 
       
   152         # make sure message isn't empty
       
   153         if format and format.startswith("tag") and subject:
       
   154             message.insert(0, "")
       
   155             message.insert(0, subject)
       
   156         return (message, comments, user, diffstart > 1)
       
   157 
       
   158     def mergeone(self, repo, mergeq, head, patch, rev, wlock):
       
   159         # first try just applying the patch
       
   160         (err, n) = self.apply(repo, [ patch ], update_status=False, 
       
   161                               strict=True, merge=rev, wlock=wlock)
       
   162 
       
   163         if err == 0:
       
   164             return (err, n)
       
   165 
       
   166         if n is None:
       
   167             self.ui.warn("apply failed for patch %s\n" % patch)
       
   168             sys.exit(1)
       
   169 
       
   170         self.ui.warn("patch didn't work out, merging %s\n" % patch)
       
   171 
       
   172         # apply failed, strip away that rev and merge.
       
   173         repo.update(head, allow=False, force=True, wlock=wlock)
       
   174         self.strip(repo, n, update=False, backup='strip', wlock=wlock)
       
   175 
       
   176         c = repo.changelog.read(rev)
       
   177         ret = repo.update(rev, allow=True, wlock=wlock)
       
   178         if ret:
       
   179             self.ui.warn("update returned %d\n" % ret)
       
   180             sys.exit(1)
       
   181         n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
       
   182         if n == None:
       
   183             self.ui.warn("repo commit failed\n")
       
   184             sys.exit(1)
       
   185         try:
       
   186             message, comments, user, patchfound = mergeq.readheaders(patch)
       
   187         except:
       
   188             self.ui.warn("Unable to read %s\n" % patch)
       
   189             sys.exit(1)
       
   190 
       
   191         patchf = self.opener(os.path.join(self.path, patch), "w")
       
   192         if comments:
       
   193             comments = "\n".join(comments) + '\n\n'
       
   194             patchf.write(comments)
       
   195         commands.dodiff(patchf, self.ui, repo, head, n)
       
   196         patchf.close()
       
   197         return (0, n)
       
   198         
       
   199     def qparents(self, repo, rev=None):
       
   200         if rev is None:
       
   201             (p1, p2) = repo.dirstate.parents()
       
   202             if p2 == revlog.nullid:
       
   203                 return p1
       
   204             if len(self.applied) == 0:
       
   205                 return None
       
   206             (top, patch) = self.applied[-1].split(':')
       
   207             top = revlog.bin(top)
       
   208             return top
       
   209         pp = repo.changelog.parents(rev)
       
   210         if pp[1] != revlog.nullid:
       
   211             arevs = [ x.split(':')[0] for x in self.applied ]
       
   212             p0 = revlog.hex(pp[0])
       
   213             p1 = revlog.hex(pp[1])
       
   214             if p0 in arevs:
       
   215                 return pp[0]
       
   216             if p1 in arevs:
       
   217                 return pp[1]
       
   218             return None
       
   219         return pp[0]
       
   220 
       
   221     def mergepatch(self, repo, mergeq, series, wlock):
       
   222         if len(self.applied) == 0:
       
   223             # each of the patches merged in will have two parents.  This
       
   224             # can confuse the qrefresh, qdiff, and strip code because it
       
   225             # needs to know which parent is actually in the patch queue.
       
   226             # so, we insert a merge marker with only one parent.  This way
       
   227             # the first patch in the queue is never a merge patch
       
   228             #
       
   229             pname = ".hg.patches.merge.marker"
       
   230             n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
       
   231                             wlock=wlock)
       
   232             self.applied.append(revlog.hex(n) + ":" + pname)
       
   233             self.applied_dirty = 1
       
   234 
       
   235         head = self.qparents(repo)
       
   236 
       
   237         for patch in series:
       
   238             patch = mergeq.lookup(patch)
       
   239             if not patch:
       
   240                 self.ui.warn("patch %s does not exist\n" % patch)
       
   241                 return (1, None)
       
   242 
       
   243             info = mergeq.isapplied(patch)
       
   244             if not info:
       
   245                 self.ui.warn("patch %s is not applied\n" % patch)
       
   246                 return (1, None)
       
   247             rev = revlog.bin(info[1])
       
   248             (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
       
   249             if head:
       
   250                 self.applied.append(revlog.hex(head) + ":" + patch)
       
   251                 self.applied_dirty = 1
       
   252             if err:
       
   253                 return (err, head)
       
   254         return (0, head)
       
   255 
       
   256     def apply(self, repo, series, list=False, update_status=True, 
       
   257               strict=False, patchdir=None, merge=None, wlock=None):
       
   258         # TODO unify with commands.py
       
   259         if not patchdir:
       
   260             patchdir = self.path
       
   261         pwd = os.getcwd()
       
   262         os.chdir(repo.root)
       
   263         err = 0
       
   264         if not wlock:
       
   265             wlock = repo.wlock()
       
   266         lock = repo.lock()
       
   267         tr = repo.transaction()
       
   268         n = None
       
   269         for patch in series:
       
   270             self.ui.warn("applying %s\n" % patch)
       
   271             pf = os.path.join(patchdir, patch)
       
   272 
       
   273             try:
       
   274                 message, comments, user, patchfound = self.readheaders(patch)
       
   275             except:
       
   276                 self.ui.warn("Unable to read %s\n" % pf)
       
   277                 err = 1
       
   278                 break
       
   279 
       
   280             if not message:
       
   281                 message = "imported patch %s\n" % patch
       
   282             else:
       
   283                 if list:
       
   284                     message.append("\nimported patch %s" % patch)
       
   285                 message = '\n'.join(message)
       
   286 
       
   287             try:
       
   288                 f = os.popen("patch -p1 --no-backup-if-mismatch < '%s'" % 
       
   289                                   (pf))
       
   290             except:
       
   291                 self.ui.warn("patch failed, unable to continue (try -v)\n")
       
   292                 err = 1
       
   293                 break
       
   294             files = []
       
   295             fuzz = False
       
   296             for l in f:
       
   297                 l = l.rstrip('\r\n');
       
   298                 if self.ui.verbose:
       
   299                     self.ui.warn(l + "\n")
       
   300                 if l[:14] == 'patching file ':
       
   301                     pf = os.path.normpath(l[14:])
       
   302                     # when patch finds a space in the file name, it puts 
       
   303                     # single quotes around the filename.  strip them off
       
   304                     if pf[0] == "'" and pf[-1] == "'":
       
   305                         pf = pf[1:-1]
       
   306                     if pf not in files:
       
   307                         files.append(pf)
       
   308                     printed_file = False
       
   309                     file_str = l
       
   310                 elif l.find('with fuzz') >= 0:
       
   311                     if not printed_file:
       
   312                         self.ui.warn(file_str + '\n')
       
   313                         printed_file = True
       
   314                     self.ui.warn(l + '\n')
       
   315                     fuzz = True
       
   316                 elif l.find('saving rejects to file') >= 0:
       
   317                     self.ui.warn(l + '\n')
       
   318                 elif l.find('FAILED') >= 0:
       
   319                     if not printed_file:
       
   320                         self.ui.warn(file_str + '\n')
       
   321                         printed_file = True
       
   322                     self.ui.warn(l + '\n')
       
   323             patcherr = f.close()
       
   324 
       
   325             if merge and len(files) > 0:
       
   326                  # Mark as merged and update dirstate parent info
       
   327                  repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
       
   328                  p1, p2 = repo.dirstate.parents()
       
   329                  repo.dirstate.setparents(p1, merge)
       
   330             if len(files) > 0:
       
   331                 commands.addremove_lock(self.ui, repo, files, 
       
   332                                         opts={}, wlock=wlock)
       
   333             n = repo.commit(files, message, user, force=1, lock=lock,
       
   334                             wlock=wlock)
       
   335 
       
   336             if n == None:
       
   337                 self.ui.warn("repo commit failed\n")
       
   338                 sys.exit(1)
       
   339 
       
   340             if update_status:
       
   341                 self.applied.append(revlog.hex(n) + ":" + patch)
       
   342 
       
   343             if patcherr:
       
   344                 if not patchfound:
       
   345                     self.ui.warn("patch %s is empty\n" % patch)
       
   346                     err = 0
       
   347                 else:
       
   348                     self.ui.warn("patch failed, rejects left in working dir\n")
       
   349                     err = 1
       
   350                 break
       
   351 
       
   352             if fuzz and strict:
       
   353                 self.ui.warn("fuzz found when applying patch, stopping\n")
       
   354                 err = 1
       
   355                 break
       
   356         tr.close()
       
   357         os.chdir(pwd)
       
   358         return (err, n)
       
   359 
       
   360     def delete(self, repo, patch):
       
   361         patch = self.lookup(patch)
       
   362         info = self.isapplied(patch)
       
   363         if info:
       
   364             self.ui.warn("cannot delete applied patch %s\n" % patch)
       
   365             sys.exit(1)
       
   366         if patch not in self.series:
       
   367             self.ui.warn("patch %s not in series file\n" % patch)
       
   368             sys.exit(1)
       
   369         i = self.find_series(patch)
       
   370         del self.full_series[i]
       
   371         self.read_series(self.full_series)
       
   372         self.series_dirty = 1
       
   373     
       
   374     def check_toppatch(self, repo):
       
   375         if len(self.applied) > 0:
       
   376             (top, patch) = self.applied[-1].split(':')
       
   377             top = revlog.bin(top)
       
   378             pp = repo.dirstate.parents()
       
   379             if top not in pp:
       
   380                 self.ui.warn("queue top not at dirstate parents. top %s dirstate %s %s\n" %( revlog.short(top), revlog.short(pp[0]), revlog.short(pp[1])))
       
   381                 sys.exit(1)
       
   382             return top
       
   383         return None
       
   384     def check_localchanges(self, repo):
       
   385         (c, a, r, d, u) = repo.changes(None, None)
       
   386         if c or a or d or r:
       
   387             self.ui.write("Local changes found, refresh first\n")
       
   388             sys.exit(1)
       
   389     def new(self, repo, patch, msg=None, force=None):
       
   390         if not force:
       
   391             self.check_localchanges(repo)
       
   392         self.check_toppatch(repo)
       
   393         wlock = repo.wlock()
       
   394         insert = self.series_end()
       
   395         if msg:
       
   396            n = repo.commit([], "[mq]: %s" % msg, force=True, wlock=wlock)
       
   397         else:
       
   398            n = repo.commit([], "New patch: %s" % patch, force=True, wlock=wlock)
       
   399         if n == None:
       
   400             self.ui.warn("repo commit failed\n")
       
   401             sys.exit(1)
       
   402         self.full_series[insert:insert] = [patch]
       
   403         self.applied.append(revlog.hex(n) + ":" + patch)
       
   404         self.read_series(self.full_series)
       
   405         self.series_dirty = 1
       
   406         self.applied_dirty = 1
       
   407         p = self.opener(os.path.join(self.path, patch), "w")
       
   408         if msg:
       
   409             msg = msg + "\n"
       
   410             p.write(msg)
       
   411         p.close()
       
   412         wlock = None
       
   413         r = self.qrepo()
       
   414         if r: r.add([patch])
       
   415 
       
   416     def strip(self, repo, rev, update=True, backup="all", wlock=None):
       
   417         def limitheads(chlog, stop):
       
   418             """return the list of all nodes that have no children"""
       
   419             p = {}
       
   420             h = []
       
   421             stoprev = 0
       
   422             if stop in chlog.nodemap:
       
   423                 stoprev = chlog.rev(stop)
       
   424 
       
   425             for r in range(chlog.count() - 1, -1, -1):
       
   426                 n = chlog.node(r)
       
   427                 if n not in p:
       
   428                     h.append(n)
       
   429                 if n == stop:
       
   430                     break
       
   431                 if r < stoprev:
       
   432                     break
       
   433                 for pn in chlog.parents(n):
       
   434                     p[pn] = 1
       
   435             return h
       
   436 
       
   437         def bundle(cg):
       
   438             backupdir = repo.join("strip-backup")
       
   439             if not os.path.isdir(backupdir):
       
   440                 os.mkdir(backupdir)
       
   441             name = os.path.join(backupdir, "%s" % revlog.short(rev))
       
   442             name = savename(name)
       
   443             self.ui.warn("saving bundle to %s\n" % name)
       
   444             # TODO, exclusive open
       
   445             f = open(name, "wb")
       
   446             try:
       
   447                 f.write("HG10")
       
   448                 z = bz2.BZ2Compressor(9)
       
   449                 while 1:
       
   450                     chunk = cg.read(4096)
       
   451                     if not chunk:
       
   452                         break
       
   453                     f.write(z.compress(chunk))
       
   454                 f.write(z.flush())
       
   455             except:
       
   456                 os.unlink(name)
       
   457                 raise
       
   458             f.close()
       
   459             return name
       
   460 
       
   461         def stripall(rev, revnum):
       
   462             cl = repo.changelog
       
   463             c = cl.read(rev)
       
   464             mm = repo.manifest.read(c[0])
       
   465             seen = {}
       
   466 
       
   467             for x in xrange(revnum, cl.count()):
       
   468                 c = cl.read(cl.node(x))
       
   469                 for f in c[3]:
       
   470                     if f in seen:
       
   471                         continue
       
   472                     seen[f] = 1
       
   473                     if f in mm:
       
   474                         filerev = mm[f]
       
   475                     else:
       
   476                         filerev = 0
       
   477                     seen[f] = filerev
       
   478             # we go in two steps here so the strip loop happens in a
       
   479             # sensible order.  When stripping many files, this helps keep
       
   480             # our disk access patterns under control.
       
   481             list = seen.keys()
       
   482             list.sort()
       
   483             for f in list:
       
   484                 ff = repo.file(f)
       
   485                 filerev = seen[f]
       
   486                 if filerev != 0:
       
   487                     if filerev in ff.nodemap:
       
   488                         filerev = ff.rev(filerev)
       
   489                     else:
       
   490                         filerev = 0
       
   491                 ff.strip(filerev, revnum)
       
   492 
       
   493         if not wlock:
       
   494             wlock = repo.wlock()
       
   495         lock = repo.lock()
       
   496         chlog = repo.changelog
       
   497         # TODO delete the undo files, and handle undo of merge sets
       
   498         pp = chlog.parents(rev)
       
   499         revnum = chlog.rev(rev)
       
   500 
       
   501         if update:
       
   502             urev = self.qparents(repo, rev)
       
   503             repo.update(urev, allow=False, force=True, wlock=wlock)
       
   504             repo.dirstate.write()
       
   505 
       
   506         # save is a list of all the branches we are truncating away
       
   507         # that we actually want to keep.  changegroup will be used
       
   508         # to preserve them and add them back after the truncate
       
   509         saveheads = []
       
   510         savebases = {}
       
   511 
       
   512         tip = chlog.tip()
       
   513         heads = limitheads(chlog, rev)
       
   514         seen = {}
       
   515 
       
   516         # search through all the heads, finding those where the revision
       
   517         # we want to strip away is an ancestor.  Also look for merges
       
   518         # that might be turned into new heads by the strip.
       
   519         while heads:
       
   520             h = heads.pop()
       
   521             n = h
       
   522             while True:
       
   523                 seen[n] = 1
       
   524                 pp = chlog.parents(n)
       
   525                 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
       
   526                     if pp[1] not in seen:
       
   527                         heads.append(pp[1])
       
   528                 if pp[0] == revlog.nullid:
       
   529                     break
       
   530                 if chlog.rev(pp[0]) < revnum:
       
   531                     break
       
   532                 n = pp[0]
       
   533                 if n == rev:
       
   534                     break
       
   535             r = chlog.reachable(h, rev)
       
   536             if rev not in r:
       
   537                 saveheads.append(h)
       
   538                 for x in r:
       
   539                     if chlog.rev(x) > revnum:
       
   540                         savebases[x] = 1
       
   541                 
       
   542         # create a changegroup for all the branches we need to keep
       
   543         if backup is "all":
       
   544             backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
       
   545             bundle(backupch)
       
   546         if saveheads:
       
   547             backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
       
   548             chgrpfile = bundle(backupch)
       
   549 
       
   550         stripall(rev, revnum)
       
   551 
       
   552         change = chlog.read(rev)
       
   553         repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
       
   554         chlog.strip(revnum, revnum)
       
   555         if saveheads:
       
   556             self.ui.status("adding branch\n")
       
   557             commands.unbundle(self.ui, repo, chgrpfile, update=False)
       
   558             if backup is not "strip":
       
   559                 os.unlink(chgrpfile)
       
   560              
       
   561     def isapplied(self, patch):
       
   562         """returns (index, rev, patch)"""
       
   563         for i in xrange(len(self.applied)):
       
   564             p = self.applied[i]
       
   565             a = p.split(':')
       
   566             if a[1] == patch:
       
   567                 return (i, a[0], a[1])
       
   568         return None
       
   569 
       
   570     def lookup(self, patch):
       
   571         if patch == None:
       
   572             return None
       
   573         if patch in self.series:
       
   574             return patch
       
   575         if not os.path.isfile(os.path.join(self.path, patch)):
       
   576             try:
       
   577                 sno = int(patch)
       
   578             except(ValueError, OverflowError):
       
   579                 self.ui.warn("patch %s not in series\n" % patch)
       
   580                 sys.exit(1)
       
   581             if sno >= len(self.series):
       
   582                 self.ui.warn("patch number %d is out of range\n" % sno)
       
   583                 sys.exit(1)
       
   584             patch = self.series[sno]
       
   585         else:
       
   586             self.ui.warn("patch %s not in series\n" % patch)
       
   587             sys.exit(1)
       
   588         return patch
       
   589 
       
   590     def push(self, repo, patch=None, force=False, list=False, 
       
   591              mergeq=None, wlock=None):
       
   592         if not wlock:
       
   593             wlock = repo.wlock()
       
   594         patch = self.lookup(patch) 
       
   595         if patch and self.isapplied(patch):
       
   596             self.ui.warn("patch %s is already applied\n" % patch)
       
   597             sys.exit(1)
       
   598         if self.series_end() == len(self.series):
       
   599             self.ui.warn("File series fully applied\n")
       
   600             sys.exit(1)
       
   601         if not force:
       
   602             self.check_localchanges(repo)
       
   603             
       
   604         self.applied_dirty = 1;
       
   605         start = self.series_end()
       
   606         if start > 0:
       
   607             self.check_toppatch(repo)
       
   608         if not patch:
       
   609             patch = self.series[start]
       
   610             end = start + 1
       
   611         else:
       
   612             end = self.series.index(patch, start) + 1
       
   613         s = self.series[start:end]
       
   614         if mergeq:
       
   615             ret = self.mergepatch(repo, mergeq, s, wlock)
       
   616         else:
       
   617             ret = self.apply(repo, s, list, wlock=wlock)
       
   618         top = self.applied[-1].split(':')[1]
       
   619         if ret[0]:
       
   620             self.ui.write("Errors during apply, please fix and refresh %s\n" % 
       
   621                           top)
       
   622         else:
       
   623             self.ui.write("Now at: %s\n" % top)
       
   624         return ret[0]
       
   625 
       
   626     def pop(self, repo, patch=None, force=False, update=True, wlock=None):
       
   627         def getfile(f, rev):
       
   628             t = repo.file(f).read(rev)
       
   629             try:
       
   630                 repo.wfile(f, "w").write(t)
       
   631             except IOError:
       
   632                 os.makedirs(os.path.dirname(repo.wjoin(f)))
       
   633                 repo.wfile(f, "w").write(t)
       
   634 
       
   635         if not wlock:
       
   636             wlock = repo.wlock()
       
   637         if patch:
       
   638             # index, rev, patch
       
   639             info = self.isapplied(patch)
       
   640             if not info:
       
   641                 patch = self.lookup(patch)
       
   642             info = self.isapplied(patch)
       
   643             if not info:
       
   644                 self.ui.warn("patch %s is not applied\n" % patch)
       
   645                 sys.exit(1)
       
   646         if len(self.applied) == 0:
       
   647             self.ui.warn("No patches applied\n")
       
   648             sys.exit(1)
       
   649 
       
   650         if not update:
       
   651             parents = repo.dirstate.parents()
       
   652             rr = [ revlog.bin(x.split(':')[0]) for x in self.applied ]
       
   653             for p in parents:
       
   654                 if p in rr:
       
   655                     self.ui.warn("qpop: forcing dirstate update\n")
       
   656                     update = True
       
   657 
       
   658         if not force and update:
       
   659             self.check_localchanges(repo)
       
   660 
       
   661         self.applied_dirty = 1;
       
   662         end = len(self.applied)
       
   663         if not patch:
       
   664             info = [len(self.applied) - 1] + self.applied[-1].split(':')
       
   665         start = info[0]
       
   666         rev = revlog.bin(info[1])
       
   667 
       
   668         # we know there are no local changes, so we can make a simplified
       
   669         # form of hg.update.
       
   670         if update:
       
   671             top = self.check_toppatch(repo)
       
   672             qp = self.qparents(repo, rev)
       
   673             changes = repo.changelog.read(qp)
       
   674             mf1 = repo.manifest.readflags(changes[0])
       
   675             mmap = repo.manifest.read(changes[0])
       
   676             (c, a, r, d, u) = repo.changes(qp, top)
       
   677             if d:
       
   678                 raise util.Abort("deletions found between repo revs")
       
   679             for f in c:
       
   680                 getfile(f, mmap[f])
       
   681             for f in r:
       
   682                 getfile(f, mmap[f])
       
   683                 util.set_exec(repo.wjoin(f), mf1[f])
       
   684             repo.dirstate.update(c + r, 'n')
       
   685             for f in a:
       
   686                 try: os.unlink(repo.wjoin(f))
       
   687                 except: raise
       
   688                 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
       
   689                 except: pass
       
   690             if a:
       
   691                 repo.dirstate.forget(a)
       
   692             repo.dirstate.setparents(qp, revlog.nullid)
       
   693         self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
       
   694         del self.applied[start:end]
       
   695         if len(self.applied):
       
   696             self.ui.write("Now at: %s\n" % self.applied[-1].split(':')[1])
       
   697         else:
       
   698             self.ui.write("Patch queue now empty\n")
       
   699 
       
   700     def diff(self, repo, files):
       
   701         top = self.check_toppatch(repo)
       
   702         if not top:
       
   703             self.ui.write("No patches applied\n")
       
   704             return
       
   705         qp = self.qparents(repo, top)
       
   706         commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
       
   707 
       
   708     def refresh(self, repo, short=False):
       
   709         if len(self.applied) == 0:
       
   710             self.ui.write("No patches applied\n")
       
   711             return
       
   712         wlock = repo.wlock()
       
   713         self.check_toppatch(repo)
       
   714         qp = self.qparents(repo)
       
   715         (top, patch) = self.applied[-1].split(':')
       
   716         top = revlog.bin(top)
       
   717         cparents = repo.changelog.parents(top)
       
   718         patchparent = self.qparents(repo, top)
       
   719         message, comments, user, patchfound = self.readheaders(patch)
       
   720 
       
   721         patchf = self.opener(os.path.join(self.path, patch), "w")
       
   722         if comments:
       
   723             comments = "\n".join(comments) + '\n\n'
       
   724             patchf.write(comments)
       
   725 
       
   726         tip = repo.changelog.tip()
       
   727         if top == tip:
       
   728             # if the top of our patch queue is also the tip, there is an 
       
   729             # optimization here.  We update the dirstate in place and strip
       
   730             # off the tip commit.  Then just commit the current directory
       
   731             # tree.  We can also send repo.commit the list of files
       
   732             # changed to speed up the diff
       
   733             #
       
   734             # in short mode, we only diff the files included in the 
       
   735             # patch already
       
   736             #
       
   737             # this should really read:
       
   738             #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
       
   739             # but we do it backwards to take advantage of manifest/chlog
       
   740             # caching against the next repo.changes call
       
   741             # 
       
   742             (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
       
   743             if short:
       
   744                 filelist = cc + aa + dd
       
   745             else:
       
   746                 filelist = None
       
   747             (c, a, r, d, u) = repo.changes(None, None, filelist)
       
   748 
       
   749             # we might end up with files that were added between tip and
       
   750             # the dirstate parent, but then changed in the local dirstate.
       
   751             # in this case, we want them to only show up in the added section
       
   752             for x in c:
       
   753                 if x not in aa:
       
   754                     cc.append(x)
       
   755             # we might end up with files added by the local dirstate that
       
   756             # were deleted by the patch.  In this case, they should only
       
   757             # show up in the changed section.
       
   758             for x in a:
       
   759                 if x in dd:
       
   760                     del dd[dd.index(x)]
       
   761                     cc.append(x)
       
   762                 else:
       
   763                     aa.append(x)
       
   764             # make sure any files deleted in the local dirstate
       
   765             # are not in the add or change column of the patch
       
   766             forget = []
       
   767             for x in d + r:
       
   768                 if x in aa:
       
   769                     del aa[aa.index(x)]
       
   770                     forget.append(x)
       
   771                     continue
       
   772                 elif x in cc:
       
   773                     del cc[cc.index(x)]
       
   774                 dd.append(x)
       
   775 
       
   776             c = list(util.unique(cc))
       
   777             r = list(util.unique(dd))
       
   778             a = list(util.unique(aa))
       
   779             filelist = list(util.unique(c + r + a ))
       
   780             commands.dodiff(patchf, self.ui, repo, patchparent, None, 
       
   781                             filelist, changes=(c, a, r, [], u))
       
   782             patchf.close()
       
   783 
       
   784             changes = repo.changelog.read(tip)
       
   785             repo.dirstate.setparents(*cparents)
       
   786             repo.dirstate.update(a, 'a')
       
   787             repo.dirstate.update(r, 'r')
       
   788             repo.dirstate.update(c, 'n')   
       
   789             repo.dirstate.forget(forget)
       
   790 
       
   791             if not message:
       
   792                 message = "patch queue: %s\n" % patch
       
   793             else:
       
   794                 message = "\n".join(message)
       
   795             self.strip(repo, top, update=False, backup='strip', wlock=wlock)
       
   796             n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
       
   797             self.applied[-1] = revlog.hex(n) + ':' + patch
       
   798             self.applied_dirty = 1
       
   799         else:
       
   800             commands.dodiff(patchf, self.ui, repo, patchparent, None)
       
   801             patchf.close()
       
   802             self.pop(repo, force=True, wlock=wlock)
       
   803             self.push(repo, force=True, wlock=wlock)
       
   804 
       
   805     def init(self, repo, create=False):
       
   806         if os.path.isdir(self.path):
       
   807             raise util.Abort("patch queue directory already exists")
       
   808         os.mkdir(self.path)
       
   809         if create:
       
   810             return self.qrepo(create=True)
       
   811 
       
   812     def unapplied(self, repo, patch=None):
       
   813         if patch and patch not in self.series:
       
   814             self.ui.warn("%s not in the series file\n" % patch)
       
   815             sys.exit(1)
       
   816         if not patch:
       
   817             start = self.series_end()
       
   818         else:
       
   819             start = self.series.index(patch) + 1
       
   820         for p in self.series[start:]:
       
   821             self.ui.write("%s\n" % p)
       
   822         
       
   823     def qseries(self, repo, missing=None):
       
   824         start = self.series_end()
       
   825         if not missing:
       
   826             for p in self.series[:start]:
       
   827                 if self.ui.verbose:
       
   828                     self.ui.write("%d A " % self.series.index(p))
       
   829                 self.ui.write("%s\n" % p)
       
   830             for p in self.series[start:]:
       
   831                 if self.ui.verbose:
       
   832                     self.ui.write("%d U " % self.series.index(p))
       
   833                 self.ui.write("%s\n" %  p)
       
   834         else:
       
   835             list = []
       
   836             for root, dirs, files in os.walk(self.path):
       
   837                 d = root[len(self.path) + 1:]
       
   838                 for f in files:
       
   839                     fl = os.path.join(d, f)
       
   840                     if (fl not in self.series and fl != "status" and
       
   841                        fl != "series" and not fl.startswith('.')):
       
   842                         list.append(fl)
       
   843             list.sort()
       
   844             if list:
       
   845                 for x in list: 
       
   846                     if self.ui.verbose:
       
   847                         self.ui.write("D ")
       
   848                     self.ui.write("%s\n" % x)
       
   849 
       
   850     def issaveline(self, l):
       
   851         name = l.split(':')[1]
       
   852         if name == '.hg.patches.save.line':
       
   853             return True
       
   854 
       
   855     def qrepo(self, create=False):
       
   856         if create or os.path.isdir(os.path.join(self.path, ".hg")):
       
   857             return hg.repository(ui=self.ui, path=self.path, create=create)
       
   858 
       
   859     def restore(self, repo, rev, delete=None, qupdate=None):
       
   860         c = repo.changelog.read(rev)
       
   861         desc = c[4].strip()
       
   862         lines = desc.splitlines()
       
   863         i = 0
       
   864         datastart = None
       
   865         series = []
       
   866         applied = []
       
   867         qpp = None
       
   868         for i in xrange(0, len(lines)):
       
   869             if lines[i] == 'Patch Data:':
       
   870                 datastart = i + 1
       
   871             elif lines[i].startswith('Dirstate:'):
       
   872                 l = lines[i].rstrip()
       
   873                 l = l[10:].split(' ')
       
   874                 qpp = [ hg.bin(x) for x in l ]
       
   875             elif datastart != None:
       
   876                 l = lines[i].rstrip()
       
   877                 index = l.index(':')
       
   878                 id = l[:index]
       
   879                 file = l[index + 1:]
       
   880                 if id:
       
   881                     applied.append(l)
       
   882                 series.append(file)
       
   883         if datastart == None:
       
   884             self.ui.warn("No saved patch data found\n")
       
   885             return 1
       
   886         self.ui.warn("restoring status: %s\n" % lines[0])
       
   887         self.full_series = series
       
   888         self.applied = applied
       
   889         self.read_series(self.full_series)
       
   890         self.series_dirty = 1
       
   891         self.applied_dirty = 1
       
   892         heads = repo.changelog.heads()
       
   893         if delete:
       
   894             if rev not in heads:
       
   895                 self.ui.warn("save entry has children, leaving it alone\n")
       
   896             else:
       
   897                 self.ui.warn("removing save entry %s\n" % hg.short(rev))
       
   898                 pp = repo.dirstate.parents()
       
   899                 if rev in pp:
       
   900                     update = True
       
   901                 else:
       
   902                     update = False
       
   903                 self.strip(repo, rev, update=update, backup='strip')
       
   904         if qpp:
       
   905             self.ui.warn("saved queue repository parents: %s %s\n" % 
       
   906                          (hg.short(qpp[0]), hg.short(qpp[1])))
       
   907             if qupdate:
       
   908                 print "queue directory updating"
       
   909                 r = self.qrepo()
       
   910                 if not r:
       
   911                     self.ui.warn("Unable to load queue repository\n")
       
   912                     return 1
       
   913                 r.update(qpp[0], allow=False, force=True)
       
   914 
       
   915     def save(self, repo, msg=None):
       
   916         if len(self.applied) == 0:
       
   917             self.ui.warn("save: no patches applied, exiting\n")
       
   918             return 1
       
   919         if self.issaveline(self.applied[-1]):
       
   920             self.ui.warn("status is already saved\n")
       
   921             return 1
       
   922         
       
   923         ar = [ ':' + x for x in self.full_series ]
       
   924         if not msg:
       
   925             msg = "hg patches saved state"
       
   926         else:
       
   927             msg = "hg patches: " + msg.rstrip('\r\n')
       
   928         r = self.qrepo()
       
   929         if r:
       
   930             pp = r.dirstate.parents()
       
   931             msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
       
   932         msg += "\n\nPatch Data:\n"
       
   933         text = msg + "\n".join(self.applied) + '\n' + (ar and "\n".join(ar) 
       
   934                                                     + '\n' or "")
       
   935         n = repo.commit(None, text, user=None, force=1)
       
   936         if not n:
       
   937             self.ui.warn("repo commit failed\n")
       
   938             return 1
       
   939         self.applied.append(revlog.hex(n) + ":" + '.hg.patches.save.line')
       
   940         self.applied_dirty = 1
       
   941 
       
   942     def series_end(self):
       
   943         end = 0
       
   944         if len(self.applied) > 0:
       
   945             (top, p) = self.applied[-1].split(':')
       
   946             try:
       
   947                 end = self.series.index(p)
       
   948             except ValueError:
       
   949                 return 0
       
   950             return end + 1
       
   951         return end
       
   952 
       
   953     def qapplied(self, repo, patch=None):
       
   954         if patch and patch not in self.series:
       
   955             self.ui.warn("%s not in the series file\n" % patch)
       
   956             sys.exit(1)
       
   957         if not patch:
       
   958             end = len(self.applied)
       
   959         else:
       
   960             end = self.series.index(patch) + 1
       
   961         for x in xrange(end):
       
   962             p = self.appliedname(x)
       
   963             self.ui.write("%s\n" % p)
       
   964 
       
   965     def appliedname(self, index):
       
   966         p = self.applied[index]
       
   967         if not self.ui.verbose:
       
   968             p = p.split(':')[1]
       
   969         return p
       
   970         
       
   971     def top(self, repo):
       
   972         if len(self.applied):
       
   973             p = self.appliedname(-1)
       
   974             self.ui.write(p + '\n')
       
   975         else:
       
   976             self.ui.write("No patches applied\n")
       
   977 
       
   978     def next(self, repo):
       
   979         end = self.series_end()
       
   980         if end == len(self.series):
       
   981             self.ui.write("All patches applied\n")
       
   982         else:
       
   983             self.ui.write(self.series[end] + '\n')
       
   984 
       
   985     def prev(self, repo):
       
   986         if len(self.applied) > 1:
       
   987             p = self.appliedname(-2)
       
   988             self.ui.write(p + '\n')
       
   989         elif len(self.applied) == 1:
       
   990             self.ui.write("Only one patch applied\n")
       
   991         else:
       
   992             self.ui.write("No patches applied\n")
       
   993 
       
   994     def qimport(self, repo, files, patch=None, existing=None, force=None):
       
   995         if len(files) > 1 and patch:
       
   996             self.ui.warn("-n option not valid when importing multiple files\n")
       
   997             sys.exit(1)
       
   998         i = 0
       
   999         for filename in files:
       
  1000             if existing:
       
  1001                 if not patch: 
       
  1002                     patch = filename
       
  1003                 if not os.path.isfile(os.path.join(self.path, patch)):
       
  1004                     self.ui.warn("patch %s does not exist\n" % patch)
       
  1005                     sys.exit(1)
       
  1006             else:
       
  1007                 try:
       
  1008                     text = file(filename).read()
       
  1009                 except IOError:
       
  1010                     self.ui.warn("Unable to read %s\n" % patch)
       
  1011                     sys.exit(1)
       
  1012                 if not patch:
       
  1013                     patch = os.path.split(filename)[1]
       
  1014                 if not force and os.path.isfile(os.path.join(self.path, patch)):
       
  1015                     self.ui.warn("patch %s already exists\n" % patch)
       
  1016                     sys.exit(1)
       
  1017                 patchf = self.opener(os.path.join(self.path, patch), "w")
       
  1018                 patchf.write(text)
       
  1019             if patch in self.series:
       
  1020                 self.ui.warn("patch %s is already in the series file\n" % patch)
       
  1021                 sys.exit(1)
       
  1022             index = self.series_end() + i
       
  1023             self.full_series[index:index] = [patch]
       
  1024             self.read_series(self.full_series)
       
  1025             self.ui.warn("adding %s to series file\n" % patch)
       
  1026             i += 1
       
  1027             patch = None
       
  1028         self.series_dirty = 1
       
  1029 
       
  1030 def delete(ui, repo, patch, **opts):
       
  1031     """remove a patch from the series file"""
       
  1032     q = repomap[repo]
       
  1033     q.delete(repo, patch)
       
  1034     q.save_dirty()
       
  1035     return 0
       
  1036 
       
  1037 def applied(ui, repo, patch=None, **opts):
       
  1038     """print the patches already applied"""
       
  1039     repomap[repo].qapplied(repo, patch)
       
  1040     return 0
       
  1041 
       
  1042 def unapplied(ui, repo, patch=None, **opts):
       
  1043     """print the patches not yet applied"""
       
  1044     repomap[repo].unapplied(repo, patch)
       
  1045     return 0
       
  1046 
       
  1047 def qimport(ui, repo, *filename, **opts):
       
  1048     """import a patch"""
       
  1049     q = repomap[repo]
       
  1050     q.qimport(repo, filename, patch=opts['name'], 
       
  1051                           existing=opts['existing'], force=opts['force'])
       
  1052     q.save_dirty()
       
  1053     return 0
       
  1054 
       
  1055 def init(ui, repo, **opts):
       
  1056     """init a new queue repository"""
       
  1057     q = repomap[repo]
       
  1058     r = q.init(repo, create=opts['create_repo'])
       
  1059     q.save_dirty()
       
  1060     if r:
       
  1061         fp = r.wopener('.hgignore', 'w')
       
  1062         print >> fp, 'syntax: glob'
       
  1063         print >> fp, 'status'
       
  1064         fp.close()
       
  1065         r.wopener('series', 'w').close()
       
  1066         r.add(['.hgignore', 'series'])
       
  1067     return 0
       
  1068 
       
  1069 def commit(ui, repo, *pats, **opts):
       
  1070     q = repomap[repo]
       
  1071     r = q.qrepo()
       
  1072     if not r: raise util.Abort('no queue repository')
       
  1073     commands.commit(r.ui, r, *pats, **opts)
       
  1074 
       
  1075 def series(ui, repo, **opts):
       
  1076     """print the entire series file"""
       
  1077     repomap[repo].qseries(repo, missing=opts['missing'])
       
  1078     return 0
       
  1079 
       
  1080 def top(ui, repo, **opts):
       
  1081     """print the name of the current patch"""
       
  1082     repomap[repo].top(repo)
       
  1083     return 0
       
  1084 
       
  1085 def next(ui, repo, **opts):
       
  1086     """print the name of the next patch"""
       
  1087     repomap[repo].next(repo)
       
  1088     return 0
       
  1089 
       
  1090 def prev(ui, repo, **opts):
       
  1091     """print the name of the previous patch"""
       
  1092     repomap[repo].prev(repo)
       
  1093     return 0
       
  1094 
       
  1095 def new(ui, repo, patch, **opts):
       
  1096     """create a new patch"""
       
  1097     q = repomap[repo]
       
  1098     q.new(repo, patch, msg=opts['message'], force=opts['force'])
       
  1099     q.save_dirty()
       
  1100     return 0
       
  1101 
       
  1102 def refresh(ui, repo, **opts):
       
  1103     """update the current patch"""
       
  1104     q = repomap[repo]
       
  1105     q.refresh(repo, short=opts['short'])
       
  1106     q.save_dirty()
       
  1107     return 0
       
  1108 
       
  1109 def diff(ui, repo, *files, **opts):
       
  1110     """diff of the current patch"""
       
  1111     repomap[repo].diff(repo, files)
       
  1112     return 0
       
  1113 
       
  1114 def lastsavename(path):
       
  1115     (dir, base) = os.path.split(path)
       
  1116     names = os.listdir(dir)
       
  1117     namere = re.compile("%s.([0-9]+)" % base)
       
  1118     max = None
       
  1119     maxname = None
       
  1120     for f in names:
       
  1121         m = namere.match(f)
       
  1122         if m:
       
  1123             index = int(m.group(1))
       
  1124             if max == None or index > max:
       
  1125                 max = index
       
  1126                 maxname = f
       
  1127     if maxname:
       
  1128         return (os.path.join(dir, maxname), max)
       
  1129     return (None, None)
       
  1130     
       
  1131 def savename(path):
       
  1132     (last, index) = lastsavename(path)
       
  1133     if last is None:
       
  1134         index = 0
       
  1135     newpath = path + ".%d" % (index + 1)
       
  1136     return newpath
       
  1137 
       
  1138 def push(ui, repo, patch=None, **opts):
       
  1139     """push the next patch onto the stack"""
       
  1140     q = repomap[repo]
       
  1141     mergeq = None
       
  1142         
       
  1143     if opts['all']:
       
  1144         patch = q.series[-1]
       
  1145     if opts['merge']:
       
  1146         if opts['name']:
       
  1147             newpath = opts['name']
       
  1148         else:
       
  1149             newpath,i = lastsavename(q.path)
       
  1150         if not newpath:
       
  1151             ui.warn("no saved queues found, please use -n\n")
       
  1152             return 1
       
  1153         mergeq = queue(ui, repo.join(""), newpath)
       
  1154         ui.warn("merging with queue at: %s\n" % mergeq.path)
       
  1155     ret = q.push(repo, patch, force=opts['force'], list=opts['list'], 
       
  1156                  mergeq=mergeq)
       
  1157     q.save_dirty()
       
  1158     return ret
       
  1159 
       
  1160 def pop(ui, repo, patch=None, **opts):
       
  1161     """pop the current patch off the stack"""
       
  1162     localupdate = True
       
  1163     if opts['name']:
       
  1164         q = queue(ui, repo.join(""), repo.join(opts['name']))
       
  1165         ui.warn('using patch queue: %s\n' % q.path)
       
  1166         localupdate = False
       
  1167     else:
       
  1168         q = repomap[repo]
       
  1169     if opts['all'] and len(q.applied) > 0:
       
  1170         patch = q.applied[0].split(':')[1]
       
  1171     q.pop(repo, patch, force=opts['force'], update=localupdate)
       
  1172     q.save_dirty()
       
  1173     return 0
       
  1174 
       
  1175 def restore(ui, repo, rev, **opts):
       
  1176     """restore the queue state saved by a rev"""
       
  1177     rev = repo.lookup(rev)
       
  1178     q = repomap[repo]
       
  1179     q.restore(repo, rev, delete=opts['delete'],
       
  1180                           qupdate=opts['update'])
       
  1181     q.save_dirty()
       
  1182     return 0
       
  1183 
       
  1184 def save(ui, repo, **opts):
       
  1185     """save current queue state"""
       
  1186     q = repomap[repo]
       
  1187     ret = q.save(repo, msg=opts['message'])
       
  1188     if ret:
       
  1189         return ret
       
  1190     q.save_dirty()
       
  1191     if opts['copy']:
       
  1192         path = q.path
       
  1193         if opts['name']:
       
  1194             newpath = os.path.join(q.basepath, opts['name'])
       
  1195             if os.path.exists(newpath):
       
  1196                 if not os.path.isdir(newpath):
       
  1197                     ui.warn("destination %s exists and is not a directory\n" %
       
  1198                              newpath)
       
  1199                     sys.exit(1)
       
  1200                 if not opts['force']:
       
  1201                     ui.warn("destination %s exists, use -f to force\n" %
       
  1202                              newpath)
       
  1203                     sys.exit(1)
       
  1204         else:
       
  1205             newpath = savename(path)
       
  1206         ui.warn("copy %s to %s\n" % (path, newpath))
       
  1207         util.copyfiles(path, newpath)
       
  1208     if opts['empty']:
       
  1209         try:
       
  1210             os.unlink(q.status_path)
       
  1211         except:
       
  1212             pass
       
  1213     return 0
       
  1214     
       
  1215 def strip(ui, repo, rev, **opts):
       
  1216     """strip a revision and all later revs on the same branch"""
       
  1217     rev = repo.lookup(rev)
       
  1218     backup = 'all'
       
  1219     if opts['backup']:
       
  1220         backup = 'strip'
       
  1221     elif opts['nobackup']:
       
  1222         backup = 'none'
       
  1223     repomap[repo].strip(repo, rev, backup=backup)
       
  1224     return 0
       
  1225 
       
  1226 def version(ui, q=None):
       
  1227     """print the version number"""
       
  1228     ui.write("mq version %s\n" % versionstr)
       
  1229     return 0
       
  1230 
       
  1231 def reposetup(ui, repo):
       
  1232     repomap[repo] = queue(ui, repo.join(""))
       
  1233 
       
  1234 cmdtable = {
       
  1235     "qapplied": (applied, [], "hg qapplied [patch]"),
       
  1236     "qcommit|qci": (commit,
       
  1237                     [('A', 'addremove', None, _('run addremove during commit')),
       
  1238                      ('I', 'include', [], _('include names matching the given patterns')),
       
  1239                      ('X', 'exclude', [], _('exclude names matching the given patterns')),
       
  1240                      ('m', 'message', "", _('use <text> as commit message')),
       
  1241                      ('l', 'logfile', "", _('read the commit message from <file>')),
       
  1242                      ('d', 'date', "", _('record datecode as commit date')),
       
  1243                      ('u', 'user', "", _('record user as commiter'))],
       
  1244                     "hg qcommit [options] [files]"),
       
  1245     "^qdiff": (diff, [], "hg qdiff [files]"),
       
  1246     "qdelete": (delete, [], "hg qdelete [patch]"),
       
  1247     "^qimport": (qimport, [('e', 'existing', None, 'import file in patch dir'),
       
  1248                            ('n', 'name', "", 'patch file name'),
       
  1249                            ('f', 'force', None, 'overwrite existing files')],
       
  1250                  "hg qimport"),
       
  1251     "^qinit": (init, [('c', 'create-repo', None, 'create patch repository')],
       
  1252                "hg [-c] qinit"),
       
  1253     "qnew": (new, [('m', 'message', "", 'commit message'),
       
  1254                    ('f', 'force', None, 'force')], 
       
  1255                    "hg qnew [-m message ] patch"),
       
  1256     "qnext": (next, [], "hg qnext"),
       
  1257     "qprev": (prev, [], "hg qprev"),
       
  1258     "^qpop": (pop, [('a', 'all', None, 'pop all patches'),
       
  1259                      ('n', 'name', "", 'queue name to pop'),
       
  1260                    ('f', 'force', None, 'forget any local changes')], 
       
  1261             'hg qpop [options] [patch/index]'),
       
  1262     "^qpush": (push, [('f', 'force', None, 'apply if the patch has rejects'),
       
  1263                      ('l', 'list', None, 'list patch name in commit text'),
       
  1264                      ('a', 'all', None, 'apply all patches'),
       
  1265                      ('m', 'merge', None, 'merge from another queue'),
       
  1266                      ('n', 'name', "", 'merge queue name')],
       
  1267              'hg qpush [options] [patch/index]'),
       
  1268     "^qrefresh": (refresh, [('s', 'short', None, 'short refresh')],"hg qrefresh"),
       
  1269     "qrestore": (restore, [('d', 'delete', None, 'delete save entry'),
       
  1270                            ('u', 'update', None, 'update queue working dir')], 
       
  1271                  'hg qrestore rev'),
       
  1272     "qsave": (save, [('m', 'message', "", 'commit message'),
       
  1273                      ('c', 'copy', None, 'copy patch directory'),
       
  1274                      ('n', 'name', "", 'copy directory name'),
       
  1275                      ('e', 'empty', None, 'clear queue status file'),
       
  1276                      ('f', 'force', None, 'force copy')], 'hg qsave'),
       
  1277     "qseries": (series, [('m', 'missing', None, 'print patches not in series')],
       
  1278                 "hg qseries"),
       
  1279     "^strip": (strip, [('f', 'force', None, 'force multi-head removal'),
       
  1280                        ('b', 'backup', None, 'bundle unrelated changesets'),
       
  1281                        ('n', 'nobackup', None, 'no backups')], "hg strip rev"),
       
  1282     "qtop": (top, [], "hg qtop"),
       
  1283     "qunapplied": (unapplied, [], "hg qunapplied [patch]"),
       
  1284     "qversion": (version, [], "hg qversion")
       
  1285 }
       
  1286