Bug 1393242 - Vendor python-hglib 2.4; r=mshal

python-hglib is a Python client for Mercurial's command server. It
facilitates querying Mercurial efficiently (using a single process)
and without having to parse output in the common case.

Let's vendor it so we can make use of it for more advanced Mercurial
scenarios.

Content vendored from changeset 820d7c1e470a without modifications
(other than deleting unwanted files).

As part of vendoring, we add the package to the virtualenv and make
it available to mach.

MozReview-Commit-ID: F4KLbW1lAvk

--HG--
extra : rebase_source : ad81f3aa4843312d300d80ce563d40736af2c16a
This commit is contained in:
Gregory Szorc 2017-08-23 15:06:18 -07:00
parent b2e9cd1780
commit d369594b32
10 changed files with 2330 additions and 0 deletions

View File

@ -9,6 +9,7 @@ mozilla.pth:third_party/python/configobj
mozilla.pth:third_party/python/cram
mozilla.pth:third_party/python/dlmanager
mozilla.pth:third_party/python/futures
mozilla.pth:third_party/python/hglib
mozilla.pth:third_party/python/jsmin
optional:setup.py:third_party/python/psutil:build_ext:--inplace
mozilla.pth:third_party/python/psutil

20
third_party/python/hglib/LICENSE vendored Normal file
View File

@ -0,0 +1,20 @@
Copyright (c) 2011 Matt Mackall and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,40 @@
import subprocess
from hglib import client, util, error
HGPATH = 'hg'
def open(path=None, encoding=None, configs=None):
'''starts a cmdserver for the given path (or for a repository found
in the cwd). HGENCODING is set to the given encoding. configs is a
list of key, value, similar to those passed to hg --config.
'''
return client.hgclient(path, encoding, configs)
def init(dest=None, ssh=None, remotecmd=None, insecure=False,
encoding=None, configs=None):
args = util.cmdbuilder('init', dest, e=ssh, remotecmd=remotecmd,
insecure=insecure)
args.insert(0, HGPATH)
proc = util.popen(args)
out, err = proc.communicate()
if proc.returncode:
raise error.CommandError(args, proc.returncode, out, err)
return client.hgclient(dest, encoding, configs, connect=False)
def clone(source=None, dest=None, noupdate=False, updaterev=None, rev=None,
branch=None, pull=False, uncompressed=False, ssh=None, remotecmd=None,
insecure=False, encoding=None, configs=None):
args = util.cmdbuilder('clone', source, dest, noupdate=noupdate,
updaterev=updaterev, rev=rev, branch=branch,
pull=pull, uncompressed=uncompressed,
e=ssh, remotecmd=remotecmd, insecure=insecure)
args.insert(0, HGPATH)
proc = util.popen(args)
out, err = proc.communicate()
if proc.returncode:
raise error.CommandError(args, proc.returncode, out, err)
return client.hgclient(dest, encoding, configs, connect=False)

1717
third_party/python/hglib/hglib/client.py vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,238 @@
import hglib.client # Circular dependency.
from hglib import util, templates
from hglib.error import CommandError
from hglib.util import b, strtobytes, integertypes
_nullcset = [b('-1'), b('0000000000000000000000000000000000000000'), b(''),
b(''), b(''), b(''), b('')]
class changectx(object):
"""A changecontext object makes access to data related to a particular
changeset convenient."""
def __init__(self, repo, changeid=b('')):
"""changeid is a revision number, node, or tag"""
if changeid == b(''):
changeid = b('.')
self._repo = repo
if isinstance(changeid, hglib.client.revision):
cset = changeid
elif changeid == -1:
cset = _nullcset
else:
if isinstance(changeid, integertypes):
changeid = b('rev(') + strtobytes(changeid) + b(')')
notfound = False
try:
cset = self._repo.log(changeid)
# hg bbf4f3dfd700 gave a null result for tip+1
if (cset and cset[0][1] == _nullcset[1]
and cset[0][0] != _nullcset[0]):
notfound = True
except CommandError:
notfound = True
if notfound or not len(cset):
raise ValueError('changeid %r not found in repo' % changeid)
if len(cset) > 1:
raise ValueError('changeid must yield a single changeset')
cset = cset[0]
self._rev, self._node, self._tags = cset[:3]
self._branch, self._author, self._description, self._date = cset[3:]
self._rev = int(self._rev)
self._tags = self._tags.split()
try:
self._tags.remove(b('tip'))
except ValueError:
pass
self._ignored = None
self._clean = None
def __str__(self):
return self._node[:12].decode('latin-1')
def __int__(self):
return self._rev
def __repr__(self):
return "<changectx %s>" % str(self)
def __hash__(self):
try:
return hash(self._rev)
except AttributeError:
return id(self)
def __eq__(self, other):
try:
return self._rev == other._rev
except AttributeError:
return False
def __ne__(self, other):
return not (self == other)
def __nonzero__(self):
return self._rev != -1
def __bool__(self):
return self.__nonzero__()
def __contains__(self, key):
return key in self._manifest
def __iter__(self):
for f in sorted(self._manifest):
yield f
@util.propertycache
def _status(self):
return self._parsestatus(self._repo.status(change=strtobytes(self)))[:4]
def _parsestatus(self, stat):
d = dict((c, [])
for c in (b('M'), b('A'), b('R'), b('!'), b('?'), b('I'),
b('C'), b(' ')))
for k, path in stat:
d[k].append(path)
return (d[b('M')], d[b('A')], d[b('R')], d[b('!')], d[b('?')],
d[b('I')], d[b('C')])
def status(self, ignored=False, clean=False):
"""Explicit status query
Unless this method is used to query the working copy status, the
_status property will implicitly read the status using its default
arguments."""
stat = self._parsestatus(self._repo.status(change=strtobytes(self),
ignored=ignored,
clean=clean))
self._unknown = self._ignored = self._clean = None
if ignored:
self._ignored = stat[5]
if clean:
self._clean = stat[6]
self._status = stat[:4]
return stat
def rev(self):
return self._rev
def node(self):
return self._node
def tags(self):
return self._tags
def branch(self):
return self._branch
def author(self):
return self._author
def user(self):
return self._author
def date(self):
return self._date
def description(self):
return self._description
def files(self):
return sorted(self._status[0] + self._status[1] + self._status[2])
def modified(self):
return self._status[0]
def added(self):
return self._status[1]
def removed(self):
return self._status[2]
def ignored(self):
if self._ignored is None:
self.status(ignored=True)
return self._ignored
def clean(self):
if self._clean is None:
self.status(clean=True)
return self._clean
@util.propertycache
def _manifest(self):
d = {}
for node, p, e, s, path in self._repo.manifest(rev=strtobytes(self)):
d[path] = node
return d
def manifest(self):
return self._manifest
def hex(self):
return hex(self._node)
@util.propertycache
def _parents(self):
"""return contexts for each parent changeset"""
par = self._repo.parents(rev=strtobytes(self))
if not par:
return [changectx(self._repo, -1)]
return [changectx(self._repo, int(cset.rev)) for cset in par]
def parents(self):
return self._parents
def p1(self):
return self._parents[0]
def p2(self):
if len(self._parents) == 2:
return self._parents[1]
return changectx(self._repo, -1)
@util.propertycache
def _bookmarks(self):
books = [bm for bm in self._repo.bookmarks()[0] if bm[1] == self._rev]
bms = []
for name, r, n in books:
bms.append(name)
return bms
def bookmarks(self):
return self._bookmarks
def hidden(self):
"""return True if the changeset is hidden, else False"""
return bool(self._repo.log(revrange=self._node + b(' and hidden()'),
hidden=True))
def phase(self):
"""return the phase of the changeset (public, draft or secret)"""
return self._repo.phase(strtobytes(self._rev))[0][1]
def children(self):
"""return contexts for each child changeset"""
for c in self._repo.log(b('children(') + self._node + b(')')):
yield changectx(self._repo, c)
def ancestors(self):
for a in self._repo.log(b('ancestors(') + self._node + b(')')):
yield changectx(self._repo, a)
def descendants(self):
for d in self._repo.log(b('descendants(') + self._node + b(')')):
yield changectx(self._repo, d)
def ancestor(self, c2):
"""
return the ancestor context of self and c2
"""
return changectx(self._repo,
b('ancestor(') + self + b(', ') + c2 + b(')'))

18
third_party/python/hglib/hglib/error.py vendored Normal file
View File

@ -0,0 +1,18 @@
class CommandError(Exception):
def __init__(self, args, ret, out, err):
self.args = args
self.ret = ret
self.out = out
self.err = err
def __str__(self):
return str((self.ret, self.out.rstrip(), self.err.rstrip()))
class ServerError(Exception):
pass
class ResponseError(ServerError, ValueError):
pass
class CapabilityError(ServerError):
pass

21
third_party/python/hglib/hglib/merge.py vendored Normal file
View File

@ -0,0 +1,21 @@
from hglib.util import b
class handlers(object):
"""
These can be used as the cb argument to hgclient.merge() to control the
behaviour when Mercurial prompts what to do with regard to a specific file,
e.g. when one parent modified a file and the other removed it.
"""
@staticmethod
def abort(size, output):
"""
Abort the merge if a prompt appears.
"""
return b('')
"""
This corresponds to Mercurial's -y/--noninteractive global option, which
picks the first choice on all prompts.
"""
noninteractive = 'yes'

View File

@ -0,0 +1,4 @@
from hglib.util import b
changeset = b('{rev}\\0{node}\\0{tags}\\0{branch}\\0{author}'
'\\0{desc}\\0{date}\\0')

217
third_party/python/hglib/hglib/util.py vendored Normal file
View File

@ -0,0 +1,217 @@
import os, subprocess, sys
from hglib import error
try:
from io import BytesIO
except ImportError:
from cStringIO import StringIO as BytesIO
if sys.version_info[0] > 2:
izip = zip
integertypes = (int,)
def b(s):
"""Encode the string as bytes."""
return s.encode('latin-1')
else:
from itertools import izip
integertypes = (long, int)
bytes = str # Defined in Python 2.6/2.7, but to the same value.
def b(s):
"""Encode the string as bytes."""
return s
def strtobytes(s):
"""Return the bytes of the string representation of an object."""
return str(s).encode('latin-1')
def grouper(n, iterable):
''' list(grouper(2, range(4))) -> [(0, 1), (2, 3)] '''
args = [iter(iterable)] * n
return izip(*args)
def eatlines(s, n):
"""
>>> eatlines(b("1\\n2"), 1) == b('2')
True
>>> eatlines(b("1\\n2"), 2) == b('')
True
>>> eatlines(b("1\\n2"), 3) == b('')
True
>>> eatlines(b("1\\n2\\n3"), 1) == b('2\\n3')
True
"""
cs = BytesIO(s)
for line in cs:
n -= 1
if n == 0:
return cs.read()
return b('')
def skiplines(s, prefix):
"""
Skip lines starting with prefix in s
>>> skiplines(b('a\\nb\\na\\n'), b('a')) == b('b\\na\\n')
True
>>> skiplines(b('a\\na\\n'), b('a')) == b('')
True
>>> skiplines(b(''), b('a')) == b('')
True
>>> skiplines(b('a\\nb'), b('b')) == b('a\\nb')
True
"""
cs = BytesIO(s)
for line in cs:
if not line.startswith(prefix):
return line + cs.read()
return b('')
def _cmdval(val):
if isinstance(val, bytes):
return val
else:
return strtobytes(val)
def cmdbuilder(name, *args, **kwargs):
"""
A helper for building the command arguments
args are the positional arguments
kwargs are the options
keys that are single lettered are prepended with '-', others with '--',
underscores are replaced with dashes
keys with False boolean values are ignored, lists add the key multiple times
None arguments are skipped
>>> cmdbuilder(b('cmd'), a=True, b=False, c=None) == [b('cmd'), b('-a')]
True
>>> cmdbuilder(b('cmd'), long=True) == [b('cmd'), b('--long')]
True
>>> cmdbuilder(b('cmd'), str=b('s')) == [b('cmd'), b('--str'), b('s')]
True
>>> cmdbuilder(b('cmd'), d_ash=True) == [b('cmd'), b('--d-ash')]
True
>>> cmdbuilder(b('cmd'), _=True) == [b('cmd'), b('-')]
True
>>> expect = [b('cmd'), b('--list'), b('1'), b('--list'), b('2')]
>>> cmdbuilder(b('cmd'), list=[1, 2]) == expect
True
>>> cmdbuilder(b('cmd'), None) == [b('cmd')]
True
"""
cmd = [name]
for arg, val in kwargs.items():
if val is None:
continue
arg = arg.encode('latin-1').replace(b('_'), b('-'))
if arg != b('-'):
if len(arg) == 1:
arg = b('-') + arg
else:
arg = b('--') + arg
if isinstance(val, bool):
if val:
cmd.append(arg)
elif isinstance(val, list):
for v in val:
cmd.append(arg)
cmd.append(_cmdval(v))
else:
cmd.append(arg)
cmd.append(_cmdval(val))
for a in args:
if a is not None:
cmd.append(a)
return cmd
class reterrorhandler(object):
"""This class is meant to be used with rawcommand() error handler
argument. It remembers the return value the command returned if
it's one of allowed values, which is only 1 if none are given.
Otherwise it raises a CommandError.
>>> e = reterrorhandler('')
>>> bool(e)
True
>>> e(1, 'a', '')
'a'
>>> bool(e)
False
"""
def __init__(self, args, allowed=None):
self.args = args
self.ret = 0
if allowed is None:
self.allowed = [1]
else:
self.allowed = allowed
def __call__(self, ret, out, err):
self.ret = ret
if ret not in self.allowed:
raise error.CommandError(self.args, ret, out, err)
return out
def __nonzero__(self):
""" Returns True if the return code was 0, False otherwise """
return self.ret == 0
def __bool__(self):
return self.__nonzero__()
class propertycache(object):
"""
Decorator that remembers the return value of a function call.
>>> execcount = 0
>>> class obj(object):
... def func(self):
... global execcount
... execcount += 1
... return []
... func = propertycache(func)
>>> o = obj()
>>> o.func
[]
>>> execcount
1
>>> o.func
[]
>>> execcount
1
"""
def __init__(self, func):
self.func = func
self.name = func.__name__
def __get__(self, obj, type=None):
result = self.func(obj)
setattr(obj, self.name, result)
return result
close_fds = os.name == 'posix'
startupinfo = None
if os.name == 'nt':
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
def popen(args, env=None):
environ = None
if env:
environ = dict(os.environ)
environ.update(env)
return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, close_fds=close_fds,
startupinfo=startupinfo, env=environ)

54
third_party/python/hglib/setup.py vendored Normal file
View File

@ -0,0 +1,54 @@
import os, time
from distutils.core import setup
# query Mercurial for version number, or pull from PKG-INFO
version = 'unknown'
if os.path.isdir('.hg'):
cmd = "hg id -i -t"
l = os.popen(cmd).read().split()
while len(l) > 1 and l[-1][0].isalpha(): # remove non-numbered tags
l.pop()
if len(l) > 1: # tag found
version = l[-1]
if l[0].endswith('+'): # propagate the dirty status to the tag
version += '+'
elif len(l) == 1: # no tag found
cmd = 'hg parents --template "{latesttag}+{latesttagdistance}-"'
version = os.popen(cmd).read() + l[0]
if version.endswith('+'):
version += time.strftime('%Y%m%d')
elif os.path.exists('.hg_archival.txt'):
kw = dict([[t.strip() for t in l.split(':', 1)]
for l in open('.hg_archival.txt')])
if 'tag' in kw:
version = kw['tag']
elif 'latesttag' in kw:
version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
else:
version = kw.get('node', '')[:12]
elif os.path.exists('PKG-INFO'):
kw = dict([[t.strip() for t in l.split(':', 1)]
for l in open('PKG-INFO') if ':' in l])
version = kw.get('Version', version)
setup(
name='python-hglib',
version=version,
author='Idan Kamara',
author_email='idankk86@gmail.com',
url='http://selenic.com/repo/python-hglib',
description='Mercurial Python library',
long_description=open(os.path.join(os.path.dirname(__file__),
'README')).read(),
classifiers=[
'Programming Language :: Python',
'Programming Language :: Python :: 2.4',
'Programming Language :: Python :: 2.5',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
],
license='MIT',
packages=['hglib'])