--- a/hgext/mq.py Tue Aug 08 18:14:03 2006 -0700
+++ b/hgext/mq.py Tue Aug 08 21:42:50 2006 -0700
@@ -65,14 +65,17 @@
self.series_dirty = 0
self.series_path = "series"
self.status_path = "status"
+ self.guards_path = "guards"
+ self.active_guards = None
+ self.guards_dirty = False
if os.path.exists(self.join(self.series_path)):
self.full_series = self.opener(self.series_path).read().splitlines()
self.parse_series()
if os.path.exists(self.join(self.status_path)):
- self.applied = [statusentry(l)
- for l in self.opener(self.status_path).read().splitlines()]
+ lines = self.opener(self.status_path).read().splitlines()
+ self.applied = [statusentry(l) for l in lines]
def join(self, *p):
return os.path.join(self.path, *p)
@@ -90,12 +93,122 @@
index += 1
return None
+ guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
+
def parse_series(self):
self.series = []
+ self.series_guards = []
for l in self.full_series:
- s = l.split('#', 1)[0].strip()
- if s:
- self.series.append(s)
+ h = l.find('#')
+ if h == -1:
+ patch = l
+ comment = ''
+ elif h == 0:
+ continue
+ else:
+ patch = l[:h]
+ comment = l[h:]
+ patch = patch.strip()
+ if patch:
+ self.series.append(patch)
+ self.series_guards.append(self.guard_re.findall(comment))
+
+ def check_guard(self, guard):
+ bad_chars = '# \t\r\n\f'
+ first = guard[0]
+ for c in '-+':
+ if first == c:
+ return (_('guard %r starts with invalid character: %r') %
+ (guard, c))
+ for c in bad_chars:
+ if c in guard:
+ return _('invalid character in guard %r: %r') % (guard, c)
+
+ def set_active(self, guards):
+ for guard in guards:
+ bad = self.check_guard(guard)
+ if bad:
+ raise util.Abort(bad)
+ guards = dict.fromkeys(guards).keys()
+ guards.sort()
+ self.ui.debug('active guards: %s\n' % ' '.join(guards))
+ self.active_guards = guards
+ self.guards_dirty = True
+
+ def active(self):
+ if self.active_guards is None:
+ self.active_guards = []
+ try:
+ guards = self.opener(self.guards_path).read().split()
+ except IOError, err:
+ if err.errno != errno.ENOENT: raise
+ guards = []
+ for i, guard in enumerate(guards):
+ bad = self.check_guard(guard)
+ if bad:
+ self.ui.warn('%s:%d: %s\n' %
+ (self.join(self.guards_path), i + 1, bad))
+ else:
+ self.active_guards.append(guard)
+ return self.active_guards
+
+ def set_guards(self, idx, guards):
+ for g in guards:
+ if len(g) < 2:
+ raise util.Abort(_('guard %r too short') % g)
+ if g[0] not in '-+':
+ raise util.Abort(_('guard %r starts with invalid char') % g)
+ bad = self.check_guard(g[1:])
+ if bad:
+ raise util.Abort(bad)
+ drop = self.guard_re.sub('', self.full_series[idx])
+ self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
+ self.parse_series()
+ self.series_dirty = True
+
+ def pushable(self, idx):
+ if isinstance(idx, str):
+ idx = self.series.index(idx)
+ patchguards = self.series_guards[idx]
+ if not patchguards:
+ return True, None
+ default = False
+ guards = self.active()
+ exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
+ if exactneg:
+ return False, exactneg[0]
+ pos = [g for g in patchguards if g[0] == '+']
+ exactpos = [g for g in pos if g[1:] in guards]
+ if pos:
+ if exactpos:
+ return True, exactpos[0]
+ return False, ''
+ return True, ''
+
+ def explain_pushable(self, idx, all_patches=False):
+ write = all_patches and self.ui.write or self.ui.warn
+ if all_patches or self.ui.verbose:
+ if isinstance(idx, str):
+ idx = self.series.index(idx)
+ pushable, why = self.pushable(idx)
+ if all_patches and pushable:
+ if why is None:
+ write(_('allowing %s - no guards in effect\n') %
+ self.series[idx])
+ else:
+ if not why:
+ write(_('allowing %s - no matching negative guards\n') %
+ self.series[idx])
+ else:
+ write(_('allowing %s - guarded by %r\n') %
+ (self.series[idx], why))
+ if not pushable:
+ if why and why[0] in '-+':
+ write(_('skipping %s - guarded by %r\n') %
+ (self.series[idx], why))
+ else:
+ write(_('skipping %s - no matching guards\n') %
+ self.series[idx])
def save_dirty(self):
def write_list(items, path):
@@ -105,6 +218,7 @@
fp.close()
if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
if self.series_dirty: write_list(self.full_series, self.series_path)
+ if self.guards_dirty: write_list(self.active_guards, self.guards_path)
def readheaders(self, patch):
def eatdiff(lines):
@@ -257,7 +371,10 @@
if not patch:
self.ui.warn("patch %s does not exist\n" % patch)
return (1, None)
-
+ pushable, reason = self.pushable(patch)
+ if not pushable:
+ self.explain_pushable(patch, all_patches=True)
+ continue
info = mergeq.isapplied(patch)
if not info:
self.ui.warn("patch %s is not applied\n" % patch)
@@ -321,6 +438,10 @@
tr = repo.transaction()
n = None
for patch in series:
+ pushable, reason = self.pushable(patch)
+ if not pushable:
+ self.explain_pushable(patch, all_patches=True)
+ continue
self.ui.warn("applying %s\n" % patch)
pf = os.path.join(patchdir, patch)
@@ -639,8 +760,7 @@
pass
else:
if sno < len(self.series):
- patch = self.series[sno]
- return patch
+ return self.series[sno]
if not strict:
# return any partial match made above
if res:
@@ -926,18 +1046,26 @@
start = self.series_end()
else:
start = self.series.index(patch) + 1
- return [(i, self.series[i]) for i in xrange(start, len(self.series))]
+ unapplied = []
+ for i in xrange(start, len(self.series)):
+ pushable, reason = self.pushable(i)
+ if pushable:
+ unapplied.append((i, self.series[i]))
+ self.explain_pushable(i)
+ return unapplied
def qseries(self, repo, missing=None, summary=False):
- start = self.series_end()
+ start = self.series_end(all_patches=True)
if not missing:
for i in range(len(self.series)):
patch = self.series[i]
if self.ui.verbose:
if i < start:
status = 'A'
+ elif self.pushable(i)[0]:
+ status = 'U'
else:
- status = 'U'
+ status = 'G'
self.ui.write('%d %s ' % (i, status))
if summary:
msg = self.readheaders(patch)[0]
@@ -1060,16 +1188,27 @@
return end + 1
return 0
- def series_end(self):
+ def series_end(self, all_patches=False):
end = 0
+ def next(start):
+ if all_patches:
+ return start
+ i = start
+ while i < len(self.series):
+ p, reason = self.pushable(i)
+ if p:
+ break
+ self.explain_pushable(i)
+ i += 1
+ return i
if len(self.applied) > 0:
p = self.applied[-1].name
try:
end = self.series.index(p)
except ValueError:
return 0
- return end + 1
- return end
+ return next(end + 1)
+ return next(end)
def qapplied(self, repo, patch=None):
if patch and patch not in self.series:
@@ -1372,6 +1511,51 @@
q.save_dirty()
+def guard(ui, repo, *args, **opts):
+ '''set or print guards for a patch
+
+ guards control whether a patch can be pushed. a patch with no
+ guards is aways pushed. a patch with posative guard ("+foo") is
+ pushed only if qselect command enables guard "foo". a patch with
+ nagative guard ("-foo") is never pushed if qselect command enables
+ guard "foo".
+
+ with no arguments, default is to print current active guards.
+ with arguments, set active guards for patch.
+
+ to set nagative guard "-foo" on topmost patch ("--" is needed so
+ hg will not interpret "-foo" as argument):
+ hg qguard -- -foo
+
+ to set guards on other patch:
+ hg qguard other.patch +2.6.17 -stable
+ '''
+ def status(idx):
+ guards = q.series_guards[idx] or ['unguarded']
+ ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
+ q = repo.mq
+ patch = None
+ args = list(args)
+ if opts['list']:
+ if args or opts['none']:
+ raise util.Abort(_('cannot mix -l/--list with options or arguments'))
+ for i in xrange(len(q.series)):
+ status(i)
+ return
+ if not args or args[0][0:1] in '-+':
+ if not q.applied:
+ raise util.Abort(_('no patches applied'))
+ patch = q.applied[-1].name
+ if patch is None and args[0][0:1] not in '-+':
+ patch = args.pop(0)
+ if patch is None:
+ raise util.Abort(_('no patch to work with'))
+ if args or opts['none']:
+ q.set_guards(q.find_series(patch), args)
+ q.save_dirty()
+ else:
+ status(q.series.index(q.lookup(patch)))
+
def header(ui, repo, patch=None):
"""Print the header of the topmost or specified patch"""
q = repo.mq
@@ -1546,6 +1730,69 @@
repo.mq.strip(repo, rev, backup=backup)
return 0
+def select(ui, repo, *args, **opts):
+ '''set or print guarded patches to push
+
+ use qguard command to set or print guards on patch. then use
+ qselect to tell mq which guards to use. example:
+
+ qguard foo.patch -stable (nagative guard)
+ qguard bar.patch +stable (posative guard)
+ qselect stable
+
+ this sets "stable" guard. mq will skip foo.patch (because it has
+ nagative match) but push bar.patch (because it has posative
+ match).
+
+ with no arguments, default is to print current active guards.
+ with arguments, set active guards as given.
+
+ use -n/--none to deactivate guards (no other arguments needed).
+ when no guards active, patches with posative guards are skipped,
+ patches with nagative guards are pushed.
+
+ use -s/--series to print list of all guards in series file (no
+ other arguments needed). use -v for more information.'''
+
+ q = repo.mq
+ guards = q.active()
+ if args or opts['none']:
+ q.set_active(args)
+ q.save_dirty()
+ if not args:
+ ui.status(_('guards deactivated\n'))
+ if q.series:
+ pushable = [p for p in q.unapplied(repo) if q.pushable(p[0])[0]]
+ ui.status(_('%d of %d unapplied patches active\n') %
+ (len(pushable), len(q.series)))
+ elif opts['series']:
+ guards = {}
+ noguards = 0
+ for gs in q.series_guards:
+ if not gs:
+ noguards += 1
+ for g in gs:
+ guards.setdefault(g, 0)
+ guards[g] += 1
+ if ui.verbose:
+ guards['NONE'] = noguards
+ guards = guards.items()
+ guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
+ if guards:
+ ui.note(_('guards in series file:\n'))
+ for guard, count in guards:
+ ui.note('%2d ' % count)
+ ui.write(guard, '\n')
+ else:
+ ui.note(_('no guards in series file\n'))
+ else:
+ if guards:
+ ui.note(_('active guards:\n'))
+ for g in guards:
+ ui.write(g, '\n')
+ else:
+ ui.write(_('no active guards\n'))
+
def version(ui, q=None):
"""print the version number of the mq extension"""
ui.write("mq version %s\n" % versionstr)
@@ -1605,6 +1852,9 @@
('m', 'message', '', _('set patch header to <text>')),
('l', 'logfile', '', _('set patch header to contents of <file>'))],
'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
+ 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
+ ('n', 'none', None, _('drop all guards'))],
+ 'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
'qheader': (header, [],
_('hg qheader [PATCH]')),
"^qimport":
@@ -1662,6 +1912,10 @@
('e', 'empty', None, 'clear queue status file'),
('f', 'force', None, 'force copy')],
'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
+ "qselect": (select,
+ [('n', 'none', None, _('disable all guards')),
+ ('s', 'series', None, _('list all guards in series file'))],
+ 'hg qselect [GUARDS]'),
"qseries":
(series,
[('m', 'missing', None, 'print patches not in series'),