Re: [PATCH v2] lazymanifest: write a more efficient, pypy friendly version of lazymanifest
This should fix the issues presented. There is one problem which is that the hash in test-rebase-detach changes. The way I see it is just an RNG phase-shift, but it might be a real bug. Some actual input will be appreciated. On Sat, Sep 17, 2016 at 8:22 PM, Maciej Fijalkowskiwrote: > # HG changeset patch > # User Maciej Fijalkowski > # Date 1473680234 -7200 > # Mon Sep 12 13:37:14 2016 +0200 > # Node ID 7551f1e60b2155462d89a9571eec065e9f67debc > # Parent df05c43bd1e64f1620d0b2e502f4603c1e5a8341 > lazymanifest: write a more efficient, pypy friendly version of lazymanifest > > diff --git a/mercurial/manifest.py b/mercurial/manifest.py > --- a/mercurial/manifest.py > +++ b/mercurial/manifest.py > @@ -104,69 +104,297 @@ > _checkforbidden(files) > return ''.join(lines) > > -class _lazymanifest(dict): > -"""This is the pure implementation of lazymanifest. > - > -It has not been optimized *at all* and is not lazy. > -""" > - > -def __init__(self, data): > -dict.__init__(self) > -for f, n, fl in _parse(data): > -self[f] = n, fl > - > -def __setitem__(self, k, v): > -node, flag = v > -assert node is not None > -if len(node) > 21: > -node = node[:21] # match c implementation behavior > -dict.__setitem__(self, k, (node, flag)) > +class lazymanifestiter(object): > +def __init__(self, lm): > +self.pos = 0 > +self.lm = lm > > def __iter__(self): > -return iter(sorted(dict.keys(self))) > +return self > > -def iterkeys(self): > -return iter(sorted(dict.keys(self))) > +def next(self): > +try: > +data, pos = self.lm._get(self.pos) > +except IndexError: > +raise StopIteration > +if pos == -1: > +self.pos += 1 > +return data[0] > +self.pos += 1 > +zeropos = data.find('\x00', pos) > +return data[pos:zeropos] > > -def iterentries(self): > -return ((f, e[0], e[1]) for f, e in sorted(self.iteritems())) > +class lazymanifestiterentries(object): > +def __init__(self, lm): > +self.lm = lm > +self.pos = 0 > + > +def __iter__(self): > +return self > + > +def next(self): > +try: > +data, pos = self.lm._get(self.pos) > +except IndexError: > +raise StopIteration > +if pos == -1: > +self.pos += 1 > +return data > +zeropos = data.find('\x00', pos) > +hashval = unhexlify(data, self.lm.extrainfo[self.pos], > +zeropos + 1, 40) > +flags = self.lm._getflags(data, self.pos, zeropos) > +self.pos += 1 > +return (data[pos:zeropos], hashval, flags) > + > +def unhexlify(data, extra, pos, length): > +s = data[pos:pos + length].decode('hex') > +if extra: > +s += chr(extra & 0xff) > +return s > + > +def _cmp(a, b): > +return (a > b) - (a < b) > + > +class _lazymanifest(object): > +def __init__(self, data, positions=None, extrainfo=None, extradata=None): > +if positions is None: > +self.positions = self.findlines(data) > +self.extrainfo = [0] * len(self.positions) > +self.data = data > +self.extradata = [] > +else: > +self.positions = positions[:] > +self.extrainfo = extrainfo[:] > +self.extradata = extradata[:] > +self.data = data > + > +def findlines(self, data): > +if not data: > +return [] > +pos = data.find("\n") > +positions = [0] > +while pos < len(data) - 1 and pos != -1: > +positions.append(pos + 1) > +pos = data.find("\n", pos + 1) > +return positions > + > +def _get(self, index): > +# get the position encoded in pos: > +# positive number is an index in 'data' > +# negative number is in extrapieces > +pos = self.positions[index] > +if pos >= 0: > +return self.data, pos > +return self.extradata[-pos - 1], -1 > + > +def _getkey(self, pos): > +if pos >= 0: > +return self.data[pos:self.data.find('\x00', pos + 1)] > +return self.extradata[-pos - 1][0] > + > +def bsearch(self, key): > +first = 0 > +last = len(self.positions) - 1 > + > +while first <= last: > +midpoint = (first + last)//2 > +nextpos = self.positions[midpoint] > +candidate = self._getkey(nextpos) > +r = _cmp(key, candidate) > +if r == 0: > +return midpoint > +else: > +if r < 0: > +last = midpoint - 1 > +else: > +first = midpoint + 1 > +return -1 > + > +def bsearch2(self, key): > +#
[PATCH] lazymanifest: write a more efficient, pypy friendly version of lazymanifest
# HG changeset patch # User Maciej Fijalkowski# Date 1473680234 -7200 # Mon Sep 12 13:37:14 2016 +0200 # Node ID 7551f1e60b2155462d89a9571eec065e9f67debc # Parent df05c43bd1e64f1620d0b2e502f4603c1e5a8341 lazymanifest: write a more efficient, pypy friendly version of lazymanifest diff --git a/mercurial/manifest.py b/mercurial/manifest.py --- a/mercurial/manifest.py +++ b/mercurial/manifest.py @@ -104,69 +104,297 @@ _checkforbidden(files) return ''.join(lines) -class _lazymanifest(dict): -"""This is the pure implementation of lazymanifest. - -It has not been optimized *at all* and is not lazy. -""" - -def __init__(self, data): -dict.__init__(self) -for f, n, fl in _parse(data): -self[f] = n, fl - -def __setitem__(self, k, v): -node, flag = v -assert node is not None -if len(node) > 21: -node = node[:21] # match c implementation behavior -dict.__setitem__(self, k, (node, flag)) +class lazymanifestiter(object): +def __init__(self, lm): +self.pos = 0 +self.lm = lm def __iter__(self): -return iter(sorted(dict.keys(self))) +return self -def iterkeys(self): -return iter(sorted(dict.keys(self))) +def next(self): +try: +data, pos = self.lm._get(self.pos) +except IndexError: +raise StopIteration +if pos == -1: +self.pos += 1 +return data[0] +self.pos += 1 +zeropos = data.find('\x00', pos) +return data[pos:zeropos] -def iterentries(self): -return ((f, e[0], e[1]) for f, e in sorted(self.iteritems())) +class lazymanifestiterentries(object): +def __init__(self, lm): +self.lm = lm +self.pos = 0 + +def __iter__(self): +return self + +def next(self): +try: +data, pos = self.lm._get(self.pos) +except IndexError: +raise StopIteration +if pos == -1: +self.pos += 1 +return data +zeropos = data.find('\x00', pos) +hashval = unhexlify(data, self.lm.extrainfo[self.pos], +zeropos + 1, 40) +flags = self.lm._getflags(data, self.pos, zeropos) +self.pos += 1 +return (data[pos:zeropos], hashval, flags) + +def unhexlify(data, extra, pos, length): +s = data[pos:pos + length].decode('hex') +if extra: +s += chr(extra & 0xff) +return s + +def _cmp(a, b): +return (a > b) - (a < b) + +class _lazymanifest(object): +def __init__(self, data, positions=None, extrainfo=None, extradata=None): +if positions is None: +self.positions = self.findlines(data) +self.extrainfo = [0] * len(self.positions) +self.data = data +self.extradata = [] +else: +self.positions = positions[:] +self.extrainfo = extrainfo[:] +self.extradata = extradata[:] +self.data = data + +def findlines(self, data): +if not data: +return [] +pos = data.find("\n") +positions = [0] +while pos < len(data) - 1 and pos != -1: +positions.append(pos + 1) +pos = data.find("\n", pos + 1) +return positions + +def _get(self, index): +# get the position encoded in pos: +# positive number is an index in 'data' +# negative number is in extrapieces +pos = self.positions[index] +if pos >= 0: +return self.data, pos +return self.extradata[-pos - 1], -1 + +def _getkey(self, pos): +if pos >= 0: +return self.data[pos:self.data.find('\x00', pos + 1)] +return self.extradata[-pos - 1][0] + +def bsearch(self, key): +first = 0 +last = len(self.positions) - 1 + +while first <= last: +midpoint = (first + last)//2 +nextpos = self.positions[midpoint] +candidate = self._getkey(nextpos) +r = _cmp(key, candidate) +if r == 0: +return midpoint +else: +if r < 0: +last = midpoint - 1 +else: +first = midpoint + 1 +return -1 + +def bsearch2(self, key): +# same as the above, but will always return the position +# done for performance reasons +first = 0 +last = len(self.positions) - 1 + +while first <= last: +midpoint = (first + last)//2 +nextpos = self.positions[midpoint] +candidate = self._getkey(nextpos) +r = _cmp(key, candidate) +if r == 0: +return (midpoint, True) +else: +if r < 0: +last = midpoint - 1 +else: +first = midpoint + 1 +
[Bug 5373] New: test-highlight.t fails when ipython is installed
https://bz.mercurial-scm.org/show_bug.cgi?id=5373 Bug ID: 5373 Summary: test-highlight.t fails when ipython is installed Product: Mercurial Version: unspecified Hardware: PC OS: Mac OS Status: UNCONFIRMED Severity: bug Priority: wish Component: Mercurial Assignee: bugzi...@selenic.com Reporter: fij...@gmail.com CC: mercurial-de...@selenic.com Simple reproduction: pip install ipython python run-tests.py --pure test-highlight.t There are some magic things with state going on in import machinery in demandimport, I don't want to know -- You are receiving this mail because: You are on the CC list for the bug. ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: [PATCH] rebase: rebase changesets with branch grouped
There's a topoiter or topoorder somewhere that is used by "hg log". Can that be used here? Sorry, on mobile and too lazy to find the exact function. On Sat, Sep 17, 2016, 08:18 Xidorn Quanwrote: > # HG changeset patch > # User Xidorn Quan > # Date 1474095776 -36000 > # Sat Sep 17 17:02:56 2016 +1000 > # Node ID 88184dc23ccacfc8ae450abc5d574b8b028bca79 > # Parent 285a8c3e53f2183438f0cdbc238e4ab851d0d110 > rebase: rebase changesets with branch grouped > > diff --git a/hgext/rebase.py b/hgext/rebase.py > --- a/hgext/rebase.py > +++ b/hgext/rebase.py > @@ -330,18 +330,38 @@ class rebaseruntime(object): > > inclusive=True) > > # Keep track of the current bookmarks in order to reset them later > self.currentbookmarks = repo._bookmarks.copy() > self.activebookmark = self.activebookmark or repo._activebookmark > if self.activebookmark: > bookmarks.deactivate(repo) > > -sortedrevs = sorted(self.state) > +# Sort revisions with branches grouped > cands = [k for k, v in self.state.iteritems() if v == revtodo] > +remainrevs = set(self.state.iterkeys()) > +sortedrevs = [] > +# Sort based on candidates and put their ancestors with them > +for cand in util.branchsorted(repo, cands): > +ancestors = [cand] > +remainrevs.remove(cand) > +i = 0 > +while i < len(ancestors): > +ctx = repo[ancestors[i]] > +for p in ctx.parents(): > +prev = p.rev() > +if prev in remainrevs: > +remainrevs.remove(prev) > +ancestors.append(prev) > +i += 1 > +ancestors.reverse() > +sortedrevs.extend(ancestors) > +# Finally, descendents which are not rebased > +sortedrevs.extend(sorted(remainrevs)) > + > total = len(cands) > pos = 0 > for rev in sortedrevs: > ctx = repo[rev] > desc = '%d:%s "%s"' % (ctx.rev(), ctx, > ctx.description().split('\n', 1)[0]) > names = repo.nodetags(ctx.node()) + > repo.nodebookmarks(ctx.node()) > if names: > diff --git a/mercurial/util.py b/mercurial/util.py > --- a/mercurial/util.py > +++ b/mercurial/util.py > @@ -2897,8 +2897,33 @@ decompressors = {None: lambda fh: fh, > 'BZ': _makedecompressor(lambda: bz2.BZ2Decompressor()), > 'GZ': _makedecompressor(lambda: zlib.decompressobj()), > } > # also support the old form by courtesies > decompressors['UN'] = decompressors[None] > > # convenient shortcut > dst = debugstacktrace > + > +def branchsorted(repo, revs, sortrevs=None): > +'''Returns a sorted list of revisions in a order that branches are > +grouped together.''' > +remainrevs = set(revs) > +stack = [sorted(revs, reverse=True)] > +result = [] > +while stack: > +stacktop = stack[-1] > +if not stacktop: > +stack.pop() > +continue > +nextrev = stacktop.pop() > +if nextrev not in remainrevs: > +continue > +ctx = repo[nextrev] > +# If any of its parents is still in remainrevs, we can not put > +# the changeset into result. We would traverse to it again when > +# we resolve all its parents. > +if any(p.rev() in remainrevs for p in ctx.parents()): > +continue > +remainrevs.remove(nextrev) > +result.append(nextrev) > +stack.append(sorted((c.rev() for c in ctx.children()), > reverse=True)) > +return result > diff --git a/tests/test-rebase-branch-order.t > b/tests/test-rebase-branch-order.t > new file mode 100644 > --- /dev/null > +++ b/tests/test-rebase-branch-order.t > @@ -0,0 +1,89 @@ > + $ cat >> $HGRCPATH < + > [extensions] > + > rebase= > + > [alias] > + > tglog=log -G --template "{rev}:{node|short} {desc}" > + > EOF > + > + $ hg init repo > + $ cd repo > + > + $ touch a > + $ hg ci -Am A > + adding a > + > + $ touch b > + $ hg ci -Am B > + adding b > + > + $ touch c > + $ hg ci -Am C > + adding c > + > + $ hg up 1 > + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved > + $ touch d > + $ hg ci -Am D > + adding d > + created new head > + > + $ hg up 2 > + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved > + $ touch e > + $ hg ci -Am E > + adding e > + > + $ hg up 3 > + 1 files updated, 0 files merged, 2 files removed, 0 files unresolved > + $ touch f > + $ hg ci -Am F > + adding f > + > + $ hg up 0 > + 0 files updated, 0 files merged, 3 files removed, 0 files unresolved > + $ touch g > + $ hg ci -Am G > + adding g > + created new head > + > + $ hg tglog > + @ 6:124bb27b6f28 G > + | > + | o 5:412b391de760 F > + | | > + | | o
[PATCH] rebase: rebase changesets with branch grouped
# HG changeset patch # User Xidorn Quan# Date 1474095776 -36000 # Sat Sep 17 17:02:56 2016 +1000 # Node ID 88184dc23ccacfc8ae450abc5d574b8b028bca79 # Parent 285a8c3e53f2183438f0cdbc238e4ab851d0d110 rebase: rebase changesets with branch grouped diff --git a/hgext/rebase.py b/hgext/rebase.py --- a/hgext/rebase.py +++ b/hgext/rebase.py @@ -330,18 +330,38 @@ class rebaseruntime(object): inclusive=True) # Keep track of the current bookmarks in order to reset them later self.currentbookmarks = repo._bookmarks.copy() self.activebookmark = self.activebookmark or repo._activebookmark if self.activebookmark: bookmarks.deactivate(repo) -sortedrevs = sorted(self.state) +# Sort revisions with branches grouped cands = [k for k, v in self.state.iteritems() if v == revtodo] +remainrevs = set(self.state.iterkeys()) +sortedrevs = [] +# Sort based on candidates and put their ancestors with them +for cand in util.branchsorted(repo, cands): +ancestors = [cand] +remainrevs.remove(cand) +i = 0 +while i < len(ancestors): +ctx = repo[ancestors[i]] +for p in ctx.parents(): +prev = p.rev() +if prev in remainrevs: +remainrevs.remove(prev) +ancestors.append(prev) +i += 1 +ancestors.reverse() +sortedrevs.extend(ancestors) +# Finally, descendents which are not rebased +sortedrevs.extend(sorted(remainrevs)) + total = len(cands) pos = 0 for rev in sortedrevs: ctx = repo[rev] desc = '%d:%s "%s"' % (ctx.rev(), ctx, ctx.description().split('\n', 1)[0]) names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node()) if names: diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -2897,8 +2897,33 @@ decompressors = {None: lambda fh: fh, 'BZ': _makedecompressor(lambda: bz2.BZ2Decompressor()), 'GZ': _makedecompressor(lambda: zlib.decompressobj()), } # also support the old form by courtesies decompressors['UN'] = decompressors[None] # convenient shortcut dst = debugstacktrace + +def branchsorted(repo, revs, sortrevs=None): +'''Returns a sorted list of revisions in a order that branches are +grouped together.''' +remainrevs = set(revs) +stack = [sorted(revs, reverse=True)] +result = [] +while stack: +stacktop = stack[-1] +if not stacktop: +stack.pop() +continue +nextrev = stacktop.pop() +if nextrev not in remainrevs: +continue +ctx = repo[nextrev] +# If any of its parents is still in remainrevs, we can not put +# the changeset into result. We would traverse to it again when +# we resolve all its parents. +if any(p.rev() in remainrevs for p in ctx.parents()): +continue +remainrevs.remove(nextrev) +result.append(nextrev) +stack.append(sorted((c.rev() for c in ctx.children()), reverse=True)) +return result diff --git a/tests/test-rebase-branch-order.t b/tests/test-rebase-branch-order.t new file mode 100644 --- /dev/null +++ b/tests/test-rebase-branch-order.t @@ -0,0 +1,89 @@ + $ cat >> $HGRCPATH < [extensions] + > rebase= + > [alias] + > tglog=log -G --template "{rev}:{node|short} {desc}" + > EOF + + $ hg init repo + $ cd repo + + $ touch a + $ hg ci -Am A + adding a + + $ touch b + $ hg ci -Am B + adding b + + $ touch c + $ hg ci -Am C + adding c + + $ hg up 1 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ touch d + $ hg ci -Am D + adding d + created new head + + $ hg up 2 + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ touch e + $ hg ci -Am E + adding e + + $ hg up 3 + 1 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ touch f + $ hg ci -Am F + adding f + + $ hg up 0 + 0 files updated, 0 files merged, 3 files removed, 0 files unresolved + $ touch g + $ hg ci -Am G + adding g + created new head + + $ hg tglog + @ 6:124bb27b6f28 G + | + | o 5:412b391de760 F + | | + | | o 4:82ae8dc7a9b7 E + | | | + | o | 3:ab709c9f7171 D + | | | + | | o 2:d84f5cfaaf14 C + | |/ + | o 1:76035bbd54bd B + |/ + o 0:216878401574 A + + +Test rebasing is done with commits in branch grouped together. + + $ hg rebase -s 1 -d 6 + rebasing 1:76035bbd54bd "B" + rebasing 2:d84f5cfaaf14 "C" + rebasing 4:82ae8dc7a9b7 "E" + rebasing 3:ab709c9f7171 "D" + rebasing 5:412b391de760 "F" + saved backup bundle to
Re: [PATCH] lazymanifest: write a more efficient, pypy friendly version of lazymanifest
I have fixed the problem with the test that you listed. I'm still completely unable to finish the test run - with -j it randomly hangs and does nothing with no extra output On Sat, Sep 17, 2016 at 1:47 PM, Maciej Fijalkowskiwrote: > managed to pull that. How do I incorporate my fixes for the problems now? > > On Sat, Sep 17, 2016 at 1:45 PM, Maciej Fijalkowski wrote: >> ok, I think at the end it is that: >> >> received listkey for "obsolete": 16746270 bytes >> >> and it takes a while, but there is no progress nothing which seems >> like it's hanging >> >> On Sat, Sep 17, 2016 at 1:44 PM, Maciej Fijalkowski wrote: >>> https://paste.pound-python.org/show/JB0RSPFycVzWS8X6C7hc/ >>> >>> On Sat, Sep 17, 2016 at 12:25 PM, Pierre-Yves David >>> wrote: On 09/17/2016 11:17 AM, Maciej Fijalkowski wrote: > > Hi > > When I'm trying to do that, I get that: > > https://paste.pound-python.org/show/HZsFNvYPt8EAH2ugETpx/ > > and then it hangs (for both system hg and local hg) Can you add --debug to get a bit more data? -- Pierre-Yves David ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: [PATCH] lazymanifest: write a more efficient, pypy friendly version of lazymanifest
https://paste.pound-python.org/show/JB0RSPFycVzWS8X6C7hc/ On Sat, Sep 17, 2016 at 12:25 PM, Pierre-Yves Davidwrote: > > > On 09/17/2016 11:17 AM, Maciej Fijalkowski wrote: >> >> Hi >> >> When I'm trying to do that, I get that: >> >> https://paste.pound-python.org/show/HZsFNvYPt8EAH2ugETpx/ >> >> and then it hangs (for both system hg and local hg) > > > Can you add --debug to get a bit more data? > > -- > Pierre-Yves David ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: [PATCH 2 of 2] log: drop hack to fix order of revset (issue5100)
On Sat, 17 Sep 2016 19:17:40 +0900, Yuya Nishihara wrote: > # HG changeset patch > # User Yuya Nishihara> # Date 1462253040 -32400 > # Tue May 03 14:24:00 2016 +0900 > # Node ID f99625523f84eaaedf48897322d5c9f6dce64bd9 > # Parent 961b53cbadd0e17b1e5e745d0e3cc582cbf6603f > # EXP-Topic revsetflag > log: drop hack to fix order of revset (issue5100) > > Specify ordered=True instead. Oops, this should be s/ordered=True/order=revset.followorder/. ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: [PATCH 2 of 3 v4] diffopts: notice a negated boolean flag in diffopts
On Fri, 16 Sep 2016 11:15:02 -0400, Augie Fackler wrote: > # HG changeset patch > # User Augie Fackler> # Date 1472586907 14400 > # Tue Aug 30 15:55:07 2016 -0400 > # Node ID aea18fa52d954e234fdfd1d24d3f37f0cb03dc60 > # Parent cb57a762749f29065635d0480bf5c87c644ba0c5 > diffopts: notice a negated boolean flag in diffopts Also queued this, thanks. > I feel a little bad about the isinstance() check, but some values in > diffopts are not booleans and so we need to preserve false iff the > flag is a boolean flag: failing to do this means we end up with empty > string defaults for flags clobbering meaningful values from the [diff] > section in hgrc. > > diff --git a/mercurial/patch.py b/mercurial/patch.py > --- a/mercurial/patch.py > +++ b/mercurial/patch.py > @@ -2144,7 +2144,14 @@ def difffeatureopts(ui, opts=None, untru > def get(key, name=None, getter=ui.configbool, forceplain=None): > if opts: > v = opts.get(key) > -if v: > +# diffopts flags are either None-default (which is passed > +# through unchanged, so we can identify unset values), or > +# some other falsey default (eg --unified, which defaults > +# to an empty string). We only want to override the config > +# entries from hgrc with command line values if they > +# appear to have been set, which is any truthy value, > +# True, or False. > +if v or isinstance(v, bool): > return v Perhaps this could be slightly simpler if we split get() to getbool() and getstr(), and use v is None, but that isn't a big deal. ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: [PATCH 1 of 3 v4] flags: allow specifying --no-boolean-flag on the command line (BC)
On Fri, 16 Sep 2016 11:15:01 -0400, Augie Fackler wrote: > # HG changeset patch > # User Augie Fackler> # Date 1473821877 14400 > # Tue Sep 13 22:57:57 2016 -0400 > # Node ID cb57a762749f29065635d0480bf5c87c644ba0c5 > # Parent 285a8c3e53f2183438f0cdbc238e4ab851d0d110 > flags: allow specifying --no-boolean-flag on the command line (BC) Queued this, thanks. > +Test that boolean flags allow --flag=false specification to override > [defaults] I made s/--flag=false/--no-flag/ in flight. ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel