Mercurial > hg
view mercurial/formatter.py @ 29610:754f63671229 stable
rebase: turn rebase revs into set before filtering obsolete
When the inhibit extension from mutable-history is enabled, it attempts to
iterate over the rebaseset to prevent the nodes being rebased from being
marked obsolete. This happens at the same time as rebase's
_filterobsoleterevs function trying to iterate over the rebaseset to figure
out which ones are obsolete. The two of these iterating over the same
revset generatorset cause a 'generator already executing' exception. This is
probably a flaw in the revset implementation, since iterating over the same
set twice should be supported.
This regression was introduced in 5d16ebe7b14, since it changed
_filterobsoleterevs to be called before the rebaseset was turned into a
set(). For now let’s just make the rebaseset an actual set again before
calling that function. This was caught by the inhibit tests.
The relevant call stack from test-inhibit.t:
File "/tmp/hgtests.jgjrN5/install/lib/python/hgext/rebase.py", line 285, in _preparenewrebase
obsrevs = _filterobsoleterevs(self.repo, rebaseset)
File "/data/hgbuild/facebook-hg-rpms/mutable-history/hgext/inhibit.py", line 197, in _filterobsoleterevswrap
r = orig(repo, rebasesetrevs, *args, **kwargs)
File "/tmp/hgtests.jgjrN5/install/lib/python/hgext/rebase.py", line 1380, in _filterobsoleterevs
return set(r for r in revs if repo[r].obsolete())
File "/tmp/hgtests.jgjrN5/install/lib/python/hgext/rebase.py", line 1380, in <genexpr>
return set(r for r in revs if repo[r].obsolete())
File "/tmp/hgtests.jgjrN5/install/lib/python/mercurial/revset.py", line 3079, in _iterordered
val2 = next(iter2)
File "/tmp/hgtests.jgjrN5/install/lib/python/mercurial/revset.py", line 3417, in gen
yield nextrev()
File "/tmp/hgtests.jgjrN5/install/lib/python/mercurial/revset.py", line 3424, in _consumegen
for item in self._gen:
File "/tmp/hgtests.jgjrN5/install/lib/python/mercurial/revset.py", line 71, in iterate
cl = repo.changelog
File "/tmp/hgtests.jgjrN5/install/lib/python/mercurial/repoview.py", line 319, in changelog
revs = filterrevs(unfi, self.filtername)
File "/tmp/hgtests.jgjrN5/install/lib/python/mercurial/repoview.py", line 261, in filterrevs
repo.filteredrevcache[filtername] = func(repo.unfiltered())
File "/data/hgbuild/facebook-hg-rpms/mutable-history/hgext/directaccess.py", line 65, in _computehidden
hidden = repoview.filterrevs(repo, 'visible')
File "/tmp/hgtests.jgjrN5/install/lib/python/mercurial/repoview.py", line 261, in filterrevs
repo.filteredrevcache[filtername] = func(repo.unfiltered())
File "/tmp/hgtests.jgjrN5/install/lib/python/mercurial/repoview.py", line 175, in computehidden
hideable = hideablerevs(repo)
File "/tmp/hgtests.jgjrN5/install/lib/python/mercurial/repoview.py", line 33, in hideablerevs
return obsolete.getrevs(repo, 'obsolete')
File "/tmp/hgtests.jgjrN5/install/lib/python/mercurial/obsolete.py", line 1097, in getrevs
repo.obsstore.caches[name] = cachefuncs[name](repo)
File "/data/hgbuild/facebook-hg-rpms/mutable-history/hgext/inhibit.py", line 255, in _computeobsoleteset
if getrev(n) not in blacklist:
File "/tmp/hgtests.jgjrN5/install/lib/python/mercurial/revset.py", line 3264, in __contains__
return x in self._r1 or x in self._r2
File "/tmp/hgtests.jgjrN5/install/lib/python/mercurial/revset.py", line 3348, in __contains__
for l in self._consumegen():
File "/tmp/hgtests.jgjrN5/install/lib/python/mercurial/revset.py", line 3424, in _consumegen
for item in self._gen:
ValueError: generator already executing
author | Simon Farnsworth <simonfar@fb.com> |
---|---|
date | Tue, 19 Jul 2016 03:29:53 -0700 |
parents | b501579147f1 |
children | c3a9cd78b151 |
line wrap: on
line source
# formatter.py - generic output formatting for mercurial # # Copyright 2012 Matt Mackall <mpm@selenic.com> # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. from __future__ import absolute_import import os from .i18n import _ from .node import ( hex, short, ) from . import ( encoding, error, templater, util, ) pickle = util.pickle class baseformatter(object): def __init__(self, ui, topic, opts): self._ui = ui self._topic = topic self._style = opts.get("style") self._template = opts.get("template") self._item = None # function to convert node to string suitable for this output self.hexfunc = hex def __nonzero__(self): '''return False if we're not doing real templating so we can skip extra work''' return True def _showitem(self): '''show a formatted item once all data is collected''' pass def startitem(self): '''begin an item in the format list''' if self._item is not None: self._showitem() self._item = {} def data(self, **data): '''insert data into item that's not shown in default output''' self._item.update(data) def write(self, fields, deftext, *fielddata, **opts): '''do default text output while assigning data to item''' fieldkeys = fields.split() assert len(fieldkeys) == len(fielddata) self._item.update(zip(fieldkeys, fielddata)) def condwrite(self, cond, fields, deftext, *fielddata, **opts): '''do conditional write (primarily for plain formatter)''' fieldkeys = fields.split() assert len(fieldkeys) == len(fielddata) self._item.update(zip(fieldkeys, fielddata)) def plain(self, text, **opts): '''show raw text for non-templated mode''' pass def end(self): '''end output for the formatter''' if self._item is not None: self._showitem() class plainformatter(baseformatter): '''the default text output scheme''' def __init__(self, ui, topic, opts): baseformatter.__init__(self, ui, topic, opts) if ui.debugflag: self.hexfunc = hex else: self.hexfunc = short def __nonzero__(self): return False def startitem(self): pass def data(self, **data): pass def write(self, fields, deftext, *fielddata, **opts): self._ui.write(deftext % fielddata, **opts) def condwrite(self, cond, fields, deftext, *fielddata, **opts): '''do conditional write''' if cond: self._ui.write(deftext % fielddata, **opts) def plain(self, text, **opts): self._ui.write(text, **opts) def end(self): pass class debugformatter(baseformatter): def __init__(self, ui, topic, opts): baseformatter.__init__(self, ui, topic, opts) self._ui.write("%s = [\n" % self._topic) def _showitem(self): self._ui.write(" " + repr(self._item) + ",\n") def end(self): baseformatter.end(self) self._ui.write("]\n") class pickleformatter(baseformatter): def __init__(self, ui, topic, opts): baseformatter.__init__(self, ui, topic, opts) self._data = [] def _showitem(self): self._data.append(self._item) def end(self): baseformatter.end(self) self._ui.write(pickle.dumps(self._data)) def _jsonifyobj(v): if isinstance(v, tuple): return '[' + ', '.join(_jsonifyobj(e) for e in v) + ']' elif v is None: return 'null' elif v is True: return 'true' elif v is False: return 'false' elif isinstance(v, (int, float)): return str(v) else: return '"%s"' % encoding.jsonescape(v) class jsonformatter(baseformatter): def __init__(self, ui, topic, opts): baseformatter.__init__(self, ui, topic, opts) self._ui.write("[") self._ui._first = True def _showitem(self): if self._ui._first: self._ui._first = False else: self._ui.write(",") self._ui.write("\n {\n") first = True for k, v in sorted(self._item.items()): if first: first = False else: self._ui.write(",\n") self._ui.write(' "%s": %s' % (k, _jsonifyobj(v))) self._ui.write("\n }") def end(self): baseformatter.end(self) self._ui.write("\n]\n") class templateformatter(baseformatter): def __init__(self, ui, topic, opts): baseformatter.__init__(self, ui, topic, opts) self._topic = topic self._t = gettemplater(ui, topic, opts.get('template', '')) def _showitem(self): g = self._t(self._topic, ui=self._ui, **self._item) self._ui.write(templater.stringify(g)) def lookuptemplate(ui, topic, tmpl): # looks like a literal template? if '{' in tmpl: return tmpl, None # perhaps a stock style? if not os.path.split(tmpl)[0]: mapname = (templater.templatepath('map-cmdline.' + tmpl) or templater.templatepath(tmpl)) if mapname and os.path.isfile(mapname): return None, mapname # perhaps it's a reference to [templates] t = ui.config('templates', tmpl) if t: return templater.unquotestring(t), None if tmpl == 'list': ui.write(_("available styles: %s\n") % templater.stylelist()) raise error.Abort(_("specify a template")) # perhaps it's a path to a map or a template if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl): # is it a mapfile for a style? if os.path.basename(tmpl).startswith("map-"): return None, os.path.realpath(tmpl) tmpl = open(tmpl).read() return tmpl, None # constant string? return tmpl, None def gettemplater(ui, topic, spec): tmpl, mapfile = lookuptemplate(ui, topic, spec) assert not (tmpl and mapfile) if mapfile: return templater.templater.frommapfile(mapfile) return maketemplater(ui, topic, tmpl) def maketemplater(ui, topic, tmpl, filters=None, cache=None): """Create a templater from a string template 'tmpl'""" aliases = ui.configitems('templatealias') t = templater.templater(filters=filters, cache=cache, aliases=aliases) if tmpl: t.cache[topic] = tmpl return t def formatter(ui, topic, opts): template = opts.get("template", "") if template == "json": return jsonformatter(ui, topic, opts) elif template == "pickle": return pickleformatter(ui, topic, opts) elif template == "debug": return debugformatter(ui, topic, opts) elif template != "": return templateformatter(ui, topic, opts) # developer config: ui.formatdebug elif ui.configbool('ui', 'formatdebug'): return debugformatter(ui, topic, opts) # deprecated config: ui.formatjson elif ui.configbool('ui', 'formatjson'): return jsonformatter(ui, topic, opts) return plainformatter(ui, topic, opts)