hgext/mq.py
changeset 2810 2e4ace008c94
parent 2808 766ecdc83e43
child 2817 12139eedd6a0
equal deleted inserted replaced
2809:1bb8dd08c594 2810:2e4ace008c94
    63         self.full_series = []
    63         self.full_series = []
    64         self.applied_dirty = 0
    64         self.applied_dirty = 0
    65         self.series_dirty = 0
    65         self.series_dirty = 0
    66         self.series_path = "series"
    66         self.series_path = "series"
    67         self.status_path = "status"
    67         self.status_path = "status"
       
    68         self.guards_path = "guards"
       
    69         self.active_guards = None
       
    70         self.guards_dirty = False
    68 
    71 
    69         if os.path.exists(self.join(self.series_path)):
    72         if os.path.exists(self.join(self.series_path)):
    70             self.full_series = self.opener(self.series_path).read().splitlines()
    73             self.full_series = self.opener(self.series_path).read().splitlines()
    71         self.parse_series()
    74         self.parse_series()
    72 
    75 
    73         if os.path.exists(self.join(self.status_path)):
    76         if os.path.exists(self.join(self.status_path)):
    74             self.applied = [statusentry(l)
    77             lines = self.opener(self.status_path).read().splitlines()
    75                             for l in self.opener(self.status_path).read().splitlines()]
    78             self.applied = [statusentry(l) for l in lines]
    76 
    79 
    77     def join(self, *p):
    80     def join(self, *p):
    78         return os.path.join(self.path, *p)
    81         return os.path.join(self.path, *p)
    79 
    82 
    80     def find_series(self, patch):
    83     def find_series(self, patch):
    88                 if s == patch:
    91                 if s == patch:
    89                     return index
    92                     return index
    90             index += 1
    93             index += 1
    91         return None
    94         return None
    92 
    95 
       
    96     guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
       
    97 
    93     def parse_series(self):
    98     def parse_series(self):
    94         self.series = []
    99         self.series = []
       
   100         self.series_guards = []
    95         for l in self.full_series:
   101         for l in self.full_series:
    96             s = l.split('#', 1)[0].strip()
   102             h = l.find('#')
    97             if s:
   103             if h == -1:
    98                 self.series.append(s)
   104                 patch = l
       
   105                 comment = ''
       
   106             elif h == 0:
       
   107                 continue
       
   108             else:
       
   109                 patch = l[:h]
       
   110                 comment = l[h:]
       
   111             patch = patch.strip()
       
   112             if patch:
       
   113                 self.series.append(patch)
       
   114                 self.series_guards.append(self.guard_re.findall(comment))
       
   115 
       
   116     def check_guard(self, guard):
       
   117         bad_chars = '# \t\r\n\f'
       
   118         first = guard[0]
       
   119         for c in '-+':
       
   120             if first == c:
       
   121                 return (_('guard %r starts with invalid character: %r') %
       
   122                         (guard, c))
       
   123         for c in bad_chars:
       
   124             if c in guard:
       
   125                 return _('invalid character in guard %r: %r') % (guard, c)
       
   126         
       
   127     def set_active(self, guards):
       
   128         for guard in guards:
       
   129             bad = self.check_guard(guard)
       
   130             if bad:
       
   131                 raise util.Abort(bad)
       
   132         guards = dict.fromkeys(guards).keys()
       
   133         guards.sort()
       
   134         self.ui.debug('active guards: %s\n' % ' '.join(guards))
       
   135         self.active_guards = guards
       
   136         self.guards_dirty = True
       
   137 
       
   138     def active(self):
       
   139         if self.active_guards is None:
       
   140             self.active_guards = []
       
   141             try:
       
   142                 guards = self.opener(self.guards_path).read().split()
       
   143             except IOError, err:
       
   144                 if err.errno != errno.ENOENT: raise
       
   145                 guards = []
       
   146             for i, guard in enumerate(guards):
       
   147                 bad = self.check_guard(guard)
       
   148                 if bad:
       
   149                     self.ui.warn('%s:%d: %s\n' %
       
   150                                  (self.join(self.guards_path), i + 1, bad))
       
   151                 else:
       
   152                     self.active_guards.append(guard)
       
   153         return self.active_guards
       
   154 
       
   155     def set_guards(self, idx, guards):
       
   156         for g in guards:
       
   157             if len(g) < 2:
       
   158                 raise util.Abort(_('guard %r too short') % g)
       
   159             if g[0] not in '-+':
       
   160                 raise util.Abort(_('guard %r starts with invalid char') % g)
       
   161             bad = self.check_guard(g[1:])
       
   162             if bad:
       
   163                 raise util.Abort(bad)
       
   164         drop = self.guard_re.sub('', self.full_series[idx])
       
   165         self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
       
   166         self.parse_series()
       
   167         self.series_dirty = True
       
   168         
       
   169     def pushable(self, idx):
       
   170         if isinstance(idx, str):
       
   171             idx = self.series.index(idx)
       
   172         patchguards = self.series_guards[idx]
       
   173         if not patchguards:
       
   174             return True, None
       
   175         default = False
       
   176         guards = self.active()
       
   177         exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
       
   178         if exactneg:
       
   179             return False, exactneg[0]
       
   180         pos = [g for g in patchguards if g[0] == '+']
       
   181         exactpos = [g for g in pos if g[1:] in guards]
       
   182         if pos:
       
   183             if exactpos:
       
   184                 return True, exactpos[0]
       
   185             return False, ''
       
   186         return True, ''
       
   187 
       
   188     def explain_pushable(self, idx, all_patches=False):
       
   189         write = all_patches and self.ui.write or self.ui.warn
       
   190         if all_patches or self.ui.verbose:
       
   191             if isinstance(idx, str):
       
   192                 idx = self.series.index(idx)
       
   193             pushable, why = self.pushable(idx)
       
   194             if all_patches and pushable:
       
   195                 if why is None:
       
   196                     write(_('allowing %s - no guards in effect\n') %
       
   197                           self.series[idx])
       
   198                 else:
       
   199                     if not why:
       
   200                         write(_('allowing %s - no matching negative guards\n') %
       
   201                               self.series[idx])
       
   202                     else:
       
   203                         write(_('allowing %s - guarded by %r\n') %
       
   204                               (self.series[idx], why))
       
   205             if not pushable:
       
   206                 if why and why[0] in '-+':
       
   207                     write(_('skipping %s - guarded by %r\n') %
       
   208                           (self.series[idx], why))
       
   209                 else:
       
   210                     write(_('skipping %s - no matching guards\n') %
       
   211                           self.series[idx])
    99 
   212 
   100     def save_dirty(self):
   213     def save_dirty(self):
   101         def write_list(items, path):
   214         def write_list(items, path):
   102             fp = self.opener(path, 'w')
   215             fp = self.opener(path, 'w')
   103             for i in items:
   216             for i in items:
   104                 print >> fp, i
   217                 print >> fp, i
   105             fp.close()
   218             fp.close()
   106         if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
   219         if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
   107         if self.series_dirty: write_list(self.full_series, self.series_path)
   220         if self.series_dirty: write_list(self.full_series, self.series_path)
       
   221         if self.guards_dirty: write_list(self.active_guards, self.guards_path)
   108 
   222 
   109     def readheaders(self, patch):
   223     def readheaders(self, patch):
   110         def eatdiff(lines):
   224         def eatdiff(lines):
   111             while lines:
   225             while lines:
   112                 l = lines[-1]
   226                 l = lines[-1]
   255         for patch in series:
   369         for patch in series:
   256             patch = mergeq.lookup(patch, strict=True)
   370             patch = mergeq.lookup(patch, strict=True)
   257             if not patch:
   371             if not patch:
   258                 self.ui.warn("patch %s does not exist\n" % patch)
   372                 self.ui.warn("patch %s does not exist\n" % patch)
   259                 return (1, None)
   373                 return (1, None)
   260 
   374             pushable, reason = self.pushable(patch)
       
   375             if not pushable:
       
   376                 self.explain_pushable(patch, all_patches=True)
       
   377                 continue
   261             info = mergeq.isapplied(patch)
   378             info = mergeq.isapplied(patch)
   262             if not info:
   379             if not info:
   263                 self.ui.warn("patch %s is not applied\n" % patch)
   380                 self.ui.warn("patch %s is not applied\n" % patch)
   264                 return (1, None)
   381                 return (1, None)
   265             rev = revlog.bin(info[1])
   382             rev = revlog.bin(info[1])
   319             wlock = repo.wlock()
   436             wlock = repo.wlock()
   320         lock = repo.lock()
   437         lock = repo.lock()
   321         tr = repo.transaction()
   438         tr = repo.transaction()
   322         n = None
   439         n = None
   323         for patch in series:
   440         for patch in series:
       
   441             pushable, reason = self.pushable(patch)
       
   442             if not pushable:
       
   443                 self.explain_pushable(patch, all_patches=True)
       
   444                 continue
   324             self.ui.warn("applying %s\n" % patch)
   445             self.ui.warn("applying %s\n" % patch)
   325             pf = os.path.join(patchdir, patch)
   446             pf = os.path.join(patchdir, patch)
   326 
   447 
   327             try:
   448             try:
   328                 message, comments, user, date, patchfound = self.readheaders(patch)
   449                 message, comments, user, date, patchfound = self.readheaders(patch)
   637                 sno = int(patch)
   758                 sno = int(patch)
   638             except(ValueError, OverflowError):
   759             except(ValueError, OverflowError):
   639                 pass
   760                 pass
   640             else:
   761             else:
   641                 if sno < len(self.series):
   762                 if sno < len(self.series):
   642                     patch = self.series[sno]
   763                     return self.series[sno]
   643                     return patch
       
   644             if not strict:
   764             if not strict:
   645                 # return any partial match made above
   765                 # return any partial match made above
   646                 if res:
   766                 if res:
   647                     return res
   767                     return res
   648                 minus = patch.rsplit('-', 1)
   768                 minus = patch.rsplit('-', 1)
   924             raise util.Abort(_("patch %s is not in series file") % patch)
  1044             raise util.Abort(_("patch %s is not in series file") % patch)
   925         if not patch:
  1045         if not patch:
   926             start = self.series_end()
  1046             start = self.series_end()
   927         else:
  1047         else:
   928             start = self.series.index(patch) + 1
  1048             start = self.series.index(patch) + 1
   929         return [(i, self.series[i]) for i in xrange(start, len(self.series))]
  1049         unapplied = []
       
  1050         for i in xrange(start, len(self.series)):
       
  1051             pushable, reason = self.pushable(i)
       
  1052             if pushable:
       
  1053                 unapplied.append((i, self.series[i]))
       
  1054             self.explain_pushable(i)
       
  1055         return unapplied
   930 
  1056 
   931     def qseries(self, repo, missing=None, summary=False):
  1057     def qseries(self, repo, missing=None, summary=False):
   932         start = self.series_end()
  1058         start = self.series_end(all_patches=True)
   933         if not missing:
  1059         if not missing:
   934             for i in range(len(self.series)):
  1060             for i in range(len(self.series)):
   935                 patch = self.series[i]
  1061                 patch = self.series[i]
   936                 if self.ui.verbose:
  1062                 if self.ui.verbose:
   937                     if i < start:
  1063                     if i < start:
   938                         status = 'A'
  1064                         status = 'A'
       
  1065                     elif self.pushable(i)[0]:
       
  1066                         status = 'U'
   939                     else:
  1067                     else:
   940                         status = 'U'
  1068                         status = 'G'
   941                     self.ui.write('%d %s ' % (i, status))
  1069                     self.ui.write('%d %s ' % (i, status))
   942                 if summary:
  1070                 if summary:
   943                     msg = self.readheaders(patch)[0]
  1071                     msg = self.readheaders(patch)[0]
   944                     msg = msg and ': ' + msg[0] or ': '
  1072                     msg = msg and ': ' + msg[0] or ': '
   945                 else:
  1073                 else:
  1058             if end == None:
  1186             if end == None:
  1059                 return len(self.full_series)
  1187                 return len(self.full_series)
  1060             return end + 1
  1188             return end + 1
  1061         return 0
  1189         return 0
  1062 
  1190 
  1063     def series_end(self):
  1191     def series_end(self, all_patches=False):
  1064         end = 0
  1192         end = 0
       
  1193         def next(start):
       
  1194             if all_patches:
       
  1195                 return start
       
  1196             i = start
       
  1197             while i < len(self.series):
       
  1198                 p, reason = self.pushable(i)
       
  1199                 if p:
       
  1200                     break
       
  1201                 self.explain_pushable(i)
       
  1202                 i += 1
       
  1203             return i
  1065         if len(self.applied) > 0:
  1204         if len(self.applied) > 0:
  1066             p = self.applied[-1].name
  1205             p = self.applied[-1].name
  1067             try:
  1206             try:
  1068                 end = self.series.index(p)
  1207                 end = self.series.index(p)
  1069             except ValueError:
  1208             except ValueError:
  1070                 return 0
  1209                 return 0
  1071             return end + 1
  1210             return next(end + 1)
  1072         return end
  1211         return next(end)
  1073 
  1212 
  1074     def qapplied(self, repo, patch=None):
  1213     def qapplied(self, repo, patch=None):
  1075         if patch and patch not in self.series:
  1214         if patch and patch not in self.series:
  1076             raise util.Abort(_("patch %s is not in series file") % patch)
  1215             raise util.Abort(_("patch %s is not in series file") % patch)
  1077         if not patch:
  1216         if not patch:
  1370     for patch in patches:
  1509     for patch in patches:
  1371         q.delete(repo, patch, force=opts['force'])
  1510         q.delete(repo, patch, force=opts['force'])
  1372 
  1511 
  1373     q.save_dirty()
  1512     q.save_dirty()
  1374 
  1513 
       
  1514 def guard(ui, repo, *args, **opts):
       
  1515     '''set or print guards for a patch
       
  1516 
       
  1517     guards control whether a patch can be pushed.  a patch with no
       
  1518     guards is aways pushed.  a patch with posative guard ("+foo") is
       
  1519     pushed only if qselect command enables guard "foo".  a patch with
       
  1520     nagative guard ("-foo") is never pushed if qselect command enables
       
  1521     guard "foo".
       
  1522 
       
  1523     with no arguments, default is to print current active guards.
       
  1524     with arguments, set active guards for patch.
       
  1525 
       
  1526     to set nagative guard "-foo" on topmost patch ("--" is needed so
       
  1527     hg will not interpret "-foo" as argument):
       
  1528       hg qguard -- -foo
       
  1529 
       
  1530     to set guards on other patch:
       
  1531       hg qguard other.patch +2.6.17 -stable    
       
  1532     '''
       
  1533     def status(idx):
       
  1534         guards = q.series_guards[idx] or ['unguarded']
       
  1535         ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
       
  1536     q = repo.mq
       
  1537     patch = None
       
  1538     args = list(args)
       
  1539     if opts['list']:
       
  1540         if args or opts['none']:
       
  1541             raise util.Abort(_('cannot mix -l/--list with options or arguments'))
       
  1542         for i in xrange(len(q.series)):
       
  1543             status(i)
       
  1544         return
       
  1545     if not args or args[0][0:1] in '-+':
       
  1546         if not q.applied:
       
  1547             raise util.Abort(_('no patches applied'))
       
  1548         patch = q.applied[-1].name
       
  1549     if patch is None and args[0][0:1] not in '-+':
       
  1550         patch = args.pop(0)
       
  1551     if patch is None:
       
  1552         raise util.Abort(_('no patch to work with'))
       
  1553     if args or opts['none']:
       
  1554         q.set_guards(q.find_series(patch), args)
       
  1555         q.save_dirty()
       
  1556     else:
       
  1557         status(q.series.index(q.lookup(patch)))
       
  1558 
  1375 def header(ui, repo, patch=None):
  1559 def header(ui, repo, patch=None):
  1376     """Print the header of the topmost or specified patch"""
  1560     """Print the header of the topmost or specified patch"""
  1377     q = repo.mq
  1561     q = repo.mq
  1378 
  1562 
  1379     if patch:
  1563     if patch:
  1544     elif opts['nobackup']:
  1728     elif opts['nobackup']:
  1545         backup = 'none'
  1729         backup = 'none'
  1546     repo.mq.strip(repo, rev, backup=backup)
  1730     repo.mq.strip(repo, rev, backup=backup)
  1547     return 0
  1731     return 0
  1548 
  1732 
       
  1733 def select(ui, repo, *args, **opts):
       
  1734     '''set or print guarded patches to push
       
  1735 
       
  1736     use qguard command to set or print guards on patch.  then use
       
  1737     qselect to tell mq which guards to use.  example:
       
  1738 
       
  1739         qguard foo.patch -stable    (nagative guard)
       
  1740         qguard bar.patch +stable    (posative guard)
       
  1741         qselect stable
       
  1742 
       
  1743     this sets "stable" guard.  mq will skip foo.patch (because it has
       
  1744     nagative match) but push bar.patch (because it has posative
       
  1745     match).
       
  1746 
       
  1747     with no arguments, default is to print current active guards.
       
  1748     with arguments, set active guards as given.
       
  1749     
       
  1750     use -n/--none to deactivate guards (no other arguments needed).
       
  1751     when no guards active, patches with posative guards are skipped,
       
  1752     patches with nagative guards are pushed.
       
  1753 
       
  1754     use -s/--series to print list of all guards in series file (no
       
  1755     other arguments needed).  use -v for more information.'''
       
  1756 
       
  1757     q = repo.mq
       
  1758     guards = q.active()
       
  1759     if args or opts['none']:
       
  1760         q.set_active(args)
       
  1761         q.save_dirty()
       
  1762         if not args:
       
  1763             ui.status(_('guards deactivated\n'))
       
  1764         if q.series:
       
  1765             pushable = [p for p in q.unapplied(repo) if q.pushable(p[0])[0]]
       
  1766             ui.status(_('%d of %d unapplied patches active\n') %
       
  1767                       (len(pushable), len(q.series)))
       
  1768     elif opts['series']:
       
  1769         guards = {}
       
  1770         noguards = 0
       
  1771         for gs in q.series_guards:
       
  1772             if not gs:
       
  1773                 noguards += 1
       
  1774             for g in gs:
       
  1775                 guards.setdefault(g, 0)
       
  1776                 guards[g] += 1
       
  1777         if ui.verbose:
       
  1778             guards['NONE'] = noguards
       
  1779         guards = guards.items()
       
  1780         guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
       
  1781         if guards:
       
  1782             ui.note(_('guards in series file:\n'))
       
  1783             for guard, count in guards:
       
  1784                 ui.note('%2d  ' % count)
       
  1785                 ui.write(guard, '\n')
       
  1786         else:
       
  1787             ui.note(_('no guards in series file\n'))
       
  1788     else:
       
  1789         if guards:
       
  1790             ui.note(_('active guards:\n'))
       
  1791             for g in guards:
       
  1792                 ui.write(g, '\n')
       
  1793         else:
       
  1794             ui.write(_('no active guards\n'))
       
  1795 
  1549 def version(ui, q=None):
  1796 def version(ui, q=None):
  1550     """print the version number of the mq extension"""
  1797     """print the version number of the mq extension"""
  1551     ui.write("mq version %s\n" % versionstr)
  1798     ui.write("mq version %s\n" % versionstr)
  1552     return 0
  1799     return 0
  1553 
  1800 
  1603          [('e', 'edit', None, _('edit patch header')),
  1850          [('e', 'edit', None, _('edit patch header')),
  1604           ('f', 'force', None, _('delete folded patch files')),
  1851           ('f', 'force', None, _('delete folded patch files')),
  1605           ('m', 'message', '', _('set patch header to <text>')),
  1852           ('m', 'message', '', _('set patch header to <text>')),
  1606           ('l', 'logfile', '', _('set patch header to contents of <file>'))],
  1853           ('l', 'logfile', '', _('set patch header to contents of <file>'))],
  1607          'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
  1854          'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
       
  1855     'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
       
  1856                        ('n', 'none', None, _('drop all guards'))],
       
  1857                'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
  1608     'qheader': (header, [],
  1858     'qheader': (header, [],
  1609                 _('hg qheader [PATCH]')),
  1859                 _('hg qheader [PATCH]')),
  1610     "^qimport":
  1860     "^qimport":
  1611         (qimport,
  1861         (qimport,
  1612          [('e', 'existing', None, 'import file in patch dir'),
  1862          [('e', 'existing', None, 'import file in patch dir'),
  1660           ('c', 'copy', None, 'copy patch directory'),
  1910           ('c', 'copy', None, 'copy patch directory'),
  1661           ('n', 'name', '', 'copy directory name'),
  1911           ('n', 'name', '', 'copy directory name'),
  1662           ('e', 'empty', None, 'clear queue status file'),
  1912           ('e', 'empty', None, 'clear queue status file'),
  1663           ('f', 'force', None, 'force copy')],
  1913           ('f', 'force', None, 'force copy')],
  1664          'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
  1914          'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
       
  1915     "qselect": (select,
       
  1916                 [('n', 'none', None, _('disable all guards')),
       
  1917                  ('s', 'series', None, _('list all guards in series file'))],
       
  1918                 'hg qselect [GUARDS]'),
  1665     "qseries":
  1919     "qseries":
  1666         (series,
  1920         (series,
  1667          [('m', 'missing', None, 'print patches not in series'),
  1921          [('m', 'missing', None, 'print patches not in series'),
  1668           ('s', 'summary', None, _('print first line of patch header'))],
  1922           ('s', 'summary', None, _('print first line of patch header'))],
  1669          'hg qseries [-m]'),
  1923          'hg qseries [-m]'),