Commit Graph

304 Commits

Author SHA1 Message Date
Durham Goode
c48caf9e03 bundle2: immediate exit for ctrl+c (issue5692)
21c2df59a regressed bundle2 by catching all exceptions and trying to handle
them. The old behavior was to allow KeyboardInterrupts to throw and not have
graceful cleanup, which allowed it to exit immediately. Let's go back to that
behavior.

Differential Revision: https://phab.mercurial-scm.org/D960
2017-10-11 10:36:59 -07:00
Boris Feld
88a253014b pull: remove inadequate use of operations records to update stepdone
The 'stepdone' set is design to be a client side mechanism. If the client used
some advanced capabilities to request necessary information (changeset,
obsmarkers, phases, etc). It marks the steps as done to avoid having a less
advanced mechanism issue a duplicated request.

So, the "stepdone.add('phases')" should be the result of a client choice,
because only the client can know it has requested all it needed to request. In
4a08cf1a2cfe this principle was broken because any phase-heads part sent by
the server to the client would declare the phases retrieval complete.

Now that there is an official phases related capability and code associated to
it. We do not need the change in 4a08cf1a2cfe anymore and we can back it out.
This brings back 'stepdone' management for 'phases' in line with the rest of
the code (including other phases handing).

Here is an example of potential misbehavior that 4a08cf1a2cfe introduced:

Imagine a server that pre-computes bundles. The bundles contains a changegroup
part and an (advisory) 'phase-heads' part. When a pull occurs, precomputed
bundled are reused if available. As the phase part is advisory it can be sent
to all clients.  However they could be relevant changesets without phase
information.  Either because they are already common or because they had no
precomputed bundle for them yet.

If receiving any 'phase-heads' parts disable subsequent phases re-trivial
parts, the client will not request phase data for all relevant changesets. For
example common changesets will not turn public.
2017-09-26 15:55:01 +02:00
Boris Feld
bbf23f4d9a pull: use 'phase-heads' to retrieve phase information
A new bundle2 capability 'phases' has been added. If 'heads' is part of the
supported value for 'phases', the server supports reading and sending 'phase-
heads' bundle2 part.

Server is now able to process a 'phases' boolean parameter to 'getbundle'. If
'True', a 'phase-heads' bundle2 part will be included in the bundle with phase
information relevant to the whole pulled set. If this method is available the
phases listkey namespace will no longer be listed.

Beside the more efficient encoding of the data, this new method will greatly
improve the phase exchange efficiency for repositories with non-served
changesets (obsolete, secret) since we'll no longer send data about the
filtered heads.

Add a new 'devel.legacy.exchange' config item to allow fallback to the old
'listkey in bundle2' method.

Reminder: the pulled set is not just the changesets bundled by the pull. It
also contains changeset selected by the "pull specification" on the client
side (eg: everything for bare pull). One of the reason why the 'pulled set' is
important is to make sure we can move -common- nodes to public.
2017-09-24 21:27:18 +02:00
Boris Feld
f759f88d15 bundle2: only grab a transaction when 'phase-heads' affect the repository
The next patch will use the 'phase-heads' part to exchange phase data relevant to
the pulled set.

'handlephases' currently acquires a transaction even in case of no-op pull,
which would results in an empty transaction and messing with the existing
journal.

Pass the transaction fetcher to updatephases so it can fetch it if necessary.
2017-09-20 18:29:10 +02:00
Boris Feld
a0c1d592a7 phases: move the binary decoding function in the phases module
We move the decoding function near the encoding one in a place where they can
be reused in other place (current target, 'exchange.py').
2017-09-19 22:23:41 +02:00
Boris Feld
ac514cb58c phases: move binary encoding into a reusable function
We want to use binary phases for pushing and pulling. We extract the encoding
function out of the bundle2 module first.
2017-09-19 22:01:31 +02:00
Boris Feld
2d59c6c27b phases: use a Struct object for binary encoding and decoding
We will move the binary encoding and decoding code to 'phases.py' in order to
make it easier to reuse. First, let's cleanup it a bit.
2017-09-19 22:08:09 +02:00
Augie Fackler
6ceabd37bd bundle2: portably grab first byte of part name for letter check 2017-09-19 00:27:55 -04:00
Augie Fackler
a00e8f6b04 bundle2: make ValueError messages native strings 2017-09-18 14:03:21 -04:00
Augie Fackler
dcafebb06b bundle2: update check for a generator to work on Python 3 2017-09-18 13:36:05 -04:00
Augie Fackler
dc633b89a0 bundle2: stop using %r to quote part names
Valid part names are restricted to [a-zA-Z0-9_:-]+, so I'm not worried
about having quoting present in places where we should have
predominantly valid part names. This will significantly ease the
Python 3 transition, and simultaneously isn't a BC because this is
only in error messages that should never be shown.
2017-09-18 13:35:43 -04:00
Durham Goode
a64cc1e3c6 bundle2: move part processing to a separate function
Now that the part processing loop is tiny, let's move it to a separate function.
This will allow extensions to completely replace the part processing logic,
without having to replace the overall bundle processing logic or the stream
maintenance logic.

This will be useful for the infinitepush extension, so it can completely take
over receiving a bundle and rerouting it to a side store. This will also make it
easier to upstream the infinitepush functionality later.

Differential Revision: https://phab.mercurial-scm.org/D709
2017-09-14 10:20:05 -07:00
Durham Goode
32d42092c8 bundle2: remove unnecessary try finally
This is no longer needed.

Differential Revision: https://phab.mercurial-scm.org/D708
2017-09-14 10:20:05 -07:00
Durham Goode
d241ca079c bundle2: move handler validation out of processpart
As part of refactoring bundle part processing let's move handler validation to
its own function.

Differential Revision: https://phab.mercurial-scm.org/D707
2017-09-14 10:20:05 -07:00
Durham Goode
b29bb0eb76 bundle2: move processpart stream maintenance into part iterator
The processpart function also did some stream maintenance, so let's move it to
the part iterator as well, as part of moving all part iteration logic into the
class.

There is one place processpart is called outside of the normal loop, so we
manually handle the seek there.

The now-empty try/finally will be removed in a later patch, for ease of review.

Differential Revision: https://phab.mercurial-scm.org/D706
2017-09-14 10:20:05 -07:00
Augie Fackler
96bbe76280 bundle2: raise a more helpful error if building a bundle part header fails
I've tripped on this several times now, and am tired of debugging. Now
the header parts are part of the error message when the ''.join()
fails, which makes debugging obvious.
2017-09-15 18:37:29 -04:00
Augie Fackler
fdaf985a63 bundles: turn nbchanges int into a bytestr using pycompat.bytestr
Fixes some python 3 failures.
2017-09-15 18:38:36 -04:00
Durham Goode
f57a683d4b bundle2: move exception handling into part iterator
As part of separating the part iteration logic from the part handling logic,
let's move the exception handling to the part iterator class.

Differential Revision: https://phab.mercurial-scm.org/D705
2017-09-13 20:39:01 -07:00
Durham Goode
a46fac03a2 bundle2: move part counter to partiterator
As part of moving the part iterator logic to a separate class, let's move the
part counting logic and the output for it.

Differential Revision: https://phab.mercurial-scm.org/D704
2017-09-13 17:16:50 -07:00
Durham Goode
577e9d1779 bundle2: move part iterator a separate class
Currently, the part iterator logic is tightly coupled with the part handling
logic, which means it's hard to replace the part handling logic without
duplicating the part iterator bits.

In a future diff we'll want to be able to replace all part handling, so let's
begin refactoring the part iterator logic to it's own class.

Differential Revision: https://phab.mercurial-scm.org/D703
2017-09-13 17:16:45 -07:00
Durham Goode
21faf618dc changegroup: replace getchangegroup with makechangegroup
As part of reducing the number of changegroup creation APIs, let's replace
getchangegroup with calls to makechangegroup. This is mostly a drop in
replacement, but it does change the version specifier to be required, so it's
more obvious which callers are creating old version 1 changegroups still.

Differential Revision: https://phab.mercurial-scm.org/D669
2017-09-10 18:50:12 -07:00
Durham Goode
22fc2e18a8 bundle2: seek part back during iteration
Previously, iterparts would yield the part to users, then consume the part. This
changed the part after the user was given it and left it at the end, both of
which seem unexpected.  Let's seek back to the beginning after we've consumed
it. I tried not seeking to the end at all, but that seems important for the
overall bundle2 consumption.

This is used in a future patch to let us move the bundlerepo
bundle2-changegroup-part to be handled entirely within the for loop, instead of
having to do a seek back to 0 after the entire loop finishes.

Differential Revision: https://phab.mercurial-scm.org/D289
2017-08-23 12:35:03 -07:00
Martin von Zweigbergk
7603f48c32 exchange: don't attempt phase exchange if phase-heads was in bundle
The Mercurial core server doesn't yet include phase-heads parts in the
bundle, but our Google-internal server wants to do
that. Unfortunately, the usual exchange still happens even if
phase-heads part is included (including the short-circuited one for
old/publishing servers). That means that even if our server (again,
the Google-internal one, but also future Mercurial core servers)
includes a phase-heads part to indicate that some heads should be
drafts, that would still get overwritten by the phase updating that
happens after. So let's fix that by marking the phase step done if we
receive at least one phase-heads part in the bundle.

Differential Revision: https://phab.mercurial-scm.org/D440
2017-08-17 13:04:47 -07:00
Alex Gaynor
df2c1417e6 bundle2: fixed usage of an attribute that was removed in py3k
Differential Revision: https://phab.mercurial-scm.org/D482
2017-08-23 01:09:08 +00:00
Pulkit Goyal
7d16e8a210 pushvars: add a coreconfigitem for push.pushvars.server
Differential Revision: https://phab.mercurial-scm.org/D359
2017-08-12 04:47:40 +05:30
Yuya Nishihara
843b049128 bundle2: relax the condition to update transaction.hookargs
This is just a micro optimization. If hookargs is empty, nothing should be
necessary.
2017-08-13 11:10:35 +09:00
Yuya Nishihara
355a92a8ee bundle2: raise ProgrammingError for invalid call of addhookargs()
It should be hard error. Also fixed the error message as s/hooks/hookargs/.
2017-08-13 11:05:56 +09:00
Boris Feld
657776f4e3 bundle2: fix transaction availability detection
Changeset aa97e972460f introduce more complex logic around
'bundleoperation.gettransaction'. In that process it turns the old "attribute"
into a proper method which breaks the code that detects the "transaction
availability".

The change was visible in 'test-acl.t', fixing this reverts the test changes.

Differential Revision: https://phab.mercurial-scm.org/D303
2017-08-09 17:01:21 +02:00
Augie Fackler
6a68930f82 bundle2: convert ints to strings using pycompat.bytestring()
Fixes some Python 3 regressions.

We don't use %d here because the part id is actually an
Optional[int]. It should always be initialized to a non-None value by
the time this code executes, but we shouldn't blindly depend on that
being the case.

Differential Revision: https://phab.mercurial-scm.org/D272
2017-07-24 11:16:32 -04:00
Pulkit Goyal
89fd642a01 pushvars: move fb extension pushvars to core
pushvars extension in fbext adds a --pushvars flag to push command using which
one send strings to server which becomes environment variables there prepended
with HG_USERVAR_. These variables can then be used to run hooks on the server.
The extension is moved directly to core and unbundling of the strings and
converting them to environment variables at server is disabled by default for
security reasons. One can turn that on by following config:

[push]
pushvars.server = true

This patch also adds the test for the extension.

Differential Revision: https://phab.mercurial-scm.org/D210
2017-07-31 09:59:42 +05:30
Yuya Nishihara
fb236e4381 py3: convert arbitrary exception object to byte string more reliably
Our exception types implement __bytes__(), which should be tried first. Do
lossy encoding conversion as a last resort.
2017-08-03 23:02:32 +09:00
Augie Fackler
65bd64ce26 bundle2: obtain repr() of exception in a python3-safe way
This was exposed by other problems in bundle generation, but I'm not
sure how to test it for now.
2017-07-24 11:19:11 -04:00
Augie Fackler
3038df5570 bundle2: use bytestr() instead of str() to convert part id to bytes
This was exposed by trying to run previously-passing Python 3 tests.
2017-07-24 11:28:40 -04:00
Augie Fackler
5d1326116a bundle2: work around zip() being lazy in Python 3 2017-07-24 11:20:08 -04:00
Augie Fackler
070535e700 bundle2: look for __next__ as well as next to identify iterators
In Python 3, next is called __next__ and this was failing to catch
some iterators.
2017-07-24 11:19:45 -04:00
Augie Fackler
6efeab9bb5 bundle2: use modern Python division
This was failing on Python 3 because the / was returning a float,
which was then making the __mul__ on a bytes sad.
2017-07-24 11:17:36 -04:00
Pulkit Goyal
f19a5db742 bundle2: load hookargs from bundleoperation into transaction when started one
When a transaction is started, we must load the hookargs from the
bundleoperation object to the transaction so that they can be used in the
transaction. Also this patch makes sure no more hookargs are added to the
bundleoperation object once the transaction starts.

This is a part of porting fb extension bundle2hooks to core.

Differential Revision: https://phab.mercurial-scm.org/D209
2017-08-02 03:23:06 +05:30
Pulkit Goyal
5534a45217 bundle2: add the capability to store hookargs on bundle operation object
There are extensions like pushrebase, pushvars which run hooks on a server
before taking the lock. Since the lock is not taken, transaction is not there,
so the hookargs can't be stored on the transaction. Adding hooksargs to bundle
operation object will help in running hooks before taking the lock.

This is a part of moving fb's extension bundle2hooks to core.

Differential Revision: https://phab.mercurial-scm.org/D208
2017-08-02 03:08:42 +05:30
Boris Feld
68ddce738e transaction-summary: display the summary for all transactions
Now that we records "all" changes happening in a transaction (in tr.changes)
we will be able to provide better report on various changes (phases turned
public, changeset obsoleted, branch merged or created, etc..)

This is far too late in the cycle to play with this, but having this existing
method called more widely will help extensions to play around with various
options during the 4.4 cycle.

Instead of calling registersummarycallback only for transactions we want, we
always call it and use the transaction name to decide when to report (eg: we
do not want `hg amend` to report new obsoleted changesets). Filtering on
transaction name does not seems great, but seems good enough for the moment.
We can change the API during the next cycle.

The previous manual call during unbundling of the bundle2 "obsmarkers" part is
no longer necessary and has been dropped.
2017-07-16 02:20:06 +02:00
Jun Wu
e47f7dc2fa codemod: register core configitems using a script
This is done by a script [2] using RedBaron [1], a tool designed for doing
code refactoring. All "default" values are decided by the script and are
strongly consistent with the existing code.

There are 2 changes done manually to fix tests:

  [warn] mercurial/exchange.py: experimental.bundle2-output-capture: default needs manual removal
  [warn] mercurial/localrepo.py: experimental.hook-track-tags: default needs manual removal

Since RedBaron is not confident about how to indent things [2].

[1]: https://github.com/PyCQA/redbaron
[2]: https://github.com/PyCQA/redbaron/issues/100
[3]:

#!/usr/bin/env python
# codemod_configitems.py - codemod tool to fill configitems
#
# Copyright 2017 Facebook, Inc.
#
# 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, print_function

import os
import sys

import redbaron

def readpath(path):
    with open(path) as f:
        return f.read()

def writepath(path, content):
    with open(path, 'w') as f:
        f.write(content)

_configmethods = {'config', 'configbool', 'configint', 'configbytes',
                  'configlist', 'configdate'}

def extractstring(rnode):
    """get the string from a RedBaron string or call_argument node"""
    while rnode.type != 'string':
        rnode = rnode.value
    return rnode.value[1:-1]  # unquote, "'str'" -> "str"

def uiconfigitems(red):
    """match *.ui.config* pattern, yield (node, method, args, section, name)"""
    for node in red.find_all('atomtrailers'):
        entry = None
        try:
            obj = node[-3].value
            method = node[-2].value
            args = node[-1]
            section = args[0].value
            name = args[1].value
            if (obj in ('ui', 'self') and method in _configmethods
                and section.type == 'string' and name.type == 'string'):
                entry = (node, method, args, extractstring(section),
                         extractstring(name))
        except Exception:
            pass
        else:
            if entry:
                yield entry

def coreconfigitems(red):
    """match coreconfigitem(...) pattern, yield (node, args, section, name)"""
    for node in red.find_all('atomtrailers'):
        entry = None
        try:
            args = node[1]
            section = args[0].value
            name = args[1].value
            if (node[0].value == 'coreconfigitem' and section.type == 'string'
                and name.type == 'string'):
                entry = (node, args, extractstring(section),
                         extractstring(name))
        except Exception:
            pass
        else:
            if entry:
                yield entry

def registercoreconfig(cfgred, section, name, defaultrepr):
    """insert coreconfigitem to cfgred AST

    section and name are plain string, defaultrepr is a string
    """
    # find a place to insert the "coreconfigitem" item
    entries = list(coreconfigitems(cfgred))
    for node, args, nodesection, nodename in reversed(entries):
        if (nodesection, nodename) < (section, name):
            # insert after this entry
            node.insert_after(
                'coreconfigitem(%r, %r,\n'
                '    default=%s,\n'
                ')' % (section, name, defaultrepr))
            return

def main(argv):
    if not argv:
        print('Usage: codemod_configitems.py FILES\n'
              'For example, FILES could be "{hgext,mercurial}/*/**.py"')
    dirname = os.path.dirname
    reporoot = dirname(dirname(dirname(os.path.abspath(__file__))))

    # register configitems to this destination
    cfgpath = os.path.join(reporoot, 'mercurial', 'configitems.py')
    cfgred = redbaron.RedBaron(readpath(cfgpath))

    # state about what to do
    registered = set((s, n) for n, a, s, n in coreconfigitems(cfgred))
    toregister = {} # {(section, name): defaultrepr}
    coreconfigs = set() # {(section, name)}, whether it's used in core

    # first loop: scan all files before taking any action
    for i, path in enumerate(argv):
        print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
        iscore = ('mercurial' in path) and ('hgext' not in path)
        red = redbaron.RedBaron(readpath(path))
        # find all repo.ui.config* and ui.config* calls, and collect their
        # section, name and default value information.
        for node, method, args, section, name in uiconfigitems(red):
            if section == 'web':
                # [web] section has some weirdness, ignore them for now
                continue
            defaultrepr = None
            key = (section, name)
            if len(args) == 2:
                if key in registered:
                    continue
                if method == 'configlist':
                    defaultrepr = 'list'
                elif method == 'configbool':
                    defaultrepr = 'False'
                else:
                    defaultrepr = 'None'
            elif len(args) >= 3 and (args[2].target is None or
                                     args[2].target.value == 'default'):
                # try to understand the "default" value
                dnode = args[2].value
                if dnode.type == 'name':
                    if dnode.value in {'None', 'True', 'False'}:
                        defaultrepr = dnode.value
                elif dnode.type == 'string':
                    defaultrepr = repr(dnode.value[1:-1])
                elif dnode.type in ('int', 'float'):
                    defaultrepr = dnode.value
            # inconsistent default
            if key in toregister and toregister[key] != defaultrepr:
                defaultrepr = None
            # interesting to rewrite
            if key not in registered:
                if defaultrepr is None:
                    print('[note] %s: %s.%s: unsupported default'
                          % (path, section, name))
                    registered.add(key) # skip checking it again
                else:
                    toregister[key] = defaultrepr
                    if iscore:
                        coreconfigs.add(key)

    # second loop: rewrite files given "toregister" result
    for path in argv:
        # reconstruct redbaron - trade CPU for memory
        red = redbaron.RedBaron(readpath(path))
        changed = False
        for node, method, args, section, name in uiconfigitems(red):
            key = (section, name)
            defaultrepr = toregister.get(key)
            if defaultrepr is None or key not in coreconfigs:
                continue
            if len(args) >= 3 and (args[2].target is None or
                                   args[2].target.value == 'default'):
                try:
                    del args[2]
                    changed = True
                except Exception:
                    # redbaron fails to do the rewrite due to indentation
                    # see https://github.com/PyCQA/redbaron/issues/100
                    print('[warn] %s: %s.%s: default needs manual removal'
                          % (path, section, name))
            if key not in registered:
                print('registering %s.%s' % (section, name))
                registercoreconfig(cfgred, section, name, defaultrepr)
                registered.add(key)
        if changed:
            print('updating %s' % path)
            writepath(path, red.dumps())

    if toregister:
        print('updating configitems.py')
        writepath(cfgpath, cfgred.dumps())

if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
2017-07-14 14:22:40 -07:00
Boris Feld
0949aa4c6f changegroup: stop returning and recording added nodes in 'cg.apply'
cg.apply used to returns the added nodes. Callers doesn't have a use for it
anymore, remove the added node and stops recording it in the current
operation.

This information was added in the current release cycle so no extensions
breakage should happens.
2017-07-13 21:08:06 +02:00
Boris Feld
f2e89981fb phases: remove trace of addednodes in the 'phase-heads' handling
updatephases have no use of the 'addednodes' parameter since 44be3dc1fec8.
However caller are still passing it for nothing, remove the parameter and
remove computing of the added nodes in caller.
2017-07-13 21:10:55 +02:00
Boris Feld
20ea8cb94b bundle2: automatically add 'targetphase' parameter in writenewbundle
If we are bundling secret changeset and the bundle will contain phase, we
request the changegroup to be applied as secret.

It will be useful for next patch as we are now sure that secrets changesets
are applied as secret and not applied as draft then forced to secret.
2017-07-11 05:12:03 +02:00
Boris Feld
438579760f bundle2: support the 'targetphase' parameter for the changegroup part
By default unbundled changesets are drafts. We want to reduce the number of
phases changes during unbundling by giving the possibility to the bundle to
indicate the phase of unbundled changesets.

The longer terms goal is to add phase movement tracking in tr.changes and the
'retractboundary' call is making it more complicated than we want.
2017-07-11 05:11:52 +02:00
Pierre-Yves David
5e97e55c0f obsolete: reports the number of local changeset obsoleted when unbundling
This is a first basic visible usage of the changes tracking in the transaction.
We adds a new function computing the pre-existing changesets obsoleted by a
transaction and a transaction call back displaying this information.

Example output:

  added 1 changesets with 1 changes to 1 files (+1 heads)
  3 new obsolescence markers
  obsoleted 1 changesets

The goal is to evolve the transaction summary into something bigger, gathering
existing output there and adding new useful one. This patch is a good first step
on this road. The new output is basic but give a user to the content of
tr.changes['obsmarkers'] and give an idea of the new options we haves. I expect
to revisit the message soon.

The caller recording the transaction summary should also be moved into a more
generic location but further refactoring is needed before it can happen.
2017-06-28 03:54:19 +02:00
Pierre-Yves David
8e3b51a1d6 configitems: register the 'server.concurrent-push-mode' config 2017-06-30 03:44:09 +02:00
Pierre-Yves David
7d6eacd5de config: register the 'devel.bundle2.debug' config
Existing explicit default are dropped now that the default is centralised.
2017-06-28 13:25:33 +02:00
Siddharth Agarwal
218f4e2803 bundle2: add debug info about the number of stream params
Seems like the %i was never substituted.
2017-06-27 14:38:00 -07:00
Siddharth Agarwal
1920cf0324 bundle2: add some debugging information to the not-a-bundle error
I found this useful while trying to debug wireproto-related issues.
2017-06-27 14:30:55 -07:00
Martin von Zweigbergk
51c1cdcd1e bundle: inline applybundle1()
We have now gotten rid of all but one caller, so let's inline it
there.
2017-06-22 21:45:32 -07:00