Commit Graph

52 Commits

Author SHA1 Message Date
Jun Wu
364689727a ssh: remove "remote:" prefix if the stderr message starts with "ssh:"
Summary:
All ssh stderr are prefixed by "remote:", which could be confusing because the
message can also be some errors from the local ssh program. Treat "ssh:" as a
special case and do not prefix it with "remote:".

Reviewed By: markbt

Differential Revision: D10437082

fbshipit-source-id: 37935bd7276969ef17fa5028b02d01e3c9d0bf30
2018-10-18 19:56:49 -07:00
Mark Thomas
50cfb5d29d sshpeer: record number of bytes sent and received via ssh
Summary:
Log the number of bytes sent to and received from ssh peers so that it can be
picked up by the sampling extension.

Reviewed By: quark-zju

Differential Revision: D9150291

fbshipit-source-id: ce70520059488688b0fb9fc4e09f4a2100a04639
2018-08-06 03:49:50 -07:00
Durham Goode
fda9668d9f ssh: update another spot to write remote stderr to stderr
Summary:
2f205fdc948fbea made remote stderr get written to local stderr, but
didn't catch this spot where when a connection closes we read the remainder of
stderr and write it out.

Reviewed By: quark-zju

Differential Revision: D8627272

fbshipit-source-id: c846ac6bb7425114fb29c6374c74b35057c14e63
2018-06-25 19:20:11 -07:00
Jun Wu
d02477d831 sshpeer: forward remote stderr to stderr
Summary:
Some scripts are parsing all hg outputs. The remote stderr output could
contain random noise that break the scripts. Therefore let's forward
remote stderr to local stderr, instead of local stdout.

Reviewed By: DurhamG

Differential Revision: D8514952

fbshipit-source-id: 2f205fdc948fbeacd20b5af9d6d52eaa8212e90e
2018-06-25 08:44:32 -07:00
Lukasz Langa
dfda82e492 Upgrade to 18.5b1
Summary: Mostly empty lines removed and added.  A few bugfixes on excessive line splitting.

Reviewed By: quark-zju

Differential Revision: D8199128

fbshipit-source-id: 90c1616061bfd7cfbba0b75f03f89683340374d5
2018-05-30 02:23:58 -07:00
Jun Wu
584656dff3 codemod: join the auto-formatter party
Summary:
Turned on the auto formatter. Ran `arc lint --apply-patches --take BLACK **/*.py`.
Then run `arc lint` again so some other autofixers like spellchecker etc. looked
at the code base. Manually accept the changes whenever they make sense, or use
a workaround (ex. changing "dict()" to "dict constructor") where autofix is false
positive. Disabled linters on files that are hard (i18n/polib.py) to fix, or less
interesting to fix (hgsubversion tests), or cannot be fixed without breaking
OSS build (FBPYTHON4).

Conflicted linters (test-check-module-imports.t, part of test-check-code.t,
test-check-pyflakes.t) are removed or disabled.

Duplicated linters (test-check-pyflakes.t, test-check-pylint.t) are removed.

An issue of the auto-formatter is lines are no longer guarnateed to be <= 80
chars. But that seems less important comparing with the benefit auto-formatter
provides.

As we're here, also remove test-check-py3-compat.t, as it is currently broken
if `PYTHON3=/bin/python3` is set.

Reviewed By: wez, phillco, simpkins, pkaush, singhsrb

Differential Revision: D8173629

fbshipit-source-id: 90e248ae0c5e6eaadbe25520a6ee42d32005621b
2018-05-25 22:17:29 -07:00
Mark Thomas
dd7b147493 sshpeer: suspend progress bars while connecting
Summary:
While establishing an ssh connection, ssh may need to interact with the user
(e.g. to collect passwords).  Suspend the progress bar for the duration of
this, so it doesn't interfere.

Reviewed By: quark-zju

Differential Revision: D7876414

fbshipit-source-id: 5fa82f0f40fcffa6b94fa0210d102c76d3618a1d
2018-05-08 03:18:36 -07:00
Mark Thomas
9979530751 sshpeer: add timeblockedsection for ssh setup
Summary:
We would like to know how much time is spent setting up ssh connections.  Add a
timeblockedsection to sshpeer that records the amount of time between starting
the ssh command, and getting (and validating) the response for the `hello` and
`between` commands.

Reviewed By: ryanmce, farnz

Differential Revision: D7584383

fbshipit-source-id: fd3c48dc57e0ebbafc191c235355ce2330c6bd61
2018-04-13 21:51:52 -07:00
Kostia Balytskyi
7405833d16 sshpeer: allow for additional environment passing to ssh exe
We already have the ability to customize the ssh command line arguments, let's
add the ability to customize its environment as well.

Example use-case is ssh.exe from Git on Windows. If `HOME` enviroment variable
is present and has some non-empty value, ssh.exe will try to access that
location for some stuff (for example, it seems for resolving `~` in
`.ssh/config`). Git for Windows seems to sometimess set this variable to the
value of `/home/username` which probably works under Git Bash, but does not
work in a native `cmd.exe` or `powershell`. Whatever the root cause, setting
`HOME` to be an empty string heals things. Therefore, some distributors
might want to set `sshenv.HOME=` in the configuration (seems less intrusive
that forcing everyone to tweak their env).

Test Plan:
- rt

Differential Revision: https://phab.mercurial-scm.org/D1683
2017-12-14 14:31:57 +00:00
Zuzanna Mroczek
344962042e sshpeer: add a configurable hint for the ssh error message
Adding a possibility to configure error hint to be shown in the case of problems with SSH. Example of such hint can be "Please see http://company/internalwiki/ssh.html".

Test Plan:
- Ran hg pull with broken link and verified the output has no hint by default:

```
pulling from ssh://brokenrepository.com//repo
remote: ssh: Could not resolve hostname brokenrepository.com: Name or service not known
abort: no suitable response from remote hg!
```

- Run hg pull --config ui.ssherrorhint="Please see http://company/internalwiki/ssh.html":

```
pulling from ssh://brokenrepository.com//repo
remote: ssh: Could not resolve hostname brokenrepository.com: Name or service not known
abort: no suitable response from remote hg!
(Please see http://company/internalwiki/ssh.html)
```

Differential Revision: https://phab.mercurial-scm.org/D1431
2017-11-20 01:40:26 -08:00
Durham Goode
477a318d3a ssh: fix flakey ssh errors on BSD systems
There's been a persistent issue with flakiness on BSD systems (like OSX) where
the 'no suitable response from remote hg' message would sometimes not appear.
This was caused by one of the earlier calls failing with a "IOError: Broken
pipe". Catching those errors and printing the same message removes the
flakiness.

Differential Revision: https://phab.mercurial-scm.org/D687
2017-09-11 15:59:18 -07:00
Gregory Szorc
d5338b2208 wireproto: use new peer interface
The wirepeer class provides concrete implementations of peer interface
methods for calling wire protocol commands. It makes sense for this
class to inherit from the peer abstract base class. So we change
that.

Since httppeer and sshpeer have already been converted to the new
interface, peerrepository is no longer adding any value. So it has
been removed. httppeer and sshpeer have been updated to reflect the
loss of peerrepository and the inheritance of the abstract base
class in wirepeer.

The code changes in wirepeer are reordering of methods to group
by interface.

Some Python code in tests was updated to reflect changed APIs.

.. api::

   peer.peerrepository has been removed. Use repository.peer abstract
   base class to represent a peer repository.

Differential Revision: https://phab.mercurial-scm.org/D338
2017-08-10 20:58:28 -07:00
Gregory Szorc
a5f89f74c6 sshpeer: use peer interface
We need the same @property conversion of ui like we did for localpeer.
We renamed _capabilities() to capabilities() to satisfy the new
naming requirement.

However, since we're inheriting from wireproto.wirepeer which inherits
from peer.peerrepository and provides its own code accessing
_capabilities(), we need to keep the old alias around. This wonkiness
will disappear once wirepeer is cleaned up in subsequent commits.

We also implement methods for basepeer that are identical to the
defaults in peer.peerrepository in preparation for the removal of
peerrepository.

Differential Revision: https://phab.mercurial-scm.org/D336
2017-08-06 17:59:48 -07:00
Gregory Szorc
b724074785 sshpeer: make instance attributes and methods internal
Peer types are supposed to conform to a formal interface defined by
peer.peerrepository and wireproto.wirepeer. Every "public" attribute on
*peer types makes it harder to understand what attributes are part
of the interface and what are instance specific.

This commit converts a number of "public" instance attributes and
methods on sshpeer to internal so they can't be confused to be part of
the peer API.

The URL-related instance attributes were introduced in 904c418bea16
in 2005. AFAICT most of them aren't used and could potentially be
removed. But I kept them around anyway.

I also reorded some code to make things slightly easier to read.

.. api::

   Rename attributes on sshpeer to reflect peer API

Differential Revision: https://phab.mercurial-scm.org/D331
2017-08-10 20:55:28 -07:00
Augie Fackler
ef945af30b merge with stable 2017-08-10 18:55:33 -04:00
Jun Wu
a0e5a4defb ssh: quote parameters using shellquote (SEC)
This patch uses shellquote to quote ssh parameters more strictly to avoid
shell injection.
2017-08-04 23:54:12 -07:00
Sean Farley
e199b92002 sshpeer: check for safe ssh url (SEC)
Checking in the sshpeer for a rogue ssh:// urls seems like the right
place to do it (instead of whack-a-mole with pull, clone, push, etc).
2017-08-01 14:40:19 -07:00
Gregory Szorc
59e773f0f6 exchange: drop support for lock-based unbundling (BC)
Locking over the wire protocol and the "addchangegroup" wire
protocol command has been deprecated since f8e443eb02c9, which was
first part of Mercurial 0.9.1.

Support for handling these commands from sshserver was dropped in
93297d5f4df2 in 2015, effectively locking out pre 0.9.1 clients
from new servers.

However, client-side code for calling lock and addchangegroup is
still present in exchange.py and the various peer classes to
facilitate pushing to pre 0.9.1 servers.

The lock-based pushing mechanism is extremely brittle. 0.9.1 was
released in July 2006 and I highly doubt anyone is still running
such an ancient version of Mercurial on a server. I'm about to
refactor the peer API and I don't think it is worth keeping
support for this ancient protocol feature. So, this commit removes
client support for the lock-based pushing mechanism. This means
modern clients will no longer be able to push to pre 0.9.1 servers.

Differential Revision: https://phab.mercurial-scm.org/D264
2017-08-06 17:44:56 -07: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
Pulkit Goyal
0993aa87aa py3: use pycompat.byteskwargs() to convert kwargs' keys to bytes
This is used where ever required like where kwargs are passed into
ui.formatter(), scmutil.match() or cmdutil.openrevlog() which expects bytes.
2017-06-27 00:20:55 +05:30
Augie Fackler
2f7c844e98 sshpeer: try harder to snag stderr when stdout closes unexpectedly
Resolves test failures on FreeBSD, but I'm not happy about the fix.

A previous version of this also wrapped readline by putting the hack
in the _call method on doublepipe. That was confusing for readers and
wasn't necessary - just doing this on read() is sufficient to fix the
bugs I'm observing. We can always come back and do readline later if
needed.
2017-04-13 16:09:40 -04:00
Augie Fackler
fe10e9b912 sshpeer: fix docstring typo 2017-04-13 14:48:18 -04:00
Simon Farnsworth
bdb0a7478b sshpeer: set a blockedtag when starting ssh
So that the data is readable.
2017-03-06 03:25:09 -08:00
Gregory Szorc
7315f10dde wireproto: consolidate code for obtaining "cmds" argument value
Both wireproto.py and sshpeer.py had code for producing the value to
the "cmds" argument used by the "batch" command. This patch extracts
that code to a standalone function and uses it.
2016-08-06 13:46:28 -07:00
Augie Fackler
15bb27e43f sshpeer: use iter(callable, sentinel) instead of while True
This is functionally equivalent, but is a little more concise.
2016-08-05 14:00:22 -04:00
liscju
c7ec9d159e i18n: translate abort messages
I found a few places where message given to abort is
not translated, I don't find any reason to not translate
them.
2016-06-14 11:53:55 +02:00
Augie Fackler
af1601947d wireproto: make iterbatcher behave streamily over http(s)
Unfortunately, the ssh and http implementations are slightly different
due to differences in their _callstream implementations, which
prevents ssh from behaving streamily. We should probably introduce a
new batch command that can stream results over ssh at some point in
the near future.

The streamy behavior of batch over http(s) is an enormous win for
remotefilelog over http: in my testing, it's saving about 40% on file
fetches with a cold cache against a server on localhost.
2016-03-01 18:41:43 -05:00
Bryan O'Sullivan
5ea0fa6c09 sshpeer: make remotelock a context manager 2016-01-15 13:14:50 -08:00
Mads Kiilerich
09567db49a spelling: trivial spell checking 2015-10-17 00:58:46 +02:00
Pierre-Yves David
30913031d4 error: get Abort from 'error' instead of 'util'
The home of 'Abort' is 'error' not 'util' however, a lot of code seems to be
confused about that and gives all the credit to 'util' instead of the
hardworking 'error'. In a spirit of equity, we break the cycle of injustice and
give back to 'error' the respect it deserves. And screw that 'util' poser.

For great justice.
2015-10-08 12:55:45 -07:00
Gregory Szorc
1251142350 sshpeer: use absolute_import 2015-08-08 19:55:01 -07:00
Pierre-Yves David
9e33e7774c sshpeer: also use doublepipe for client to server communication
This will allow even more real time output when the server issue output in the
middle a stream push.
2015-05-20 11:55:59 -05:00
Pierre-Yves David
69155bf9f2 sshpeer: allow doublepipe on unbuffered main pipe
The output pipe does not have manually managed read buffer (actually, no read
anything). To also use the doublepipe for outgoing write (useful to consume
remote output when pushing large set of data) we need the doublepipe to work on
standard pipe too.
2015-06-05 04:54:23 -07:00
Pierre-Yves David
6a2621d0a1 sshpeer: allow write operations through double pipe
We have a shiny toy, lets make it wider.
2015-05-20 10:58:29 -05:00
Pierre-Yves David
25d5d30d03 sshpeer: rename 'size' to 'data' in doublepipe
We are about to add 'write' support, the argument will be either an int or a
string.
2015-05-20 17:40:47 -05:00
Pierre-Yves David
b4290f7a14 sshpeer: use the doublepipe object for the server to client channel
This restores real-time output from ssh server while waiting for protocol data
sent by the server.
2015-05-20 11:41:48 -05:00
Pierre-Yves David
fdc193e836 sshpeer: introduce a "doublepipe" class
This class is responsible for ensuring we still process the server output
streamed through the ssh's 'stderr' pipe during the initial wait for other
protocol streams.

It currently only works on posix system because of its use of 'select.select'.
2015-05-22 10:48:11 -05:00
Pierre-Yves David
e6518023e3 sshpeer: run the ssh command unbuffered
This is necessary to use non-blocking IO base on polling. Such polling is
needed to restore real time output with ssh peer.

Changeset 6a565336bae3 is talking about 5x regression on Mac OS X when playing
with this value. So we introduced our own buffering layer in previous
changesets. This seems to keep the regression away (we are even issuing much
less read).
2015-05-20 11:31:38 -05:00
Pierre-Yves David
de72eff352 sshpeer: use a 'bufferedinputpipe' for standard output of the ssh process
We need this pipe to still be buffered when will switch to unbuffered pipe.
(switch motivated by the need of using polling to restore real time output from
ssh server). This is the only pipe that needs to be wrapped because this is the
one who do extensive usage of 'readline'. The stderr pipe of the process is
alway read in non blocking raw chunk, so it won't benefit from the
buffering.
2015-05-31 00:00:36 -07:00
Pierre-Yves David
c899dea3d5 sshpeer: extract the forward output logic
We are about to make a more aggressive use of this when reading and writing on
the other pipes. We it needs to be reusable.
2015-05-18 23:19:11 -05:00
Pierre-Yves David
b51b0deda6 sshpeer: break "OutOfBandError" feature for ssh (BC)
When we'll be using the ssh's 'stderr' for realtime output successfully, it will
no longer be possible to use 'stderr' to carry the error message (because it
is already consumed by the real time output logic.

This feature is very rarely used (test says largefile only) and I think
breaking its output pattern is worth the benefit of having real time output with
ssh.
2015-05-20 12:33:12 -05:00
Matt Mackall
3406ce4956 merge with stable 2014-12-29 16:39:20 -06:00
Matt Mackall
bf17f56a67 sshpeer: more thorough shell quoting
This fixes an issue spotted by Jesse Hertz.
2014-12-29 14:27:02 -06:00
Yuya Nishihara
ceda6fbba9 util.system: use ui.system() in place of optional ui.fout parameter 2014-11-08 13:06:22 +09:00
Yuya Nishihara
187868d5fe sshpeer: forward stdout of remote "hg init" to appropriate output channel
Otherwise, commandserver channel could be corrupted.
2014-10-14 21:59:39 +09:00
Gregory Szorc
db57d5e9d6 platform: implement readpipe()
Reading all available data from a pipe has a platform-dependent
implementation.

This patch establishes platform.readpipe() by copying the
inline implementation in sshpeer.readerr(). The implementations
for POSIX and Windows are currently identical. The POSIX
implementation will be changed in a subsequent patch.
2014-08-15 20:02:18 -07:00
Pierre-Yves David
1383ee8462 sshpeer: add implementation of _calltwowaystream
This implements the call needed by bundle2. The error handling is a bit flaky
right now, but bundle2 error handling in general is still flaky anyway. Bundle2
is still disabled by default, I do not expect this to be a problem.
2014-04-15 17:18:35 -04:00
Pierre-Yves David
e3dee4d3e4 wireproto: drop the _decompress method in favor a new call type
We already have multiple call function for multiple return type. The
`_decompress` function is only used for http and seems like a layer violation.
We drop it in favor of a new call type dedicated to "stream that may be useful to
compress".
2014-03-28 14:24:13 -07:00
Siddharth Agarwal
1c5bc58f8f sshpeer: only print out 'running ssh' messages in debug mode (BC)
Previously, if another command was run with --verbose, and for whatever reason
that invoked sshpeer, we'd get a 'running ssh' message from sshpeer. This extra
line would interfere with that command's output and cause dumb parsers to
break.

For example, hg annotate can be run with --verbose to get full usernames. This,
combined with the third-party remotefilelog extension which can cause ssh
connections to be created, leads to an extra 'running ssh' line that breaks
most parsers.

This patch is (BC) because hg pull --verbose will no longer print out exactly
what ssh command it is running.

No tests are affected by this change.
2014-03-18 13:40:03 -07:00
Matt Mackall
796c64f268 sshpeer: mark _validrepo internal 2013-07-16 11:18:16 -05:00