summaryrefslogtreecommitdiffstats
path: root/third_party/python/python-hglib
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/python/python-hglib')
-rw-r--r--third_party/python/python-hglib/LICENSE20
-rw-r--r--third_party/python/python-hglib/Makefile17
-rw-r--r--third_party/python/python-hglib/PKG-INFO26
-rw-r--r--third_party/python/python-hglib/README9
-rw-r--r--third_party/python/python-hglib/examples/stats.py35
-rw-r--r--third_party/python/python-hglib/hglib/__init__.py40
-rw-r--r--third_party/python/python-hglib/hglib/client.py1717
-rw-r--r--third_party/python/python-hglib/hglib/context.py238
-rw-r--r--third_party/python/python-hglib/hglib/error.py18
-rw-r--r--third_party/python/python-hglib/hglib/merge.py21
-rw-r--r--third_party/python/python-hglib/hglib/templates.py4
-rw-r--r--third_party/python/python-hglib/hglib/util.py217
-rw-r--r--third_party/python/python-hglib/setup.py54
-rw-r--r--third_party/python/python-hglib/test.py7
14 files changed, 2423 insertions, 0 deletions
diff --git a/third_party/python/python-hglib/LICENSE b/third_party/python/python-hglib/LICENSE
new file mode 100644
index 0000000000..25d01ceb87
--- /dev/null
+++ b/third_party/python/python-hglib/LICENSE
@@ -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. \ No newline at end of file
diff --git a/third_party/python/python-hglib/Makefile b/third_party/python/python-hglib/Makefile
new file mode 100644
index 0000000000..ad26093755
--- /dev/null
+++ b/third_party/python/python-hglib/Makefile
@@ -0,0 +1,17 @@
+PYTHON=python
+help:
+ @echo 'Commonly used make targets:'
+ @echo ' tests - run all tests in the automatic test suite'
+
+all: help
+
+.PHONY: tests
+
+MANIFEST.in:
+ hg manifest | sed -e 's/^/include /' > MANIFEST.in
+
+dist: MANIFEST.in
+ TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py -q sdist
+
+tests:
+ $(PYTHON) test.py --with-doctest
diff --git a/third_party/python/python-hglib/PKG-INFO b/third_party/python/python-hglib/PKG-INFO
new file mode 100644
index 0000000000..e32cabb04d
--- /dev/null
+++ b/third_party/python/python-hglib/PKG-INFO
@@ -0,0 +1,26 @@
+Metadata-Version: 1.1
+Name: python-hglib
+Version: 2.4
+Summary: Mercurial Python library
+Home-page: http://selenic.com/repo/python-hglib
+Author: Idan Kamara
+Author-email: idankk86@gmail.com
+License: MIT
+Description: python-hglib
+ ============
+
+ python-hglib is a library with a fast, convenient interface to Mercurial.
+ It uses Mercurial's command server for communication with hg.
+
+ Installation is standard:
+
+ $ python setup.py install
+
+Platform: UNKNOWN
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.4
+Classifier: Programming Language :: Python :: 2.5
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.4
diff --git a/third_party/python/python-hglib/README b/third_party/python/python-hglib/README
new file mode 100644
index 0000000000..01c45e7a3b
--- /dev/null
+++ b/third_party/python/python-hglib/README
@@ -0,0 +1,9 @@
+python-hglib
+============
+
+python-hglib is a library with a fast, convenient interface to Mercurial.
+It uses Mercurial's command server for communication with hg.
+
+Installation is standard:
+
+ $ python setup.py install
diff --git a/third_party/python/python-hglib/examples/stats.py b/third_party/python/python-hglib/examples/stats.py
new file mode 100644
index 0000000000..f54a59236e
--- /dev/null
+++ b/third_party/python/python-hglib/examples/stats.py
@@ -0,0 +1,35 @@
+# stats - get stats on the given repo
+
+import sys
+import hglib
+
+# figure out what repo path to use
+repo = '.'
+if len(sys.argv) > 1:
+ repo = sys.argv[1]
+
+# connect to hg
+client = hglib.open(repo)
+
+# gather some stats
+revs = int(client.tip().rev)
+files = len(list(client.manifest()))
+heads = len(client.heads())
+branches = len(client.branches())
+tags = len(client.tags()) - 1 # don't count tip
+
+authors = {}
+for e in client.log():
+ authors[e.author] = True
+
+merges = 0
+for e in client.log(onlymerges=True):
+ merges += 1
+
+print "%d revisions" % revs
+print "%d merges" % merges
+print "%d files" % files
+print "%d heads" % heads
+print "%d branches" % branches
+print "%d tags" % tags
+print "%d authors" % len(authors)
diff --git a/third_party/python/python-hglib/hglib/__init__.py b/third_party/python/python-hglib/hglib/__init__.py
new file mode 100644
index 0000000000..a522d33382
--- /dev/null
+++ b/third_party/python/python-hglib/hglib/__init__.py
@@ -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)
diff --git a/third_party/python/python-hglib/hglib/client.py b/third_party/python/python-hglib/hglib/client.py
new file mode 100644
index 0000000000..4eababdf40
--- /dev/null
+++ b/third_party/python/python-hglib/hglib/client.py
@@ -0,0 +1,1717 @@
+import struct, re, datetime
+import hglib
+from hglib import error, util, templates, merge, context
+
+from hglib.util import b, cmdbuilder, BytesIO, strtobytes
+
+class revision(tuple):
+ def __new__(cls, rev, node, tags, branch, author, desc, date):
+ return tuple.__new__(cls, (rev, node, tags, branch, author, desc, date))
+
+ @property
+ def rev(self):
+ return self[0]
+
+ @property
+ def node(self):
+ return self[1]
+
+ @property
+ def tags(self):
+ return self[2]
+
+ @property
+ def branch(self):
+ return self[3]
+
+ @property
+ def author(self):
+ return self[4]
+
+ @property
+ def desc(self):
+ return self[5]
+
+ @property
+ def date(self):
+ return self[6]
+
+class hgclient(object):
+ inputfmt = '>I'
+ outputfmt = '>cI'
+ outputfmtsize = struct.calcsize(outputfmt)
+ retfmt = '>i'
+
+ def __init__(self, path, encoding, configs, connect=True):
+ self._args = [hglib.HGPATH, 'serve', '--cmdserver', 'pipe',
+ '--config', 'ui.interactive=True']
+ if path:
+ self._args += ['-R', path]
+ if configs:
+ for config in configs:
+ self._args += ['--config', config]
+ self._env = {'HGPLAIN': '1'}
+ if encoding:
+ self._env['HGENCODING'] = encoding
+
+ self.server = None
+ self._version = None
+ # include the hidden changesets if True
+ self.hidden = None
+
+ self._cbout = None
+ self._cberr = None
+ self._cbprompt = None
+
+ if connect:
+ self.open()
+
+ self._protocoltracefn = None
+
+ def setcbout(self, cbout):
+ """
+ cbout is a function that will be called with the stdout data of
+ the command as it runs. Call with None to stop getting call backs.
+ """
+ self._cbout = cbout
+
+ def setcberr(self, cberr):
+ """
+ cberr is a function that will be called with the stderr data of
+ the command as it runs.Call with None to stop getting call backs.
+ """
+ self._cberr = cberr
+
+ def setcbprompt(self, cbprompt):
+ """
+ cbprompt is used to reply to prompts by the server
+ It receives the max number of bytes to return and the
+ contents of stdout received so far.
+
+ Call with None to stop getting call backs.
+
+ cbprompt is never called from merge() or import_()
+ which already handle the prompt.
+ """
+ self._cbprompt = cbprompt
+
+ def setprotocoltrace(self, tracefn=None):
+ """
+ if tracefn is None no trace calls will be made.
+ Otherwise tracefn is call as tracefn( direction, channel, data )
+ direction is 'r' for read from server and 'w' for write to server
+ channel is always None when direction is 'w'
+ and the channel-identified when the direction is 'r'
+ """
+ self._protocoltracefn = tracefn
+
+ def __enter__(self):
+ if self.server is None:
+ self.open()
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.close()
+
+ def _readhello(self):
+ """ read the hello message the server sends when started """
+ ch, msg = self._readchannel()
+ assert ch == b('o')
+
+ msg = msg.split(b('\n'))
+
+ self.capabilities = msg[0][len(b('capabilities: ')):]
+ if not self.capabilities:
+ raise error.ResponseError(
+ "bad hello message: expected 'capabilities: '"
+ ", got %r" % msg[0])
+
+ self.capabilities = set(self.capabilities.split())
+
+ # at the very least the server should be able to run commands
+ assert b('runcommand') in self.capabilities
+
+ self._encoding = msg[1][len(b('encoding: ')):]
+ if not self._encoding:
+ raise error.ResponseError("bad hello message: expected 'encoding: '"
+ ", got %r" % msg[1])
+
+ def _readchannel(self):
+ data = self.server.stdout.read(hgclient.outputfmtsize)
+ if not data:
+ raise error.ServerError()
+ channel, length = struct.unpack(hgclient.outputfmt, data)
+ if channel in b('IL'):
+ return channel, length
+ else:
+ return channel, self.server.stdout.read(length)
+
+ @staticmethod
+ def _parserevs(splitted):
+ '''splitted is a list of fields according to our rev.style, where
+ each 6 fields compose one revision.
+ '''
+ revs = []
+ for rev in util.grouper(7, splitted):
+ # truncate the timezone and convert to a local datetime
+ posixtime = float(rev[6].split(b('.'), 1)[0])
+ dt = datetime.datetime.fromtimestamp(posixtime)
+ revs.append(revision(rev[0], rev[1], rev[2], rev[3],
+ rev[4], rev[5], dt))
+ return revs
+
+ def runcommand(self, args, inchannels, outchannels):
+ def writeblock(data):
+ if self._protocoltracefn is not None:
+ self._protocoltracefn('w', None, data)
+ self.server.stdin.write(struct.pack(self.inputfmt, len(data)))
+ self.server.stdin.write(data)
+ self.server.stdin.flush()
+
+ if not self.server:
+ raise ValueError("server not connected")
+
+ self.server.stdin.write(b('runcommand\n'))
+ writeblock(b('\0').join(args))
+
+ while True:
+ channel, data = self._readchannel()
+ if self._protocoltracefn is not None:
+ self._protocoltracefn('r', channel, data)
+
+ # input channels
+ if channel in inchannels:
+ writeblock(inchannels[channel](data))
+ # output channels
+ elif channel in outchannels:
+ outchannels[channel](data)
+ # result channel, command finished
+ elif channel == b('r'):
+ return struct.unpack(hgclient.retfmt, data)[0]
+ # a channel that we don't know and can't ignore
+ elif channel.isupper():
+ raise error.ResponseError(
+ "unexpected data on required channel '%s'" % channel)
+ # optional channel
+ else:
+ pass
+
+ def rawcommand(self, args, eh=None, prompt=None, input=None):
+ """
+ args is the cmdline (usually built using util.cmdbuilder)
+
+ eh is an error handler that is passed the return code, stdout and stderr
+ If no eh is given, we raise a CommandError if ret != 0
+
+ prompt is used to reply to prompts by the server
+ It receives the max number of bytes to return and the contents of stdout
+ received so far
+
+ input is used to reply to bulk data requests by the server
+ It receives the max number of bytes to return
+ """
+ out, err = BytesIO(), BytesIO()
+ outchannels = {}
+ if self._cbout is None:
+ outchannels[b('o')] = out.write
+ else:
+ def out_handler(data):
+ out.write(data)
+ self._cbout(data)
+ outchannels[b('o')] = out_handler
+ if self._cberr is None:
+ outchannels[b('e')] = err.write
+ else:
+ def err_handler(data):
+ err.write(data)
+ self._cberr(data)
+ outchannels[b('e')] = err_handler
+
+ inchannels = {}
+ if prompt is None:
+ prompt = self._cbprompt
+ if prompt is not None:
+ def func(size):
+ reply = prompt(size, out.getvalue())
+ return reply
+ inchannels[b('L')] = func
+ if input is not None:
+ inchannels[b('I')] = input
+
+ ret = self.runcommand(args, inchannels, outchannels)
+ out, err = out.getvalue(), err.getvalue()
+
+ if ret:
+ if eh is None:
+ raise error.CommandError(args, ret, out, err)
+ else:
+ return eh(ret, out, err)
+ return out
+
+ def open(self):
+ if self.server is not None:
+ raise ValueError('server already open')
+
+ self.server = util.popen(self._args, self._env)
+ try:
+ self._readhello()
+ except error.ServerError:
+ ret, serr = self._close()
+ raise error.ServerError('server exited with status %d: %s'
+ % (ret, serr.strip()))
+ return self
+
+ def close(self):
+ """Closes the command server instance and waits for it to exit,
+ returns the exit code.
+
+ Attempting to call any function afterwards that needs to
+ communicate with the server will raise a ValueError.
+ """
+ return self._close()[0]
+
+ def _close(self):
+ _sout, serr = self.server.communicate()
+ ret = self.server.returncode
+ self.server = None
+ return ret, serr
+
+ def add(self, files=[], dryrun=False, subrepos=False, include=None,
+ exclude=None):
+ """
+ Add the specified files on the next commit.
+ If no files are given, add all files to the repository.
+
+ dryrun - do no perform actions
+ subrepos - recurse into subrepositories
+ include - include names matching the given patterns
+ exclude - exclude names matching the given patterns
+
+ Return whether all given files were added.
+ """
+ if not isinstance(files, list):
+ files = [files]
+
+ args = cmdbuilder(b('add'), n=dryrun, S=subrepos, I=include, X=exclude,
+ *files)
+
+ eh = util.reterrorhandler(args)
+ self.rawcommand(args, eh=eh)
+
+ return bool(eh)
+
+ def addremove(self, files=[], similarity=None, dryrun=False, include=None,
+ exclude=None):
+ """Add all new files and remove all missing files from the repository.
+
+ New files are ignored if they match any of the patterns in
+ ".hgignore". As with add, these changes take effect at the
+ next commit.
+
+ similarity - used to detect renamed files. With a parameter
+ greater than 0, this compares every removed file with every
+ added file and records those similar enough as renames. This
+ option takes a percentage between 0 (disabled) and 100 (files
+ must be identical) as its parameter. Detecting renamed files
+ this way can be expensive. After using this option, "hg status
+ -C" can be used to check which files were identified as moved
+ or renamed.
+
+ dryrun - do no perform actions
+ include - include names matching the given patterns
+ exclude - exclude names matching the given patterns
+
+ Return True if all files are successfully added.
+
+ """
+ if not isinstance(files, list):
+ files = [files]
+
+ args = cmdbuilder(b('addremove'), s=similarity, n=dryrun, I=include,
+ X=exclude, *files)
+
+ eh = util.reterrorhandler(args)
+ self.rawcommand(args, eh=eh)
+
+ return bool(eh)
+
+ def annotate(self, files, rev=None, nofollow=False, text=False, user=False,
+ file=False, date=False, number=False, changeset=False,
+ line=False, verbose=False, include=None, exclude=None):
+ """
+ Show changeset information by line for each file in files.
+
+ rev - annotate the specified revision
+ nofollow - don't follow copies and renames
+ text - treat all files as text
+ user - list the author (long with -v)
+ file - list the filename
+ date - list the date
+ number - list the revision number (default)
+ changeset - list the changeset
+ line - show line number at the first appearance
+ include - include names matching the given patterns
+ exclude - exclude names matching the given patterns
+
+ Yields a (info, contents) tuple for each line in a file. Info is a space
+ separated string according to the given options.
+ """
+ if not isinstance(files, list):
+ files = [files]
+
+ args = cmdbuilder(b('annotate'), r=rev, no_follow=nofollow, a=text,
+ u=user, f=file, d=date, n=number, c=changeset,
+ l=line, v=verbose, I=include, X=exclude,
+ hidden=self.hidden, *files)
+
+ out = self.rawcommand(args)
+
+ for line in out.splitlines():
+ yield tuple(line.split(b(': '), 1))
+
+ def archive(self, dest, rev=None, nodecode=False, prefix=None, type=None,
+ subrepos=False, include=None, exclude=None):
+ """Create an unversioned archive of a repository revision.
+
+ The exact name of the destination archive or directory is given using a
+ format string; see export for details.
+
+ Each member added to an archive file has a directory prefix
+ prepended. Use prefix to specify a format string for the
+ prefix. The default is the basename of the archive, with
+ suffixes removed.
+
+ dest - destination path
+ rev - revision to distribute. The revision used is the parent of the
+ working directory if one isn't given.
+
+ nodecode - do not pass files through decoders
+ prefix - directory prefix for files in archive
+ type - type of distribution to create. The archive type is automatically
+ detected based on file extension if one isn't given.
+
+ Valid types are:
+
+ "files" a directory full of files (default)
+ "tar" tar archive, uncompressed
+ "tbz2" tar archive, compressed using bzip2
+ "tgz" tar archive, compressed using gzip
+ "uzip" zip archive, uncompressed
+ "zip" zip archive, compressed using deflate
+
+ subrepos - recurse into subrepositories
+ include - include names matching the given patterns
+ exclude - exclude names matching the given patterns
+
+ """
+ args = cmdbuilder(b('archive'), dest, r=rev,
+ no_decode=nodecode, p=prefix,
+ t=type, S=subrepos, I=include, X=exclude,
+ hidden=self.hidden)
+
+ self.rawcommand(args)
+
+ def backout(self, rev, merge=False, parent=None, tool=None, message=None,
+ logfile=None, date=None, user=None):
+ """Prepare a new changeset with the effect of rev undone in the current
+ working directory.
+
+ If rev is the parent of the working directory, then this new
+ changeset is committed automatically. Otherwise, hg needs to
+ merge the changes and the merged result is left uncommitted.
+
+ rev - revision to backout
+ merge - merge with old dirstate parent after backout
+ parent - parent to choose when backing out merge
+ tool - specify merge tool
+ message - use text as commit message
+ logfile - read commit message from file
+ date - record the specified date as commit date
+ user - record the specified user as committer
+
+ """
+ if message and logfile:
+ raise ValueError("cannot specify both a message and a logfile")
+
+ args = cmdbuilder(b('backout'), r=rev, merge=merge, parent=parent,
+ t=tool, m=message, l=logfile, d=date, u=user,
+ hidden=self.hidden)
+
+ self.rawcommand(args)
+
+ def bookmark(self, name, rev=None, force=False, delete=False,
+ inactive=False, rename=None):
+ """
+ Set a bookmark on the working directory's parent revision or rev,
+ with the given name.
+
+ name - bookmark name
+ rev - revision to bookmark
+ force - bookmark even if another bookmark with the same name exists
+ delete - delete the given bookmark
+ inactive - do not mark the new bookmark active
+ rename - rename the bookmark given by rename to name
+ """
+ args = cmdbuilder(b('bookmark'), name, r=rev, f=force, d=delete,
+ i=inactive, m=rename)
+
+ self.rawcommand(args)
+
+ def bookmarks(self):
+ """
+ Return the bookmarks as a list of (name, rev, node) and the index of the
+ current one.
+
+ If there isn't a current one, -1 is returned as the index.
+ """
+ args = cmdbuilder(b('bookmarks'), hidden=self.hidden)
+ out = self.rawcommand(args)
+
+ bms = []
+ current = -1
+ if out.rstrip() != b('no bookmarks set'):
+ for line in out.splitlines():
+ iscurrent, line = line[0:3], line[3:]
+ if b('*') in iscurrent:
+ current = len(bms)
+ name, line = line.split(b(' '), 1)
+ rev, node = line.split(b(':'))
+ bms.append((name, int(rev), node))
+ return bms, current
+
+ def branch(self, name=None, clean=False, force=False):
+ """When name isn't given, return the current branch name. Otherwise
+ set the working directory branch name (the branch will not
+ exist in the repository until the next commit). Standard
+ practice recommends that primary development take place on the
+ 'default' branch.
+
+ When clean is True, reset and return the working directory
+ branch to that of the parent of the working directory,
+ negating a previous branch change.
+
+ name - new branch name
+ clean - reset branch name to parent branch name
+ force - set branch name even if it shadows an existing branch
+
+ """
+ if name and clean:
+ raise ValueError('cannot use both name and clean')
+
+ args = cmdbuilder(b('branch'), name, f=force, C=clean)
+ out = self.rawcommand(args).rstrip()
+
+ if name:
+ return name
+ elif not clean:
+ return out
+ else:
+ # len('reset working directory to branch ') == 34
+ return out[34:]
+
+ def branches(self, active=False, closed=False):
+ """
+ Returns the repository's named branches as a list of (name, rev, node).
+
+ active - show only branches that have unmerged heads
+ closed - show normal and closed branches
+ """
+ args = cmdbuilder(b('branches'), a=active, c=closed, hidden=self.hidden)
+ out = self.rawcommand(args)
+
+ branches = []
+ for line in out.rstrip().splitlines():
+ namerev, node = line.rsplit(b(':'), 1)
+ name, rev = namerev.rsplit(b(' '), 1)
+ name = name.rstrip()
+ node = node.split()[0] # get rid of ' (inactive)'
+ branches.append((name, int(rev), node))
+ return branches
+
+ def bundle(self, file, destrepo=None, rev=[], branch=[], base=[], all=False,
+ force=False, type=None, ssh=None, remotecmd=None,
+ insecure=False):
+ """Generate a compressed changegroup file collecting changesets not
+ known to be in another repository.
+
+ If destrepo isn't given, then hg assumes the destination will have all
+ the nodes you specify with base. To create a bundle containing all
+ changesets, use all (or set base to 'null').
+
+ file - destination file name
+ destrepo - repository to look for changes
+ rev - a changeset intended to be added to the destination
+ branch - a specific branch you would like to bundle
+ base - a base changeset assumed to be available at the destination
+ all - bundle all changesets in the repository
+ type - bundle compression type to use, available compression
+ methods are: none, bzip2, and gzip (default: bzip2)
+
+ force - run even when the destrepo is unrelated
+ ssh - specify ssh command to use
+ remotecmd - specify hg command to run on the remote side
+ insecure - do not verify server certificate (ignoring
+ web.cacerts config)
+
+ Return True if a bundle was created, False if no changes were found.
+
+ """
+ args = cmdbuilder(b('bundle'), file, destrepo, f=force, r=rev, b=branch,
+ base=base, a=all, t=type, e=ssh, remotecmd=remotecmd,
+ insecure=insecure, hidden=self.hidden)
+
+ eh = util.reterrorhandler(args)
+ self.rawcommand(args, eh=eh)
+
+ return bool(eh)
+
+ def cat(self, files, rev=None, output=None):
+ """Return a string containing the specified files as they were at the
+ given revision. If no revision is given, the parent of the working
+ directory is used, or tip if no revision is checked out.
+
+ If output is given, writes the contents to the specified file.
+ The name of the file is given using a format string. The
+ formatting rules are the same as for the export command, with
+ the following additions:
+
+ "%s" basename of file being printed
+ "%d" dirname of file being printed, or '.' if in repository root
+ "%p" root-relative path name of file being printed
+
+ """
+ args = cmdbuilder(b('cat'), r=rev, o=output, hidden=self.hidden, *files)
+ out = self.rawcommand(args)
+
+ if not output:
+ return out
+
+ def clone(self, source=b('.'), dest=None, branch=None, updaterev=None,
+ revrange=None):
+ """
+ Create a copy of an existing repository specified by source in a new
+ directory dest.
+
+ If dest isn't specified, it defaults to the basename of source.
+
+ branch - clone only the specified branch
+ updaterev - revision, tag or branch to check out
+ revrange - include the specified changeset
+ """
+ args = cmdbuilder(b('clone'), source, dest, b=branch,
+ u=updaterev, r=revrange)
+ self.rawcommand(args)
+
+ def init(self, dest, ssh=None, remotecmd=None, insecure=False):
+ args = util.cmdbuilder('init', dest, e=ssh, remotecmd=remotecmd,
+ insecure=insecure)
+ self.rawcommand(args)
+
+ def commit(self, message=None, logfile=None, addremove=False,
+ closebranch=False, date=None, user=None, include=None,
+ exclude=None, amend=False):
+ """
+ Commit changes reported by status into the repository.
+
+ message - the commit message
+ logfile - read commit message from file
+ addremove - mark new/missing files as added/removed before committing
+ closebranch - mark a branch as closed, hiding it from the branch list
+ date - record the specified date as commit date
+ user - record the specified user as committer
+ include - include names matching the given patterns
+ exclude - exclude names matching the given patterns
+ amend - amend the parent of the working dir
+ """
+ if amend and message is None and logfile is None:
+ # retrieve current commit message
+ message = self.log(b('.'))[0][5]
+ if message is None and logfile is None and not amend:
+ raise ValueError("must provide at least a message or a logfile")
+ elif message and logfile:
+ raise ValueError("cannot specify both a message and a logfile")
+
+ # --debug will print the committed cset
+ args = cmdbuilder(b('commit'), debug=True, m=message, A=addremove,
+ close_branch=closebranch, d=date, u=user, l=logfile,
+ I=include, X=exclude, amend=amend)
+ out = self.rawcommand(args)
+ m = re.search(b(r'^committed changeset (\d+):([0-9a-f]+)'), out,
+ re.MULTILINE)
+ if not m:
+ raise ValueError('revision and node not found in hg output: %r'
+ % out)
+ rev, node = m.groups()
+ return int(rev), node
+
+ def config(self, names=[], untrusted=False, showsource=False):
+ """Return a list of (section, key, value) config settings from all
+ hgrc files
+
+ When showsource is specified, return (source, section, key, value) where
+ source is of the form filename:[line]
+
+ """
+ def splitline(s):
+ k, value = s.rstrip().split(b('='), 1)
+ section, key = k.split(b('.'), 1)
+ return section, key, value
+
+ if not isinstance(names, list):
+ names = [names]
+
+ args = cmdbuilder(b('showconfig'), u=untrusted, debug=showsource,
+ *names)
+ out = self.rawcommand(args)
+
+ conf = []
+ if showsource:
+ out = util.skiplines(out, b('read config from: '))
+ for line in out.splitlines():
+ m = re.match(b(r"(.+?:(?:\d+:)?) (.*)"), line)
+ t = splitline(m.group(2))
+ conf.append((m.group(1)[:-1], t[0], t[1], t[2]))
+ else:
+ for line in out.splitlines():
+ conf.append(splitline(line))
+
+ return conf
+
+ @property
+ def encoding(self):
+ """
+ Return the server's encoding (as reported in the hello message).
+ """
+ if not b('getencoding') in self.capabilities:
+ raise CapabilityError('getencoding')
+
+ if not self._encoding:
+ self.server.stdin.write(b('getencoding\n'))
+ self._encoding = self._readfromchannel('r')
+
+ return self._encoding
+
+ def copy(self, source, dest, after=False, force=False, dryrun=False,
+ include=None, exclude=None):
+ """Mark dest as having copies of source files. If dest is a
+ directory, copies are put in that directory. If dest is a
+ file, then source must be a string.
+
+ Returns True on success, False if errors are encountered.
+
+ source - a file or a list of files
+ dest - a destination file or directory
+ after - record a copy that has already occurred
+ force - forcibly copy over an existing managed file
+ dryrun - do not perform actions, just print output
+ include - include names matching the given patterns
+ exclude - exclude names matching the given patterns
+
+ """
+ if not isinstance(source, list):
+ source = [source]
+
+ source.append(dest)
+ args = cmdbuilder(b('copy'), A=after, f=force, n=dryrun,
+ I=include, X=exclude, *source)
+
+ eh = util.reterrorhandler(args)
+ self.rawcommand(args, eh=eh)
+
+ return bool(eh)
+
+ def diff(self, files=[], revs=[], change=None, text=False,
+ git=False, nodates=False, showfunction=False,
+ reverse=False, ignoreallspace=False,
+ ignorespacechange=False, ignoreblanklines=False,
+ unified=None, stat=False, subrepos=False, include=None,
+ exclude=None):
+ """
+ Return differences between revisions for the specified files.
+
+ revs - a revision or a list of two revisions to diff
+ change - change made by revision
+ text - treat all files as text
+ git - use git extended diff format
+ nodates - omit dates from diff headers
+ showfunction - show which function each change is in
+ reverse - produce a diff that undoes the changes
+ ignoreallspace - ignore white space when comparing lines
+ ignorespacechange - ignore changes in the amount of white space
+ ignoreblanklines - ignore changes whose lines are all blank
+ unified - number of lines of context to show
+ stat - output diffstat-style summary of changes
+ subrepos - recurse into subrepositories
+ include - include names matching the given patterns
+ exclude - exclude names matching the given patterns
+ """
+ if change and revs:
+ raise ValueError('cannot specify both change and rev')
+
+ args = cmdbuilder(b('diff'), r=list(map(strtobytes, revs)), c=change,
+ a=text, g=git, nodates=nodates,
+ p=showfunction, reverse=reverse,
+ w=ignoreallspace, b=ignorespacechange,
+ B=ignoreblanklines, U=unified, stat=stat,
+ S=subrepos, I=include, X=exclude, hidden=self.hidden,
+ *files)
+
+ return self.rawcommand(args)
+
+ def export(self, revs, output=None, switchparent=False,
+ text=False, git=False, nodates=False):
+ """Return the header and diffs for one or more changesets. When
+ output is given, dumps to file. The name of the file is given
+ using a format string. The formatting rules are as follows:
+
+ "%%" literal "%" character
+ "%H" changeset hash (40 hexadecimal digits)
+ "%N" number of patches being generated
+ "%R" changeset revision number
+ "%b" basename of the exporting repository
+ "%h" short-form changeset hash (12 hexadecimal digits)
+ "%n" zero-padded sequence number, starting at 1
+ "%r" zero-padded changeset revision number
+
+ output - print output to file with formatted name
+ switchparent - diff against the second parent
+ rev - a revision or list of revisions to export
+ text - treat all files as text
+ git - use git extended diff format
+ nodates - omit dates from diff headers
+
+ """
+ if not isinstance(revs, list):
+ revs = [revs]
+ args = cmdbuilder(b('export'), o=output, switch_parent=switchparent,
+ a=text, g=git, nodates=nodates, hidden=self.hidden,
+ *revs)
+
+ out = self.rawcommand(args)
+
+ if output is None:
+ return out
+
+ def forget(self, files, include=None, exclude=None):
+ """Mark the specified files so they will no longer be tracked after
+ the next commit.
+
+ This only removes files from the current branch, not from the entire
+ project history, and it does not delete them from the working directory.
+
+ Returns True on success.
+
+ include - include names matching the given patterns
+ exclude - exclude names matching the given patterns
+
+ """
+ if not isinstance(files, list):
+ files = [files]
+
+ args = cmdbuilder(b('forget'), I=include, X=exclude, *files)
+
+ eh = util.reterrorhandler(args)
+ self.rawcommand(args, eh=eh)
+
+ return bool(eh)
+
+ def grep(self, pattern, files=[], all=False, text=False, follow=False,
+ ignorecase=False, fileswithmatches=False, line=False, user=False,
+ date=False, include=None, exclude=None):
+ """Search for a pattern in specified files and revisions.
+
+ This behaves differently than Unix grep. It only accepts Python/Perl
+ regexps. It searches repository history, not the working directory.
+ It always prints the revision number in which a match appears.
+
+ Yields (filename, revision, [line, [match status, [user,
+ [date, [match]]]]]) per match depending on the given options.
+
+ all - print all revisions that match
+ text - treat all files as text
+ follow - follow changeset history, or file history across
+ copies and renames
+ ignorecase - ignore case when matching
+ fileswithmatches - return only filenames and revisions that match
+ line - return line numbers in the result tuple
+ user - return the author in the result tuple
+ date - return the date in the result tuple
+ include - include names matching the given patterns
+ exclude - exclude names matching the given patterns
+
+ """
+ if not isinstance(files, list):
+ files = [files]
+
+ args = cmdbuilder(b('grep'), all=all, a=text, f=follow, i=ignorecase,
+ l=fileswithmatches, n=line, u=user, d=date,
+ I=include, X=exclude, hidden=self.hidden,
+ *[pattern] + files)
+ args.append(b('-0'))
+
+ def eh(ret, out, err):
+ if ret != 1:
+ raise error.CommandError(args, ret, out, err)
+ return b('')
+
+ out = self.rawcommand(args, eh=eh).split(b('\0'))
+
+ fieldcount = 3
+ if user:
+ fieldcount += 1
+ if date:
+ fieldcount += 1
+ if line:
+ fieldcount += 1
+ if all:
+ fieldcount += 1
+ if fileswithmatches:
+ fieldcount -= 1
+
+ return util.grouper(fieldcount, out)
+
+ def heads(self, rev=[], startrev=[], topological=False, closed=False):
+ """Return a list of current repository heads or branch heads.
+
+ rev - return only branch heads on the branches associated with
+ the specified changesets.
+
+ startrev - return only heads which are descendants of the given revs.
+ topological - named branch mechanics will be ignored and only changesets
+ without children will be shown.
+
+ closed - normal and closed branch heads.
+
+ """
+ if not isinstance(rev, list):
+ rev = [rev]
+
+ args = cmdbuilder(b('heads'), r=startrev, t=topological, c=closed,
+ template=templates.changeset, hidden=self.hidden,
+ *rev)
+
+ def eh(ret, out, err):
+ if ret != 1:
+ raise error.CommandError(args, ret, out, err)
+ return b('')
+
+ out = self.rawcommand(args, eh=eh).split(b('\0'))[:-1]
+ return self._parserevs(out)
+
+ def identify(self, rev=None, source=None, num=False, id=False, branch=False,
+ tags=False, bookmarks=False):
+ """Return a summary string identifying the repository state at rev
+ using one or two parent hash identifiers, followed by a "+" if
+ the working directory has uncommitted changes, the branch name
+ (if not default), a list of tags, and a list of bookmarks.
+
+ When rev is not given, return a summary string of the current
+ state of the repository.
+
+ Specifying source as a repository root or Mercurial bundle will cause
+ lookup to operate on that repository/bundle.
+
+ num - show local revision number
+ id - show global revision id
+ branch - show branch
+ tags - show tags
+ bookmarks - show bookmarks
+
+ """
+ args = cmdbuilder(b('identify'), source, r=rev, n=num, i=id,
+ b=branch, t=tags, B=bookmarks,
+ hidden=self.hidden)
+
+ return self.rawcommand(args)
+
+ def import_(self, patches, strip=None, force=False, nocommit=False,
+ bypass=False, exact=False, importbranch=False, message=None,
+ date=None, user=None, similarity=None):
+ """Import the specified patches which can be a list of file names or a
+ file-like object and commit them individually (unless nocommit is
+ specified).
+
+ strip - directory strip option for patch. This has the same
+ meaning as the corresponding patch option (default: 1)
+
+ force - skip check for outstanding uncommitted changes
+ nocommit - don't commit, just update the working directory
+ bypass - apply patch without touching the working directory
+ exact - apply patch to the nodes from which it was generated
+ importbranch - use any branch information in patch (implied by exact)
+ message - the commit message
+ date - record the specified date as commit date
+ user - record the specified user as committer
+ similarity - guess renamed files by similarity (0<=s<=100)
+
+ """
+ if hasattr(patches, 'read') and hasattr(patches, 'readline'):
+ patch = patches
+
+ def readline(size, output):
+ return patch.readline(size)
+
+ stdin = True
+ patches = ()
+ prompt = readline
+ input = patch.read
+ else:
+ stdin = False
+ prompt = None
+ input = None
+
+ args = cmdbuilder(b('import'), strip=strip, force=force,
+ no_commit=nocommit, bypass=bypass, exact=exact,
+ import_branch=importbranch, message=message,
+ date=date, user=user, similarity=similarity, _=stdin,
+ *patches)
+
+ self.rawcommand(args, prompt=prompt, input=input)
+
+ def incoming(self, revrange=None, path=None, force=False, newest=False,
+ bundle=None, bookmarks=False, branch=None, limit=None,
+ nomerges=False, subrepos=False):
+ """Return new changesets found in the specified path or the default pull
+ location.
+
+ When bookmarks=True, return a list of (name, node) of incoming
+ bookmarks.
+
+ revrange - a remote changeset or list of changesets intended to be added
+ force - run even if remote repository is unrelated
+ newest - show newest record first
+ bundle - avoid downloading the changesets twice and store the
+ bundles into the specified file.
+
+ bookmarks - compare bookmarks (this changes the return value)
+ branch - a specific branch you would like to pull
+ limit - limit number of changes returned
+ nomerges - do not show merges
+ ssh - specify ssh command to use
+ remotecmd - specify hg command to run on the remote side
+ insecure- do not verify server certificate (ignoring web.cacerts config)
+ subrepos - recurse into subrepositories
+
+ """
+ args = cmdbuilder(b('incoming'), path,
+ template=templates.changeset, r=revrange,
+ f=force, n=newest, bundle=bundle,
+ B=bookmarks, b=branch, l=limit, M=nomerges,
+ S=subrepos)
+
+ def eh(ret, out, err):
+ if ret != 1:
+ raise error.CommandError(args, ret, out, err)
+
+ out = self.rawcommand(args, eh=eh)
+ if not out:
+ return []
+
+ out = util.eatlines(out, 2)
+ if bookmarks:
+ bms = []
+ for line in out.splitlines():
+ bms.append(tuple(line.split()))
+ return bms
+ else:
+ out = out.split(b('\0'))[:-1]
+ return self._parserevs(out)
+
+ def log(self, revrange=None, files=[], follow=False,
+ followfirst=False, date=None, copies=False, keyword=None,
+ removed=False, onlymerges=False, user=None, branch=None,
+ prune=None, hidden=None, limit=None, nomerges=False,
+ include=None, exclude=None):
+ """Return the revision history of the specified files or the entire
+ project.
+
+ File history is shown without following rename or copy history of files.
+ Use follow with a filename to follow history across renames and copies.
+ follow without a filename will only show ancestors or descendants of the
+ starting revision. followfirst only follows the first parent of merge
+ revisions.
+
+ If revrange isn't specified, the default is "tip:0" unless
+ follow is set, in which case the working directory parent is
+ used as the starting revision.
+
+ The returned changeset is a named tuple with the following
+ string fields:
+
+ - rev
+ - node
+ - tags (space delimited)
+ - branch
+ - author
+ - desc
+
+ follow - follow changeset history, or file history across
+ copies and renames
+ followfirst - only follow the first parent of merge changesets
+ date - show revisions matching date spec
+ copies - show copied files
+ keyword - do case-insensitive search for a given text
+ removed - include revisions where files were removed
+ onlymerges - show only merges
+ user - revisions committed by user
+ branch - show changesets within the given named branch
+ prune - do not display revision or any of its ancestors
+ hidden - show hidden changesets
+ limit - limit number of changes displayed
+ nomerges - do not show merges
+ include - include names matching the given patterns
+ exclude - exclude names matching the given patterns
+
+ """
+ if hidden is None:
+ hidden = self.hidden
+ args = cmdbuilder(b('log'), template=templates.changeset,
+ r=revrange, f=follow, follow_first=followfirst,
+ d=date, C=copies, k=keyword, removed=removed,
+ m=onlymerges, u=user, b=branch, P=prune,
+ l=limit, M=nomerges, I=include, X=exclude,
+ hidden=hidden, *files)
+
+ out = self.rawcommand(args)
+ out = out.split(b('\0'))[:-1]
+
+ return self._parserevs(out)
+
+ def manifest(self, rev=None, all=False):
+ """Yields (nodeid, permission, executable, symlink, file path) tuples
+ for version controlled files for the given revision. If no
+ revision is given, the first parent of the working directory
+ is used, or the null revision if no revision is checked out.
+
+ When all is True, all files from all revisions are yielded
+ (just the name). This includes deleted and renamed files.
+
+ """
+ args = cmdbuilder(b('manifest'), r=rev, all=all, debug=True,
+ hidden=self.hidden)
+
+ out = self.rawcommand(args)
+
+ if all:
+ for line in out.splitlines():
+ yield line
+ else:
+ for line in out.splitlines():
+ node = line[0:40]
+ perm = line[41:44]
+ symlink = line[45:46] == b('@')
+ executable = line[45:46] == b('*')
+ yield node, perm, executable, symlink, line[47:]
+
+ def merge(self, rev=None, force=False, tool=None, cb=merge.handlers.abort):
+ """Merge working directory with rev. If no revision is specified, the
+ working directory's parent is a head revision, and the current
+ branch contains exactly one other head, the other head is
+ merged with by default.
+
+ The current working directory is updated with all changes made in the
+ requested revision since the last common predecessor revision.
+
+ Files that changed between either parent are marked as changed for the
+ next commit and a commit must be performed before any further updates to
+ the repository are allowed. The next commit will have two parents.
+
+ force - force a merge with outstanding changes
+ tool - can be used to specify the merge tool used for file merges. It
+ overrides the HGMERGE environment variable and your configuration files.
+
+ cb - controls 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. It can be one of
+ merge.handlers, or a function that gets a single argument
+ which are the contents of stdout. It should return one of the
+ expected choices (a single character).
+
+ """
+ # we can't really use --preview since merge doesn't support --template
+ args = cmdbuilder(b('merge'), r=rev, f=force, t=tool)
+
+ prompt = None
+ if cb is merge.handlers.abort:
+ prompt = cb
+ elif cb is merge.handlers.noninteractive:
+ args.append(b('-y'))
+ else:
+ prompt = lambda size, output: cb(output) + b('\n')
+
+ self.rawcommand(args, prompt=prompt)
+
+ def move(self, source, dest, after=False, force=False, dryrun=False,
+ include=None, exclude=None):
+ """Mark dest as copies of source; mark source for deletion. If dest
+ is a directory, copies are put in that directory. If dest is a
+ file, then source must be a string.
+
+ Returns True on success, False if errors are encountered.
+
+ source - a file or a list of files
+ dest - a destination file or directory
+ after - record a rename that has already occurred
+ force - forcibly copy over an existing managed file
+ dryrun - do not perform actions, just print output
+ include - include names matching the given patterns
+ exclude - exclude names matching the given patterns
+
+ """
+ if not isinstance(source, list):
+ source = [source]
+
+ source.append(dest)
+ args = cmdbuilder(b('move'), A=after, f=force, n=dryrun,
+ I=include, X=exclude, *source)
+
+ eh = util.reterrorhandler(args)
+ self.rawcommand(args, eh=eh)
+
+ return bool(eh)
+
+ def outgoing(self, revrange=None, path=None, force=False, newest=False,
+ bookmarks=False, branch=None, limit=None, nomerges=False,
+ subrepos=False):
+ """Return changesets not found in the specified path or the default push
+ location.
+
+ When bookmarks=True, return a list of (name, node) of
+ bookmarks that will be pushed.
+
+ revrange - a (list of) changeset intended to be included in
+ the destination force - run even when the destination is
+ unrelated newest - show newest record first branch - a
+ specific branch you would like to push limit - limit number of
+ changes displayed nomerges - do not show merges ssh - specify
+ ssh command to use remotecmd - specify hg command to run on
+ the remote side insecure - do not verify server certificate
+ (ignoring web.cacerts config) subrepos - recurse into
+ subrepositories
+
+ """
+ args = cmdbuilder(b('outgoing'),
+ path,
+ template=templates.changeset, r=revrange,
+ f=force, n=newest, B=bookmarks,
+ b=branch, S=subrepos)
+
+ def eh(ret, out, err):
+ if ret != 1:
+ raise error.CommandError(args, ret, out, err)
+
+ out = self.rawcommand(args, eh=eh)
+ if not out:
+ return []
+
+ out = util.eatlines(out, 2)
+ if bookmarks:
+ bms = []
+ for line in out.splitlines():
+ bms.append(tuple(line.split()))
+ return bms
+ else:
+ out = out.split(b('\0'))[:-1]
+ return self._parserevs(out)
+
+ def parents(self, rev=None, file=None):
+ """Return the working directory's parent revisions. If rev is given,
+ the parent of that revision will be printed. If file is given,
+ the revision in which the file was last changed (before the
+ working directory revision or the revision specified by rev)
+ is returned.
+
+ """
+ args = cmdbuilder(b('parents'), file, template=templates.changeset,
+ r=rev, hidden=self.hidden)
+
+ out = self.rawcommand(args)
+ if not out:
+ return
+
+ out = out.split(b('\0'))[:-1]
+
+ return self._parserevs(out)
+
+ def paths(self, name=None):
+ """
+ Return the definition of given symbolic path name. If no name is given,
+ return a dictionary of pathname : url of all available names.
+
+ Path names are defined in the [paths] section of your configuration file
+ and in "/etc/mercurial/hgrc". If run inside a repository, ".hg/hgrc" is
+ used, too.
+ """
+ if not name:
+ out = self.rawcommand([b('paths')])
+ if not out:
+ return {}
+
+ return dict([s.split(b(' = '))
+ for s in out.rstrip().split(b('\n'))])
+ else:
+ args = cmdbuilder(b('paths'), name)
+ out = self.rawcommand(args)
+ return out.rstrip()
+
+ def pull(self, source=None, rev=None, update=False, force=False,
+ bookmark=None, branch=None, ssh=None, remotecmd=None,
+ insecure=False, tool=None):
+ """Pull changes from a remote repository.
+
+ This finds all changes from the repository specified by source
+ and adds them to this repository. If source is omitted, the
+ 'default' path will be used. By default, this does not update
+ the copy of the project in the working directory.
+
+ Returns True on success, False if update was given and there were
+ unresolved files.
+
+ update - update to new branch head if changesets were pulled
+ force - run even when remote repository is unrelated
+ rev - a (list of) remote changeset intended to be added
+ bookmark - (list of) bookmark to pull
+ branch - a (list of) specific branch you would like to pull
+ ssh - specify ssh command to use
+ remotecmd - specify hg command to run on the remote side
+ insecure - do not verify server certificate (ignoring
+ web.cacerts config)
+ tool - specify merge tool for rebase
+
+ """
+ args = cmdbuilder(b('pull'), source, r=rev, u=update, f=force,
+ B=bookmark, b=branch, e=ssh,
+ remotecmd=remotecmd, insecure=insecure,
+ t=tool)
+
+ eh = util.reterrorhandler(args)
+ self.rawcommand(args, eh=eh)
+
+ return bool(eh)
+
+ def push(self, dest=None, rev=None, force=False, bookmark=None, branch=None,
+ newbranch=False, ssh=None, remotecmd=None, insecure=False):
+ """Push changesets from this repository to the specified destination.
+
+ This operation is symmetrical to pull: it is identical to a pull in the
+ destination repository from the current one.
+
+ Returns True if push was successful, False if nothing to push.
+
+ rev - the (list of) specified revision and all its ancestors
+ will be pushed to the remote repository.
+
+ force - override the default behavior and push all changesets on all
+ branches.
+
+ bookmark - (list of) bookmark to push
+ branch - a (list of) specific branch you would like to push
+ newbranch - allows push to create a new named branch that is
+ not present at the destination. This allows you to only create
+ a new branch without forcing other changes.
+
+ ssh - specify ssh command to use
+ remotecmd - specify hg command to run on the remote side
+ insecure - do not verify server certificate (ignoring
+ web.cacerts config)
+
+ """
+ args = cmdbuilder(b('push'), dest, r=rev, f=force, B=bookmark, b=branch,
+ new_branch=newbranch, e=ssh, remotecmd=remotecmd,
+ insecure=insecure)
+
+ eh = util.reterrorhandler(args)
+ self.rawcommand(args, eh=eh)
+
+ return bool(eh)
+
+ def remove(self, files, after=False, force=False, include=None,
+ exclude=None):
+ """Schedule the indicated files for removal from the repository. This
+ only removes files from the current branch, not from the
+ entire project history.
+
+ Returns True on success, False if any warnings encountered.
+
+ after - used to remove only files that have already been deleted
+ force - remove (and delete) file even if added or modified
+ include - include names matching the given patterns
+ exclude - exclude names matching the given patterns
+
+ """
+ if not isinstance(files, list):
+ files = [files]
+
+ args = cmdbuilder(b('remove'), A=after, f=force, I=include, X=exclude,
+ *files)
+
+ eh = util.reterrorhandler(args)
+ self.rawcommand(args, eh=eh)
+
+ return bool(eh)
+
+ def resolve(self, file=[], all=False, listfiles=False, mark=False,
+ unmark=False, tool=None, include=None, exclude=None):
+ """
+ Redo merges or set/view the merge status of given files.
+
+ Returns True on success, False if any files fail a resolve attempt.
+
+ When listfiles is True, returns a list of (code, file path) of resolved
+ and unresolved files. Code will be 'R' or 'U' accordingly.
+
+ all - select all unresolved files
+ mark - mark files as resolved
+ unmark - mark files as unresolved
+ tool - specify merge tool
+ include - include names matching the given patterns
+ exclude - exclude names matching the given patterns
+ """
+ if not isinstance(file, list):
+ file = [file]
+
+ args = cmdbuilder(b('resolve'), a=all, l=listfiles, m=mark, u=unmark,
+ t=tool, I=include, X=exclude, *file)
+
+ out = self.rawcommand(args)
+
+ if listfiles:
+ l = []
+ for line in out.splitlines():
+ l.append(tuple(line.split(b(' '), 1)))
+ return l
+
+ def revert(self, files, rev=None, all=False, date=None, nobackup=False,
+ dryrun=False, include=None, exclude=None):
+ """With no revision specified, revert the specified files or
+ directories to the contents they had in the parent of the
+ working directory. This restores the contents of files to an
+ unmodified state and unschedules adds, removes, copies, and
+ renames. If the working directory has two parents, you must
+ explicitly specify a revision.
+
+ Specifying rev or date will revert the given files or
+ directories to their states as of a specific revision. Because
+ revert does not change the working directory parents, this
+ will cause these files to appear modified. This can be helpful
+ to "back out" some or all of an earlier change.
+
+ Modified files are saved with a .orig suffix before reverting.
+ To disable these backups, use nobackup.
+
+ Returns True on success.
+
+ all - revert all changes when no arguments given
+ date - tipmost revision matching date
+ rev - revert to the specified revision
+ nobackup - do not save backup copies of files
+ include - include names matching the given patterns
+ exclude - exclude names matching the given patterns
+ dryrun - do not perform actions, just print output
+
+ """
+ if not isinstance(files, list):
+ files = [files]
+
+ args = cmdbuilder(b('revert'), r=rev, a=all, d=date,
+ no_backup=nobackup, n=dryrun, I=include, X=exclude,
+ hidden=self.hidden, *files)
+
+ eh = util.reterrorhandler(args)
+ self.rawcommand(args, eh=eh)
+
+ return bool(eh)
+
+ def root(self):
+ """
+ Return the root directory of the current repository.
+ """
+ return self.rawcommand([b('root')]).rstrip()
+
+ def status(self, rev=None, change=None, all=False, modified=False,
+ added=False, removed=False, deleted=False, clean=False,
+ unknown=False, ignored=False, copies=False,
+ subrepos=False, include=None, exclude=None):
+ """
+ Return status of files in the repository as a list of (code, file path)
+ where code can be:
+
+ M = modified
+ A = added
+ R = removed
+ C = clean
+ ! = missing (deleted by non-hg command, but still tracked)
+ ? = untracked
+ I = ignored
+ = origin of the previous file listed as A (added)
+
+ rev - show difference from (list of) revision
+ change - list the changed files of a revision
+ all - show status of all files
+ modified - show only modified files
+ added - show only added files
+ removed - show only removed files
+ deleted - show only deleted (but tracked) files
+ clean - show only files without changes
+ unknown - show only unknown (not tracked) files
+ ignored - show only ignored files
+ copies - show source of copied files
+ subrepos - recurse into subrepositories
+ include - include names matching the given patterns
+ exclude - exclude names matching the given patterns
+ """
+ if rev and change:
+ raise ValueError('cannot specify both rev and change')
+
+ args = cmdbuilder(b('status'), rev=rev, change=change, A=all,
+ m=modified, a=added, r=removed, d=deleted, c=clean,
+ u=unknown, i=ignored, C=copies, S=subrepos, I=include,
+ X=exclude, hidden=self.hidden)
+
+ args.append(b('-0'))
+
+ out = self.rawcommand(args)
+ l = []
+
+ for entry in out.split(b('\0')):
+ if entry:
+ if entry[0:1] == b(' '):
+ l.append((b(' '), entry[2:]))
+ else:
+ l.append(tuple(entry.split(b(' '), 1)))
+
+ return l
+
+ def tag(self, names, rev=None, message=None, force=False, local=False,
+ remove=False, date=None, user=None):
+ """Add one or more tags specified by names for the current or given
+ revision.
+
+ Changing an existing tag is normally disallowed; use force to override.
+
+ Tag commits are usually made at the head of a branch. If the
+ parent of the working directory is not a branch head, a
+ CommandError will be raised. force can be specified to force
+ the tag commit to be based on a non-head changeset.
+
+ local - make the tag local
+ rev - revision to tag
+ remove - remove a tag
+ message - set commit message
+ date - record the specified date as commit date
+ user - record the specified user as committer
+
+ """
+ if not isinstance(names, list):
+ names = [names]
+
+ args = cmdbuilder(b('tag'), r=rev, m=message, f=force, l=local,
+ remove=remove, d=date, u=user, hidden=self.hidden,
+ *names)
+
+ self.rawcommand(args)
+
+ def tags(self):
+ """
+ Return a list of repository tags as: (name, rev, node, islocal)
+ """
+ args = cmdbuilder(b('tags'), v=True)
+
+ out = self.rawcommand(args)
+
+ t = []
+ for line in out.splitlines():
+ taglocal = line.endswith(b(' local'))
+ if taglocal:
+ line = line[:-6]
+ name, rev = line.rsplit(b(' '), 1)
+ rev, node = rev.split(b(':'))
+ t.append((name.rstrip(), int(rev), node, taglocal))
+ return t
+
+ def phase(self, revs=(), secret=False, draft=False, public=False,
+ force=False):
+ '''Set or show the current phase name.
+
+ revs - target revision(s)
+ public - set changeset phase to public
+ draft - set changeset phase to draft
+ secret - set changeset phase to secret
+ force - allow to move boundary backward
+
+ output format: [(id, phase) ...] for each changeset
+
+ The arguments match the mercurial API.
+ '''
+ if not isinstance(revs, (list, tuple)):
+ revs = [revs]
+ args = util.cmdbuilder(b('phase'), secret=secret, draft=draft,
+ public=public, force=force,
+ hidden=self.hidden, *revs)
+ out = self.rawcommand(args)
+ if draft or public or secret:
+ return
+ else:
+ output = [i.split(b(': '))for i in out.strip().split(b('\n'))]
+ return [(int(num), phase) for (num, phase) in output]
+
+ def summary(self, remote=False):
+ """
+ Return a dictionary with a brief summary of the working directory state,
+ including parents, branch, commit status, and available updates.
+
+ 'parent' : a list of (rev, node, tags, message)
+ 'branch' : the current branch
+ 'commit' : True if the working directory is clean, False otherwise
+ 'update' : number of available updates,
+ ['remote' : (in, in bookmarks, out, out bookmarks),]
+ ['mq': (applied, unapplied) mq patches,]
+
+ unparsed entries will be of them form key : value
+ """
+ args = cmdbuilder(b('summary'), remote=remote, hidden=self.hidden)
+
+ out = self.rawcommand(args).splitlines()
+
+ d = {}
+ while out:
+ line = out.pop(0)
+ name, value = line.split(b(': '), 1)
+
+ if name == b('parent'):
+ parent, tags = value.split(b(' '), 1)
+ rev, node = parent.split(b(':'))
+
+ if tags:
+ tags = tags.replace(b(' (empty repository)'), b(''))
+ else:
+ tags = None
+
+ value = d.get(name, [])
+
+ if rev == b('-1'):
+ value.append((int(rev), node, tags, None))
+ else:
+ message = out.pop(0)[1:]
+ value.append((int(rev), node, tags, message))
+ elif name == b('branch'):
+ pass
+ elif name == b('commit'):
+ value = value == b('(clean)')
+ elif name == b('update'):
+ if value == b('(current)'):
+ value = 0
+ else:
+ value = int(value.split(b(' '), 1)[0])
+ elif remote and name == b('remote'):
+ if value == b('(synced)'):
+ value = 0, 0, 0, 0
+ else:
+ inc = incb = out_ = outb = 0
+
+ for v in value.split(b(', ')):
+ count, v = v.split(b(' '), 1)
+ if v == b('outgoing'):
+ out_ = int(count)
+ elif v.endswith(b('incoming')):
+ inc = int(count)
+ elif v == b('incoming bookmarks'):
+ incb = int(count)
+ elif v == b('outgoing bookmarks'):
+ outb = int(count)
+
+ value = inc, incb, out_, outb
+ elif name == b('mq'):
+ applied = unapplied = 0
+ for v in value.split(b(', ')):
+ count, v = v.split(b(' '), 1)
+ if v == b('applied'):
+ applied = int(count)
+ elif v == b('unapplied'):
+ unapplied = int(count)
+ value = applied, unapplied
+
+ d[name] = value
+
+ return d
+
+ def tip(self):
+ """
+ Return the tip revision (usually just called the tip) which is the
+ changeset most recently added to the repository (and therefore the most
+ recently changed head).
+ """
+ args = cmdbuilder(b('tip'), template=templates.changeset,
+ hidden=self.hidden)
+ out = self.rawcommand(args)
+ out = out.split(b('\0'))
+
+ return self._parserevs(out)[0]
+
+ def update(self, rev=None, clean=False, check=False, date=None):
+ """
+ Update the repository's working directory to changeset specified by rev.
+ If rev isn't specified, update to the tip of the current named branch.
+
+ Return the number of files (updated, merged, removed, unresolved)
+
+ clean - discard uncommitted changes (no backup)
+ check - update across branches if no uncommitted changes
+ date - tipmost revision matching date
+ """
+ if clean and check:
+ raise ValueError('clean and check cannot both be True')
+
+ args = cmdbuilder(b('update'), r=rev, C=clean, c=check, d=date,
+ hidden=self.hidden)
+
+ def eh(ret, out, err):
+ if ret == 1:
+ return out
+
+ raise error.CommandError(args, ret, out, err)
+
+
+ out = self.rawcommand(args, eh=eh)
+
+ m = re.search(b(r'^(\d+).+, (\d+).+, (\d+).+, (\d+)'), out,
+ re.MULTILINE)
+ return tuple(map(int, list(m.groups())))
+
+ @property
+ def version(self):
+ """Return hg version that runs the command server as a 4 fielded
+ tuple: major, minor, micro and local build info. e.g. (1, 9,
+ 1, '+4-3095db9f5c2c')
+ """
+ if self._version is None:
+ v = self.rawcommand(cmdbuilder(b('version'), q=True))
+ v = list(re.match(b(r'.*?(\d+)\.(\d+)\.?(\d+)?(\+[0-9a-f-]+)?'),
+ v).groups())
+
+ for i in range(3):
+ try:
+ v[i] = int(v[i])
+ except TypeError:
+ v[i] = 0
+
+ self._version = tuple(v)
+
+ return self._version
+
+ def __getitem__(self, changeid):
+ try:
+ return context.changectx(self, changeid)
+ except ValueError as e:
+ raise KeyError(*e.args)
+
+ def __contains__(self, changeid):
+ """
+ check if changeid, which can be either a local revision number or a
+ changeset id, matches a changeset in the client.
+ """
+ try:
+ context.changectx(self, changeid)
+ return True
+ except ValueError:
+ return False
diff --git a/third_party/python/python-hglib/hglib/context.py b/third_party/python/python-hglib/hglib/context.py
new file mode 100644
index 0000000000..3ba9abb890
--- /dev/null
+++ b/third_party/python/python-hglib/hglib/context.py
@@ -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(')'))
diff --git a/third_party/python/python-hglib/hglib/error.py b/third_party/python/python-hglib/hglib/error.py
new file mode 100644
index 0000000000..e0652dc74d
--- /dev/null
+++ b/third_party/python/python-hglib/hglib/error.py
@@ -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
diff --git a/third_party/python/python-hglib/hglib/merge.py b/third_party/python/python-hglib/hglib/merge.py
new file mode 100644
index 0000000000..88bc99d993
--- /dev/null
+++ b/third_party/python/python-hglib/hglib/merge.py
@@ -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'
diff --git a/third_party/python/python-hglib/hglib/templates.py b/third_party/python/python-hglib/hglib/templates.py
new file mode 100644
index 0000000000..f91ee466a7
--- /dev/null
+++ b/third_party/python/python-hglib/hglib/templates.py
@@ -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')
diff --git a/third_party/python/python-hglib/hglib/util.py b/third_party/python/python-hglib/hglib/util.py
new file mode 100644
index 0000000000..b4bfe731f9
--- /dev/null
+++ b/third_party/python/python-hglib/hglib/util.py
@@ -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)
diff --git a/third_party/python/python-hglib/setup.py b/third_party/python/python-hglib/setup.py
new file mode 100644
index 0000000000..f565ae7f98
--- /dev/null
+++ b/third_party/python/python-hglib/setup.py
@@ -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'])
diff --git a/third_party/python/python-hglib/test.py b/third_party/python/python-hglib/test.py
new file mode 100644
index 0000000000..e0b4021f45
--- /dev/null
+++ b/third_party/python/python-hglib/test.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+
+import nose
+from tests import with_hg
+
+if __name__ == '__main__':
+ nose.main(addplugins=[with_hg.WithHgPlugin()])