[ros-vcstools] 26/31: Imported Upstream version 0.1.37
Jochen Sprickerhof
jspricke-guest at moszumanska.debian.org
Sun Oct 18 14:23:22 UTC 2015
This is an automated email from the git hooks/post-receive script.
jspricke-guest pushed a commit to branch master
in repository ros-vcstools.
commit 7de340f1dc51ecca75c594dad861d0837580eaf5
Author: Jochen Sprickerhof <git at jochen.sprickerhof.de>
Date: Fri Sep 4 10:07:21 2015 +0200
Imported Upstream version 0.1.37
---
.gitignore | 1 +
.hgignore | 14 ---
.travis.yml | 6 ++
doc/changelog.rst | 12 +++
doc/conf.py | 14 +++
doc/developers_guide.rst | 13 +--
doc/vcsclient.rst | 26 ++++-
setup.sh | 10 --
src/vcstools/__version__.py | 2 +-
src/vcstools/bzr.py | 17 ++++
src/vcstools/common.py | 8 +-
src/vcstools/git.py | 204 ++++++++++++++++++++++++++--------------
src/vcstools/hg.py | 61 ++++++++++--
src/vcstools/svn.py | 123 ++++++++++++++++++++++--
src/vcstools/tar.py | 8 ++
src/vcstools/vcs_abstraction.py | 10 ++
src/vcstools/vcs_base.py | 35 +++++++
test/test_git.py | 203 ++++++++++++++++++++++++++++++++-------
test/test_hg.py | 129 ++++++++++++++++++++++---
test/test_svn.py | 156 +++++++++++++++++++++++++++++-
test/test_tar.py | 8 +-
21 files changed, 883 insertions(+), 177 deletions(-)
diff --git a/.gitignore b/.gitignore
index cb0633e..3a386ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
build
dist
*.egg-info
+nosetests.xml
diff --git a/.hgignore b/.hgignore
deleted file mode 100644
index 34c80ae..0000000
--- a/.hgignore
+++ /dev/null
@@ -1,14 +0,0 @@
-syntax: glob
-*.orig
-*.swp
-*.pyc
-*.DS_Store
-*~
-*.log
-.coverage
-doc-pak/*
-src/vcstools.egg-info/*
-nosetests.xml
-syntax: regexp
-(target|build|dist)/.*
-
diff --git a/.travis.yml b/.travis.yml
index 5d6e3c9..9863346 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,7 @@ python:
- "2.7"
- "3.2"
- "3.3"
+ - "3.4"
env:
# lucid
- HG='1.4.2' BZR=2.1.4 GIT=v1.7.0.4 SVN=1.6.6 PYYAML=3.10
@@ -16,6 +17,8 @@ env:
- HG='2.0.2' BZR=2.5.0 GIT=v1.7.9.5 SVN=1.6.17 PYYAML=3.10
# quantal
- HG='2.2.2' BZR=2.6.0+bzr6595 GIT=v1.7.10.4 SVN=1.7.5 PYYAML=3.10
+ # trusty
+ - HG='2.8.2' BZR=2.6.0+bzr6595 GIT=v1.9.1 SVN=1.8.8 PYYAML=3.10 SQLITE=sqlite-amalgamation-3071501.zip
# bzr 2.1.1 only builds with python 2.6
matrix:
exclude:
@@ -25,12 +28,15 @@ matrix:
env: HG='1.4.2' BZR=2.1.4 GIT=v1.7.0.4 SVN=1.6.6 PYYAML=3.10
- python: "3.3"
env: HG='1.4.2' BZR=2.1.4 GIT=v1.7.0.4 SVN=1.6.6 PYYAML=3.10
+ - python: "3.4"
+ env: HG='1.4.2' BZR=2.1.4 GIT=v1.7.0.4 SVN=1.6.6 PYYAML=3.10
before_install:
- export REPO=`pwd`
install:
- sudo apt-get update
- sudo apt-get install -qq python3-yaml python3-dev
- sudo apt-get install -qq libapr1 libapr1-dev libaprutil1 libaprutil1-dev libneon27 libneon27-dev libc6-dev g++ gcc
+ - if [ $SQLITE ]; then (wget http://www.sqlite.org/$SQLITE && mkdir -p $HOME/builds/subversion-$SVN/sqlite-amalgamation && unzip -j $SQLITE -d $HOME/builds/subversion-$SVN/sqlite-amalgamation); fi
- echo $PYTHONPATH
- python -c 'import sys;print(sys.path)'
- python setup.py build
diff --git a/doc/changelog.rst b/doc/changelog.rst
index d58a2d2..15bcd23 100644
--- a/doc/changelog.rst
+++ b/doc/changelog.rst
@@ -4,6 +4,18 @@ Changelog
0.1
===
+0.1.37
+------
+
+- Fix an issue where log were restricted to the named branch (hg).
+- Fixed svn to use a global revision number rather than a branch-local revision.
+- Added the get_remote_version() and get_current_version_label() API calls.
+- Enhanced use of ``no_warn`` in run_shell_command().
+- Fix get_version() to catch stderr.
+- Added get_branches() API call.
+- Fix some errors and warnings to output to stderr.
+- Fix output to avoid extra newlines when show_stdout=True.
+
0.1.36
------
diff --git a/doc/conf.py b/doc/conf.py
index 3c4f45d..42faf0e 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -217,3 +217,17 @@ man_pages = [
('index', 'vcstools', u'vcstools Documentation',
[u'Tully Foote, Thibault Kruse, Ken Conley'], 1)
]
+
+autodoc_default_flags = ['members', 'private-members', 'special-members',
+ #'undoc-members',
+ 'show-inheritance']
+
+def autodoc_skip_member(app, what, name, obj, skip, options):
+ exclusions = ('__weakref__', # special-members
+ '__doc__', '__module__', '__dict__', # undoc-members
+ )
+ exclude = name in exclusions
+ return skip or exclude
+
+def setup(app):
+ app.connect('autodoc-skip-member', autodoc_skip_member)
diff --git a/doc/developers_guide.rst b/doc/developers_guide.rst
index cddca68..a9da14c 100644
--- a/doc/developers_guide.rst
+++ b/doc/developers_guide.rst
@@ -20,8 +20,7 @@ Changelog
Bug reports and feature requests
--------------------------------
-- `Submit a bug report <https://kforge.ros.org/vcstools/trac/newticket?component=vcstools&type=defect>`_
-- `Submit a feature request <https://kforge.ros.org/vcstools/trac/newticket?component=vcstools&type=enhancement&vcstools>`_
+- `Submit a bug report <https://github.com/vcstools/vcstools>`_
Developer Setup
---------------
@@ -31,17 +30,9 @@ which you will need to download and install in order to run the
packaging. We use setuptools instead of distutils in order to be able
use ``setup()`` keys like ``install_requires``.
-Configure your :envvar:`PYTHONPATH`::
-
- cd vcstools
- . setup.sh
-
-OR::
-
cd vcstools
- python setup.py install
+ python setup.py develop
-The first will prepend ``vcstools/src`` to your :envvar:`PYTHONPATH`. The second will install vcstools into your dist/site-packages.
Testing
-------
diff --git a/doc/vcsclient.rst b/doc/vcsclient.rst
index e673c72..fc30183 100644
--- a/doc/vcsclient.rst
+++ b/doc/vcsclient.rst
@@ -28,7 +28,15 @@ The :class:`VcsClient` class provides a generic API for
:returns: filesystem path this client is initialized with.
- .. method:: get_version([spec=None])
+ .. method:: url_matches(self, url, url_or_shortcut) -> bool
+
+ client can decide whether the url and the other url are equivalent.
+ Checks string equality by default
+
+ :param url_or_shortcut: url or shortcut (e.g. bzr launchpad url)
+ :returns: bool if params are equivalent
+
+ .. method:: get_version([spec=None]) -> str
:param spec: token for identifying repository revision
desired. Token might be a tagname, branchname, version-id,
@@ -49,6 +57,22 @@ The :class:`VcsClient` class provides a generic API for
(e.g. revision number, or SHA-ID) of a revision specified by
some token.
+ .. method:: get_remote_version(self, fatch=False) -> str
+
+ Find an identifier for the current revision on remote.
+ Token spec might be a tagname,
+ version-id, SHA-ID, ... depending on the VCS implementation.
+
+ :param fetch: if False, only local information may be used
+ :returns: current revision number of the remote repository.
+
+ .. method:: get_current_version_label() -> str
+
+ Find an description for the current local version.
+ Token spec might be a branchname,
+ version-id, SHA-ID, ... depending on the VCS implementation.
+
+ :returns: short description of local version (e.g. branchname, tagename).
.. method:: checkout(url, [version=''], [verbose=False], [shallow=False])
diff --git a/setup.sh b/setup.sh
deleted file mode 100644
index 14d4b2f..0000000
--- a/setup.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-SCRIPT_PATH="${BASH_SOURCE[0]}";
-if([ -h "${SCRIPT_PATH}" ]) then
- while([ -h "${SCRIPT_PATH}" ]) do SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
-fi
-pushd . > /dev/null
-cd `dirname ${SCRIPT_PATH}` > /dev/null
-SCRIPT_PATH=`pwd`;
-popd > /dev/null
-
-export PYTHONPATH=$SCRIPT_PATH/src:$PYTHONPATH
diff --git a/src/vcstools/__version__.py b/src/vcstools/__version__.py
index 13bb794..2d1c1e1 100644
--- a/src/vcstools/__version__.py
+++ b/src/vcstools/__version__.py
@@ -1 +1 @@
-version = '0.1.36'
+version = '0.1.37'
diff --git a/src/vcstools/bzr.py b/src/vcstools/bzr.py
index 703f93d..6c2ab14 100644
--- a/src/vcstools/bzr.py
+++ b/src/vcstools/bzr.py
@@ -208,6 +208,15 @@ class BzrClient(VcsClientBase):
us_env=True)
return output.strip()
+ def get_current_version_label(self):
+ # url contains branch information
+ return None
+
+ def get_remote_version(self, fetch=False):
+ # Not sure how to get any useful information from bzr about this,
+ # since bzr has no globally unique IDs
+ return None
+
def get_diff(self, basepath=None):
response = None
if basepath is None:
@@ -277,6 +286,14 @@ class BzrClient(VcsClientBase):
response = response_processed
return response
+ def get_branches(self, local_only=False):
+ # see http://doc.bazaar.canonical.com/beta/en/user-guide/shared_repository_layouts.html
+ # the 'bzr branches' command exists, but is not useful here (too many assumptions)
+ # Else bazaar branches are equivalent to forks in git and hg
+ # such branches (forks) on launchpad could be retrieved using
+ # the launchpadlib, but the API is probably not stable.
+ raise NotImplementedError("get_branches is not implemented for bzr")
+
def export_repository(self, version, basepath):
# execute the bzr export cmd
cmd = 'bzr export --format=tgz {0} '.format(basepath + '.tar.gz')
diff --git a/src/vcstools/common.py b/src/vcstools/common.py
index 8a2a9dc..fdb68d8 100644
--- a/src/vcstools/common.py
+++ b/src/vcstools/common.py
@@ -33,6 +33,7 @@
from __future__ import absolute_import, print_function, unicode_literals
import errno
import os
+import sys
import copy
import shlex
import subprocess
@@ -241,7 +242,7 @@ def _read_shell_output(proc, no_filter, verbose, show_stdout, output_queue):
line = line.decode('UTF-8')
if line is not None and line != '':
if verbose or not _discard_line(line):
- print(line),
+ sys.stdout.write(line),
stdout_buf.append(line)
if (not line or proc.returncode is not None):
break
@@ -250,7 +251,7 @@ def _read_shell_output(proc, no_filter, verbose, show_stdout, output_queue):
for line in iter(proc.stderr.readline, b''):
line = line.decode('UTF-8')
if line != '':
- print(line),
+ sys.stdout.write(line),
stderr_buf.append(line)
if not line:
break
@@ -341,7 +342,8 @@ def run_shell_command(cmd, cwd=None, shell=False, us_env=True,
if cwd is not None:
message += "\n run at: '%s'" % (cwd)
message += "\n errcode: %s:\n%s" % (proc.returncode, stderr)
- logger.warn(message)
+ if not no_warn:
+ logger.warn(message)
result = stdout
if result is not None:
result = result.rstrip()
diff --git a/src/vcstools/git.py b/src/vcstools/git.py
index fc7c816..8d456c8 100644
--- a/src/vcstools/git.py
+++ b/src/vcstools/git.py
@@ -53,6 +53,7 @@ disambiguation, and in some cases warns.
from __future__ import absolute_import, print_function, unicode_literals
import os
+import sys
import gzip
import dateutil.parser # For parsing date strings
from distutils.version import LooseVersion
@@ -262,7 +263,7 @@ class GitClient(VcsClientBase):
# local branch might be named differently from remote by user, we respect that
same_branch = (refname == current_branch)
if not same_branch:
- branch_parent = self.get_branch_parent(current_branch=current_branch)
+ (branch_parent, remote) = self.get_branch_parent(current_branch=current_branch)
if not refname:
# ! changing refname to cause fast-forward
refname = branch_parent
@@ -283,7 +284,12 @@ class GitClient(VcsClientBase):
if same_branch:
if fast_foward:
if not branch_parent and current_branch:
- branch_parent = self.get_branch_parent(current_branch=current_branch)
+ (branch_parent, remote) = self.get_branch_parent(current_branch=current_branch)
+ if remote != 'origin':
+ # if remote is not origin, must not fast-forward (because based on origin)
+ sys.stderr.write("vcstools only handles branches tracking remote 'origin'," +
+ " branch '%s' tracks remote '%s'\n" % (current_branch, remote))
+ branch_parent = None
# already on correct branch, fast-forward if there is a parent
if branch_parent:
if not self._do_fast_forward(branch_parent=branch_parent,
@@ -317,7 +323,7 @@ class GitClient(VcsClientBase):
# commit becomes dangling unless we move to one of its descendants
if not self.rev_list_contains(refname, current_version, fetch=False):
# TODO: should raise error instead of printing message
- print("vcstools refusing to move away from dangling commit, to protect your work.")
+ sys.stderr.write("vcstools refusing to move away from dangling commit, to protect your work.\n")
return False
# git checkout makes all the decisions for us
@@ -325,7 +331,12 @@ class GitClient(VcsClientBase):
if refname_is_local_branch:
# if we just switched to a local tracking branch (not created one), we should also fast forward
- new_branch_parent = self.get_branch_parent(current_branch=refname)
+ (new_branch_parent, remote) = self.get_branch_parent(current_branch=refname)
+ if remote != 'origin':
+ # if remote is not origin, must not fast-forward (because based on origin)
+ sys.stderr.write("vcstools only handles branches tracking remote 'origin'," +
+ " branch '%s' tracks remote '%s'\n" % (current_branch, remote))
+ new_branch_parent = None
if new_branch_parent is not None:
if fast_foward:
if not self._do_fast_forward(branch_parent=new_branch_parent,
@@ -334,6 +345,31 @@ class GitClient(VcsClientBase):
return False
return (not update_submodules) or self.update_submodules(verbose=verbose, timeout=timeout)
+ def get_current_version_label(self):
+ """
+ For git we change the label to clarify when a different remote
+ is configured.
+ """
+ branch = self.get_branch()
+ if branch is None:
+ return '<detached>'
+ result = branch
+ (remote_branch, remote) = self.get_branch_parent()
+ if remote_branch is not None:
+ # if not following 'origin/branch', display 'branch < tracked ref'
+ if (remote_branch != branch or remote != 'origin'):
+ result += ' < '
+ if remote != 'origin':
+ result += remote + '/'
+ result += remote_branch
+ return result
+
+ def get_remote_version(self, fetch=False):
+ # try tracked branch on origin (returns None if on other remote)
+ (parent_branch, remote) = self.get_branch_parent(fetch=fetch)
+ if parent_branch is not None:
+ return self.get_version(spec=remote+'/'+parent_branch)
+
def get_version(self, spec=None):
"""
:param spec: (optional) token to identify desired version. For
@@ -348,22 +384,27 @@ class GitClient(VcsClientBase):
if spec is not None:
command += " %s" % sanitized(spec)
command += " --format='%H'"
- output = ''
- # we repeat the call once after fetching if necessary
- for _ in range(2):
- _, output, _ = run_shell_command(command,
- shell=True,
- cwd=self._path)
- if (output != '' or spec is None):
- break
- # we try again after fetching if given spec had not been found
- try:
- self._do_fetch()
- except GitError:
- return None
- # On Windows the version can have single quotes around it
- output = output.strip("'")
- return output
+ _, output, _ = run_shell_command(command, shell=True,
+ no_warn=True, cwd=self._path)
+ if output.strip() != '':
+ # On Windows the version can have single quotes around it
+ version = output.strip().strip("'")
+ return version # found SHA-ID
+ elif spec is None:
+ return None
+ # we try again after fetching if given spec had not been found
+ try:
+ self._do_fetch()
+ except GitError:
+ return None
+ # we repeat the call once again after fetching
+ _, output, _ = run_shell_command(command, shell=True,
+ no_warn=True, cwd=self._path)
+ if output.strip() == '':
+ # even if after fetching, not found specified version
+ return None
+ version = output.strip().strip("'")
+ return version
return None
def get_diff(self, basepath=None):
@@ -450,13 +491,16 @@ class GitClient(VcsClientBase):
response = response_processed
return response
- def is_remote_branch(self, branch_name, fetch=True):
+ def is_remote_branch(self, branch_name, remote_name=None, fetch=True):
"""
checks list of remote branches for match. Set fetch to False if you just fetched already.
- :returns: True if git branch knows ref for remote "origin"
+ :returns: True if branch_name exists for remote <remote_name> (or 'origin' if None)
:raises: GitError when git fetch fails
"""
+ if remote_name is None:
+ remote_name = "origin" # default remote name is origin
+
if self.path_exists():
if fetch:
self._do_fetch()
@@ -467,7 +511,7 @@ class GitClient(VcsClientBase):
elem = l.split()[0]
rem_name = elem[:elem.find('/')]
br_name = elem[elem.find('/') + 1:]
- if rem_name == "origin" and br_name == branch_name:
+ if rem_name == remote_name and br_name == branch_name:
return True
return False
@@ -499,54 +543,62 @@ class GitClient(VcsClientBase):
def get_branch_parent(self, fetch=False, current_branch=None):
"""
- return the name of the branch this branch tracks, if any
-
+ :param fetch: if true, performs git fetch first
+ :param current_branch: if not None, this is used as current branch (else extra shell call)
+ :returns: (branch, remote) the name of the branch this branch tracks and its remote
:raises: GitError if fetch fails
"""
- if self.path_exists():
- # get name of configured merge ref.
- branchname = current_branch or self.get_branch()
- if branchname is None:
- return None
- cmd = 'git config --get %s' % sanitized('branch.%s.merge' % branchname)
+ if not self.path_exists():
+ return (None, None)
+ # get name of configured merge ref.
+ branchname = current_branch or self.get_branch()
+ if branchname is None:
+ return (None, None)
- _, output, _ = run_shell_command(cmd,
- shell=True,
- cwd=self._path)
- if not output:
- return None
- lines = output.splitlines()
- if len(lines) > 1:
- print("vcstools unable to handle multiple merge references for branch %s:\n%s" % (branchname, output))
- return None
- # get name of configured remote
- cmd = 'git config --get "branch.%s.remote"' % branchname
- _, output2, _ = run_shell_command(cmd, shell=True, cwd=self._path)
- if output2 != "origin":
- print("vcstools only handles branches tracking remote 'origin'," +
- " branch '%s' tracks remote '%s'" % (branchname, output2))
- return None
- output = lines[0]
- # output is either refname, or /refs/heads/refname, or
- # heads/refname we would like to return refname however,
- # user could also have named any branch
- # "/refs/heads/refname", for some unholy reason check all
- # known branches on remote for refname, then for the odd
- # cases, as git seems to do
- candidate = output
- if candidate.startswith('refs/'):
- candidate = candidate[len('refs/'):]
- if candidate.startswith('heads/'):
- candidate = candidate[len('heads/'):]
- elif candidate.startswith('tags/'):
- candidate = candidate[len('tags/'):]
- elif candidate.startswith('remotes/'):
- candidate = candidate[len('remotes/'):]
- if self.is_remote_branch(candidate, fetch=fetch):
- return candidate
- if output != candidate and self.is_remote_branch(output, fetch=False):
- return output
- return None
+ cmd = 'git config --get %s' % sanitized('branch.%s.merge' % branchname)
+
+ _, output, _ = run_shell_command(cmd,
+ shell=True,
+ cwd=self._path)
+ if not output:
+ return (None, None)
+ lines = output.splitlines()
+ if len(lines) > 1:
+ sys.stderr.write("vcstools unable to handle multiple merge references for branch %s:\n%s\n"
+ % (branchname, output))
+ return (None, None)
+
+ # get name of configured remote
+ cmd = 'git config --get "branch.%s.remote"' % branchname
+ _, output2, _ = run_shell_command(cmd, shell=True, cwd=self._path)
+ remote = output2 or 'origin'
+
+ branch_reference = lines[0]
+ # branch_reference is either refname, or /refs/heads/refname, or
+ # heads/refname we would like to return refname however,
+ # user could also have named any branch
+ # "/refs/heads/refname", for some unholy reason check all
+ # known branches on remote for refname, then for the odd
+ # cases, as git seems to do
+ candidate = branch_reference
+ if candidate.startswith('refs/'):
+ candidate = candidate[len('refs/'):]
+ if candidate.startswith('heads/'):
+ candidate = candidate[len('heads/'):]
+ elif candidate.startswith('tags/'):
+ candidate = candidate[len('tags/'):]
+ elif candidate.startswith('remotes/'):
+ candidate = candidate[len('remotes/'):]
+
+ result = None
+ if self.is_remote_branch(candidate, remote_name=remote, fetch=fetch):
+ result = candidate
+ elif branch_reference != candidate and self.is_remote_branch(branch_reference, remote_name=remote, fetch=False):
+ result = branch_reference
+
+ if result is not None:
+ return (result, remote)
+ return None, None
def is_tag(self, tag_name, fetch=True):
"""
@@ -659,6 +711,22 @@ class GitClient(VcsClientBase):
os.remove(basepath + '.tar')
return True
+ def get_branches(self, local_only=False):
+ cmd = 'git branch --no-color'
+ if not local_only:
+ cmd += ' -a'
+ result, out, err = run_shell_command(cmd,
+ cwd=self._path,
+ shell=True,
+ show_stdout=False)
+ branches = []
+ for line in out.splitlines():
+ if 'HEAD -> ' in line:
+ continue
+ line = line.strip('* ')
+ branches.append(line)
+ return branches
+
def _do_fetch(self, timeout=None):
"""
calls git fetch
diff --git a/src/vcstools/hg.py b/src/vcstools/hg.py
index fbd4bdd..a776685 100644
--- a/src/vcstools/hg.py
+++ b/src/vcstools/hg.py
@@ -215,9 +215,9 @@ class HgClient(VcsClientBase):
shell=True,
cwd=self._path,
us_env=True)
- if (output.strip() != ''
- and not output.startswith("abort")
- or repeated is True):
+ if (output.strip() != '' and
+ not output.startswith("abort") or
+ repeated is True):
matches = [l for l in output.splitlines() if l.startswith('changeset: ')]
if len(matches) == 1:
@@ -237,6 +237,34 @@ class HgClient(VcsClientBase):
# changes, inconsistent to hg log
return output.strip().rstrip('+')
+ def get_current_version_label(self):
+ """
+ :param spec: (optional) spec can be what 'svn info --help'
+ allows, meaning a revnumber, {date}, HEAD, BASE, PREV, or
+ COMMITTED.
+ :returns: current revision number of the repository. Or if spec
+ provided, the number of a revision specified by some
+ token.
+ """
+ return self.get_branch()
+
+ def get_branch(self):
+ if self.path_exists():
+ command = "hg branch --repository %s" % self.get_path()
+ _, output, _ = run_shell_command(command, shell=True)
+ if output is not None:
+ return output.strip()
+ return None
+
+ def get_remote_version(self, fetch=False):
+ if fetch:
+ self._do_pull(filter=True)
+ # use local information only
+ result = self.get_log(limit=1)
+ if (len(result) == 1 and 'id' in result[0]):
+ return result[0]['id']
+ return None
+
def get_diff(self, basepath=None):
response = None
if basepath is None:
@@ -262,10 +290,10 @@ class HgClient(VcsClientBase):
'{autor|email}', '{date|isodate}',
'{desc}']) + '\x1e'
- command = "hg log %s --template '%s' %s" % (sanitized(relpath),
- HG_LOG_FORMAT,
- limit_cmd)
-
+ command = "hg log %s -b %s --template '%s' %s" % (sanitized(relpath),
+ self.get_branch(),
+ HG_LOG_FORMAT,
+ limit_cmd)
return_code, response_str, stderr = run_shell_command(command, shell=True, cwd=self._path)
if return_code == 0:
@@ -318,11 +346,26 @@ class HgClient(VcsClientBase):
os.remove(basepath + '.tar')
return True
- def _do_pull(self):
+ def get_branches(self, local_only=False):
+ if not local_only:
+ self._do_pull()
+ cmd = 'hg branches'
+ result, out, _ = run_shell_command(cmd, shell=True, cwd=self._path,
+ show_stdout=False)
+ if result:
+ return []
+ branches = []
+ for line in out.splitlines():
+ line = line.strip()
+ line = line.split()
+ branches.append(line[0])
+ return branches
+
+ def _do_pull(self, filter=False):
value, _, _ = run_shell_command("hg pull",
cwd=self._path,
shell=True,
- no_filter=True)
+ no_filter=not filter)
return value == 0
# backwards compat
diff --git a/src/vcstools/svn.py b/src/vcstools/svn.py
index 2458c71..e338f39 100755
--- a/src/vcstools/svn.py
+++ b/src/vcstools/svn.py
@@ -34,11 +34,15 @@
svn vcs support.
"""
-
from __future__ import absolute_import, print_function, unicode_literals
import os
import sys
-
+try:
+ # PY3K
+ from urlparse import urlsplit
+except ImportError:
+ from urllib.parse import urlsplit
+import re
import tarfile
import dateutil.parser # For parsing date strings
@@ -49,6 +53,67 @@ from vcstools.common import sanitized, normalized_rel_path, \
run_shell_command, ensure_dir_notexists
+def canonical_svn_url_split(url):
+ """
+ checks url for traces of canonical svn structure,
+ and return root, type, name (of tag or branch), subfolder, query and fragment (see urllib urlparse)
+ This should allow creating a different url for switching to a different tag or branch
+
+ :param url: location of central repo, ``str``
+ :returns: dict {root, type, name, subfolder, query, fragment}
+ with type one of "trunk", "tags", "branches"
+ """
+ result = {'root': url, 'type': None, 'name': None, 'subfolder': None, 'query': None, 'fragment': None}
+ if not url:
+ return result
+ splitresult = urlsplit(url)
+ if not splitresult.scheme:
+ # svn does not accept mere paths
+ return result
+ canonical_pattern = re.compile('(.*/)?(trunk|branches|tags)(/.*)?')
+ matches = canonical_pattern.findall(splitresult.path)
+ if len(matches) > 0:
+ if len(matches) > 1:
+ raise ValueError('Invalid path in url %s' % splitresult.path)
+ prefix, branchtype, rest = matches[0]
+ prefix = prefix.rstrip('/')
+ rest = rest.lstrip('/')
+ if branchtype == 'trunk':
+ result['root'] = '%s://%s%s' % (splitresult.scheme,
+ splitresult.netloc,
+ prefix)
+ result['type'] = branchtype
+ result['query'] = splitresult.query or None
+ result['fragment'] = splitresult.fragment or None
+ if rest:
+ result['subfolder'] = rest
+ elif branchtype in ['tags', 'branches']:
+ result['type'] = branchtype
+ result['root'] = '%s://%s%s' % (splitresult.scheme,
+ splitresult.netloc,
+ prefix)
+ result['query'] = splitresult.query or None
+ result['fragment'] = splitresult.fragment or None
+ if rest:
+ splitrest = rest.split('/', 1)
+ print(splitrest)
+ result['name'] = splitrest[0]
+ if len(splitrest) == 2 and splitrest[1]:
+ result['subfolder'] = splitrest[1]
+ return result
+
+
+def get_remote_contents(url):
+ contents = []
+ if url:
+ cmd = 'svn ls %s' % (url)
+ result_code, output, _ = run_shell_command(cmd, shell=True)
+ if result_code:
+ return []
+ contents = [line.strip('/') for line in output.splitlines()]
+ return contents
+
+
def _get_svn_version():
"""Looks up svn version by calling svn --version.
:raises: VcsError if svn is not installed"""
@@ -60,11 +125,11 @@ def _get_svn_version():
if value == 0 and output is not None and len(output.splitlines()) > 0:
version = output.splitlines()[0]
else:
- raise VcsError("svn --version returned "
- + "%s maybe svn is not installed" % value)
+ raise VcsError("svn --version returned " +
+ "%s maybe svn is not installed" % value)
except VcsError as exc:
- raise VcsError("Could not determine whether svn is installed: "
- + str(exc))
+ raise VcsError("Could not determine whether svn is installed: " +
+ str(exc))
return version
@@ -154,10 +219,25 @@ class SvnClient(VcsClientBase):
:param spec: (optional) spec can be what 'svn info --help'
allows, meaning a revnumber, {date}, HEAD, BASE, PREV, or
COMMITTED.
+ :param path: the url to use, default is for this repo
+ :returns: current revision number of the repository. Or if spec
+ provided, the number of a revision specified by some
+ token.
+ """
+ return self._get_version_from_path(spec=spec, path=self._path)
+
+ def _get_version_from_path(self, spec=None, path=None):
+ """
+ :param spec: (optional) spec can be what 'svn info --help'
+ allows, meaning a revnumber, {date}, HEAD, BASE, PREV, or
+ COMMITTED.
+ :param path: the url to use, default is for this repo
:returns: current revision number of the repository. Or if spec
provided, the number of a revision specified by some
token.
"""
+ if not self.path_exists():
+ return None
command = 'svn info '
if spec is not None:
if spec.isdigit():
@@ -178,18 +258,27 @@ class SvnClient(VcsClientBase):
command += sanitized(spec)
else:
command += sanitized('-r%s' % spec)
- command += " %s" % self._path
+ command += " %s" % path
# #3305: parsing not robust to non-US locales
_, output, _ = run_shell_command(command, shell=True, us_env=True)
if output is not None:
matches = \
- [l for l in output.splitlines() if l.startswith('Revision: ')]
+ [l for l in output.splitlines() if l.startswith('Last Changed Rev: ')]
if len(matches) == 1:
split_str = matches[0].split()
- if len(split_str) == 2:
- return '-r' + split_str[1]
+ if len(split_str) == 4:
+ return '-r' + split_str[3]
return None
+ def get_current_version_label(self):
+ # SVN branches are part or URL
+ return None
+
+ def get_remote_version(self, fetch=False):
+ if fetch is False:
+ return None
+ return self._get_version_from_path(path=self.get_url())
+
def get_diff(self, basepath=None):
response = None
if basepath is None:
@@ -277,5 +366,19 @@ class SvnClient(VcsClientBase):
rmtree(basepath)
return True
+ def get_branches(self, local_only=False):
+ url = self.get_url()
+ canonical_dict = canonical_svn_url_split(url)
+ if local_only:
+ if canonical_dict['type'] == 'branches':
+ return [canonical_dict['name']]
+ return []
+
+ branches = []
+ if canonical_dict['type']:
+ branches = get_remote_contents('%s/%s' % (canonical_dict['root'], 'branches'))
+
+ return branches
+
SVNClient = SvnClient
diff --git a/src/vcstools/tar.py b/src/vcstools/tar.py
index 3b619e8..85fe9d4 100644
--- a/src/vcstools/tar.py
+++ b/src/vcstools/tar.py
@@ -169,6 +169,14 @@ class TarClient(VcsClientBase):
return metadata['version']
return None
+ def get_current_version_label(self):
+ # exploded tar has no local version
+ return None
+
+ def get_remote_version(self, fetch=False):
+ # exploded tar has no remote version (not a required feature)
+ return None
+
def get_diff(self, basepath=None):
return ''
diff --git a/src/vcstools/vcs_abstraction.py b/src/vcstools/vcs_abstraction.py
index 746cd96..af51799 100644
--- a/src/vcstools/vcs_abstraction.py
+++ b/src/vcstools/vcs_abstraction.py
@@ -101,6 +101,12 @@ class VcsClient(object):
def get_version(self, spec=None):
return self.vcs.get_version(spec)
+ def get_current_version_label(self):
+ return self.vcs.get_current_version_label()
+
+ def get_remote_version(self, fetch=False):
+ return self.vcs.get_remote_version(fetch)
+
def checkout(self, url, version='', verbose=False, shallow=False):
return self.vcs.checkout(url,
version,
@@ -134,5 +140,9 @@ class VcsClient(object):
def export_repository(self, version, basepath):
return self.vcs.export_repository(version, basepath)
+ def get_branches(self, local_only=False):
+ return self.vcs.get_branches(local_only)
+
+
# backwards compat
VCSClient = VcsClient
diff --git a/src/vcstools/vcs_base.py b/src/vcstools/vcs_base.py
index 42343af..901406b 100644
--- a/src/vcstools/vcs_base.py
+++ b/src/vcstools/vcs_base.py
@@ -130,6 +130,31 @@ class VcsClientBase(object):
raise NotImplementedError("Base class get_version method must be overridden for client type %s " %
self._vcs_type_name)
+ def get_current_version_label(self):
+ """
+ Find an description for the current local version.
+ Token spec might be a branchname,
+ version-id, SHA-ID, ... depending on the VCS implementation.
+
+ :returns: short description of local version (e.g. branchname, tagename).
+ :rtype: str
+ """
+ raise NotImplementedError("Base class get_current_version method must be overridden for client type %s " %
+ self._vcs_type_name)
+
+ def get_remote_version(self, fetch=False):
+ """
+ Find an identifier for the current revision on remote.
+ Token spec might be a tagname,
+ version-id, SHA-ID, ... depending on the VCS implementation.
+
+ :param fetch: if False, only local information may be used
+ :returns: current revision number of the remote repository.
+ :rtype: str
+ """
+ raise NotImplementedError("Base class get_remote_version method must be overridden for client type %s " %
+ self._vcs_type_name)
+
def checkout(self, url, version=None, verbose=False, shallow=False, timeout=None):
"""
Attempts to create a local repository given a remote
@@ -256,3 +281,13 @@ class VcsClientBase(object):
"""
raise NotImplementedError("Base class export_repository method must be overridden for client type %s " %
self._vcs_type_name)
+
+ def get_branches(self, local_only=False):
+ """
+ Returns a list of all branches in the vcs repository.
+
+ :param local_only: if True it will only list local branches
+ :returns: list of branches in the repository, [] if none exist
+ """
+ raise NotImplementedError("Base class get_branches method must "
+ "be overridden")
diff --git a/test/test_git.py b/test/test_git.py
index b250f8f..4da4f84 100644
--- a/test/test_git.py
+++ b/test/test_git.py
@@ -43,6 +43,7 @@ import types
import threading
import time
+from distutils.version import LooseVersion
from vcstools import GitClient
from vcstools.vcs_base import VcsError
@@ -143,7 +144,7 @@ class GitClientTest(GitClientTestSetups):
self.assertEqual(client.get_path(), self.local_path)
self.assertEqual(client.get_url(), url)
self.assertEqual(client.get_branch(), "master")
- self.assertEqual(client.get_branch_parent(), "master")
+ self.assertEqual(client.get_branch_parent(), ("master", "origin"))
#self.assertEqual(client.get_version(), '-r*')
def test_checkout_dir_exists(self):
@@ -234,11 +235,15 @@ class GitClientTest(GitClientTestSetups):
self.assertEqual(client.get_path(), self.local_path)
self.assertEqual(client.get_url(), url)
self.assertEqual(client.get_branch(), "master")
- self.assertEqual(client.get_branch_parent(), "master")
+ self.assertEqual(client.get_branch_parent(), ("master", "origin"))
po = subprocess.Popen("git log --pretty=format:%H", shell=True, cwd=self.local_path, stdout=subprocess.PIPE)
- log = po.stdout.read().decode('UTF-8').splitlines()
- # shallow only contains last 2 commits
- self.assertEqual(2, len(log), log)
+ log = po.stdout.read().decode('UTF-8').strip().splitlines()
+ if LooseVersion(client.gitversion) >= LooseVersion('1.8.2'):
+ # shallow only contains last commit
+ self.assertEqual(1, len(log), log)
+ else:
+ # shallow only contains last 2 commits
+ self.assertEqual(2, len(log), log)
def test_checkout_specific_version_and_update(self):
url = self.remote_path
@@ -269,10 +274,10 @@ class GitClientTest(GitClientTestSetups):
self.assertTrue(client.detect_presence())
self.assertEqual(client.get_path(), self.local_path)
self.assertEqual(client.get_url(), url)
- self.assertEqual(client.get_branch_parent(), branch)
+ self.assertEqual(client.get_branch_parent(), (branch, "origin"))
self.assertTrue(client.update(branch))
- self.assertEqual(client.get_branch_parent(), branch)
+ self.assertEqual(client.get_branch_parent(), (branch, "origin"))
def test_checkout_specific_branch_and_update(self):
# subdir = "checkout_specific_version_test"
@@ -289,22 +294,22 @@ class GitClientTest(GitClientTestSetups):
self.assertEqual(client.get_url(), url)
self.assertEqual(client.get_version(), self.readonly_version_init)
self.assertEqual(client.get_branch(), branch)
- self.assertEqual(client.get_branch_parent(), branch)
+ self.assertEqual(client.get_branch_parent(), (branch, "origin"))
self.assertTrue(client.update()) # no arg
self.assertEqual(client.get_branch(), branch)
self.assertEqual(client.get_version(), self.readonly_version_init)
- self.assertEqual(client.get_branch_parent(), branch)
+ self.assertEqual(client.get_branch_parent(), (branch, "origin"))
self.assertTrue(client.update(branch)) # same branch arg
self.assertEqual(client.get_branch(), branch)
self.assertEqual(client.get_version(), self.readonly_version_init)
- self.assertEqual(client.get_branch_parent(), branch)
+ self.assertEqual(client.get_branch_parent(), (branch, "origin"))
new_branch = 'master'
self.assertTrue(client.update(new_branch))
self.assertEqual(client.get_branch(), new_branch)
- self.assertEqual(client.get_branch_parent(), new_branch)
+ self.assertEqual(client.get_branch_parent(), (new_branch, "origin"))
def test_checkout_local_only_branch_and_update(self):
# prevent regression on wstool#25: no rebase after switching branch
@@ -324,7 +329,7 @@ class GitClientTest(GitClientTestSetups):
self.assertTrue(client.update(branch)) # same branch arg
self.assertEqual(client.get_branch(), branch)
self.assertEqual(client.get_version(), self.readonly_version)
- self.assertEqual(client.get_branch_parent(), branch)
+ self.assertEqual(client.get_branch_parent(), (branch, "origin"))
def test_checkout_specific_tag_and_update(self):
@@ -338,14 +343,14 @@ class GitClientTest(GitClientTestSetups):
self.assertTrue(client.detect_presence())
self.assertEqual(client.get_path(), self.local_path)
self.assertEqual(client.get_url(), url)
- self.assertEqual(client.get_branch_parent(), None)
+ self.assertEqual(client.get_branch_parent(), (None, None))
tag = "test_tag"
self.assertTrue(client.update(tag))
- self.assertEqual(client.get_branch_parent(), None)
+ self.assertEqual(client.get_branch_parent(), (None, None))
new_branch = 'master'
self.assertTrue(client.update(new_branch))
- self.assertEqual(client.get_branch_parent(), new_branch)
+ self.assertEqual(client.get_branch_parent(), (new_branch, "origin"))
tag = "test_tag"
self.assertTrue(client.update(tag))
@@ -375,7 +380,90 @@ class GitClientTest(GitClientTestSetups):
# replace "refs/head/master" with just "master"
subprocess.check_call("git config --replace-all branch.master.merge master", shell=True, cwd=self.local_path)
- self.assertTrue(client.get_branch_parent() is not None)
+ self.assertTrue(client.get_branch_parent() is not (None, None))
+
+ def test_get_version_not_exist(self):
+ client = GitClient(path=self.local_path)
+ client.checkout(url=self.remote_path, version='master')
+ self.assertEqual(client.get_version(spec='not_exist_version'), None)
+
+ def test_get_branch_parent(self):
+ client = GitClient(path=self.local_path)
+ client.checkout(url=self.remote_path, version='master')
+ self.assertEqual(client.get_branch_parent(), ("master", "origin"))
+
+ # with other remote than origin
+ for cmd in ['git remote add remote2 %s' % self.remote_path,
+ 'git config --replace-all branch.master.remote remote2']:
+ subprocess.check_call(cmd, shell=True, cwd=self.local_path)
+ self.assertEqual(client.get_branch_parent(), (None, None))
+ self.assertEqual(client.get_branch_parent(fetch=True), ('master', "remote2"))
+ # with not actual remote branch
+ cmd = 'git config --replace-all branch.master.merge dummy_branch'
+ subprocess.check_call(cmd, shell=True, cwd=self.local_path)
+ self.assertEqual(client.get_branch_parent(), (None, None))
+ # return remote back to original config
+ for cmd in [
+ 'git config --replace-all branch.master.remote origin',
+ 'git config --replace-all branch.master.merge refs/heads/master']:
+ subprocess.check_call(cmd, shell=True, cwd=self.local_path)
+
+ # with detached local status
+ client.update(version='test_tag')
+ self.assertEqual(client.get_branch_parent(), (None, None))
+ # back to master branch
+ client.update(version='master')
+
+ def test_get_current_version_label(self):
+ client = GitClient(path=self.local_path)
+ # with detached local status
+ client.checkout(url=self.remote_path, version='test_tag')
+ self.assertEqual(client.get_current_version_label(), '<detached>')
+ # when difference between local and tracking branch
+ client.update(version='master')
+ self.assertEqual(client.get_current_version_label(), 'master')
+ # with other tracking branch
+ cmd = 'git config --replace-all branch.master.merge test_branch'
+ subprocess.check_call(cmd, shell=True, cwd=self.local_path)
+ self.assertEqual(client.get_current_version_label(),
+ 'master < test_branch')
+ # with other remote
+ for cmd in [
+ 'git remote add remote2 %s' % self.remote_path,
+ 'git config --replace-all branch.master.remote remote2',
+ 'git fetch remote2']:
+ subprocess.check_call(cmd, shell=True, cwd=self.local_path)
+ self.assertEqual(client.get_current_version_label(),
+ 'master < remote2/test_branch')
+ # return remote back to original config
+ for cmd in [
+ 'git config --replace-all branch.master.remote origin',
+ 'git config --replace-all branch.master.merge refs/heads/master']:
+ subprocess.check_call(cmd, shell=True, cwd=self.local_path)
+
+ def test_get_remote_version(self):
+ url = self.remote_path
+ client = GitClient(path=self.local_path)
+ client.checkout(url, version='master')
+ self.assertEqual(client.get_remote_version(fetch=True), self.readonly_version)
+ self.assertEqual(client.get_remote_version(fetch=False), self.readonly_version)
+ subprocess.check_call("git reset --hard test_tag", shell=True, cwd=self.local_path)
+ self.assertEqual(client.get_remote_version(fetch=True), self.readonly_version)
+ client.update(version='test_branch')
+ self.assertEqual(client.get_remote_version(fetch=True), self.readonly_version_init)
+ client.update(version='test_branch')
+ self.assertEqual(client.get_remote_version(fetch=False), self.readonly_version_init)
+ # switch tracked branch
+ subprocess.check_call('git config --replace-all branch.master.merge test_branch', shell=True, cwd=self.local_path)
+ client.update(version='master')
+ self.assertEqual(client.get_remote_version(fetch=False), self.readonly_version_init)
+ # with other remote
+ for cmd in [
+ 'git remote add remote2 %s' % self.remote_path,
+ 'git config --replace-all branch.master.remote remote2',
+ 'git fetch remote2']:
+ subprocess.check_call(cmd, shell=True, cwd=self.local_path)
+ self.assertEqual(client.get_remote_version(fetch=False), self.readonly_version_init)
def testDiffClean(self):
client = GitClient(self.remote_path)
@@ -436,6 +524,31 @@ test_tag
''', output)
+class GitClientRemoteVersionFetchTest(GitClientTestSetups):
+
+ def test_update_fetch_all_tags(self):
+ url = self.remote_path
+ client = GitClient(self.local_path)
+ self.assertTrue(client.checkout(url, "master"))
+ self.assertEqual(client.get_branch(), "master")
+ self.assertEqual(client.get_remote_version(fetch=False), self.readonly_version)
+ self.assertEqual(client.get_remote_version(fetch=True), self.readonly_version)
+
+ subprocess.check_call("touch new_file.txt", shell=True, cwd=self.remote_path)
+ subprocess.check_call("git add *", shell=True, cwd=self.remote_path)
+ subprocess.check_call("git commit -m newfile", shell=True, cwd=self.remote_path)
+
+ po = subprocess.Popen("git log -n 1 --pretty=format:\"%H\"", shell=True, cwd=self.remote_path, stdout=subprocess.PIPE)
+ remote_new_version = po.stdout.read().decode('UTF-8').rstrip('"').lstrip('"')
+
+ self.assertNotEqual(self.readonly_version, remote_new_version)
+
+ # remote version stays same until we fetch
+ self.assertEqual(client.get_remote_version(fetch=False), self.readonly_version)
+ self.assertEqual(client.get_remote_version(fetch=True), remote_new_version)
+ self.assertEqual(client.get_remote_version(fetch=False), remote_new_version)
+
+
class GitClientLogTest(GitClientTestSetups):
def setUp(self):
@@ -537,19 +650,19 @@ class GitClientDanglingCommitsTest(GitClientTestSetups):
tag = "no_br_tag"
self.assertTrue(client.update(tag))
self.assertEqual(client.get_branch(), None)
- self.assertEqual(client.get_branch_parent(), None)
+ self.assertEqual(client.get_branch_parent(), (None, None))
tag = "test_tag"
self.assertTrue(client.update(tag))
self.assertEqual(client.get_branch(), None)
- self.assertEqual(client.get_branch_parent(), None)
+ self.assertEqual(client.get_branch_parent(), (None, None))
# to dangling commit
sha = self.dangling_version
self.assertTrue(client.update(sha))
self.assertEqual(client.get_branch(), None)
self.assertEqual(client.get_version(), self.dangling_version)
- self.assertEqual(client.get_branch_parent(), None)
+ self.assertEqual(client.get_branch_parent(), (None, None))
# now HEAD protects the dangling commit, should not be allowed to move off.
new_branch = 'master'
@@ -562,25 +675,25 @@ class GitClientDanglingCommitsTest(GitClientTestSetups):
tag = "no_br_tag"
self.assertTrue(client.update(tag))
self.assertEqual(client.get_branch(), None)
- self.assertEqual(client.get_branch_parent(), None)
+ self.assertEqual(client.get_branch_parent(), (None, None))
tag = "test_tag"
self.assertTrue(client.update(tag))
self.assertEqual(client.get_branch(), None)
self.assertEqual(client.get_version(), self.readonly_version_init)
- self.assertEqual(client.get_branch_parent(), None)
+ self.assertEqual(client.get_branch_parent(), (None, None))
#update should not change anything
self.assertTrue(client.update()) # no arg
self.assertEqual(client.get_branch(), None)
self.assertEqual(client.get_version(), self.readonly_version_init)
- self.assertEqual(client.get_branch_parent(), None)
+ self.assertEqual(client.get_branch_parent(), (None, None))
new_branch = 'master'
self.assertTrue(client.update(new_branch))
self.assertEqual(client.get_branch(), new_branch)
self.assertEqual(client.get_version(), self.readonly_version)
- self.assertEqual(client.get_branch_parent(), new_branch)
+ self.assertEqual(client.get_branch_parent(), (new_branch, "origin"))
def test_checkout_untracked_branch_and_update(self):
# difference to tracked branches is that branch parent is None, and we may hop outside lineage
@@ -596,37 +709,37 @@ class GitClientDanglingCommitsTest(GitClientTestSetups):
self.assertTrue(client.update(branch))
self.assertEqual(client.get_version(), self.untracked_version)
self.assertEqual(client.get_branch(), branch)
- self.assertEqual(client.get_branch_parent(), None)
+ self.assertEqual(client.get_branch_parent(), (None, None))
self.assertTrue(client.update()) # no arg
self.assertEqual(client.get_branch(), branch)
self.assertEqual(client.get_version(), self.untracked_version)
- self.assertEqual(client.get_branch_parent(), None)
+ self.assertEqual(client.get_branch_parent(), (None, None))
self.assertTrue(client.update(branch)) # same branch arg
self.assertEqual(client.get_branch(), branch)
self.assertEqual(client.get_version(), self.untracked_version)
- self.assertEqual(client.get_branch_parent(), None)
+ self.assertEqual(client.get_branch_parent(), (None, None))
# to master
new_branch = 'master'
self.assertTrue(client.update(new_branch))
self.assertEqual(client.get_branch(), new_branch)
self.assertEqual(client.get_version(), self.readonly_version)
- self.assertEqual(client.get_branch_parent(), new_branch)
+ self.assertEqual(client.get_branch_parent(), (new_branch, "origin"))
# and back
self.assertTrue(client.update(branch)) # same branch arg
self.assertEqual(client.get_branch(), branch)
self.assertEqual(client.get_version(), self.untracked_version)
- self.assertEqual(client.get_branch_parent(), None)
+ self.assertEqual(client.get_branch_parent(), (None, None))
# to dangling commit
sha = self.dangling_version
self.assertTrue(client.update(sha))
self.assertEqual(client.get_branch(), None)
self.assertEqual(client.get_version(), self.dangling_version)
- self.assertEqual(client.get_branch_parent(), None)
+ self.assertEqual(client.get_branch_parent(), (None, None))
#should not work to protect commits from becoming dangled
# to commit outside lineage
@@ -765,11 +878,37 @@ class GitExportClientTest(GitClientTestSetups):
self.assertFalse(os.path.exists(self.basepath_export))
+class GitGetBranchesClientTest(GitClientTestSetups):
+
+ @classmethod
+ def setUpClass(self):
+ GitClientTestSetups.setUpClass()
+
+ def tearDown(self):
+ pass
+
+ def testGetBranches(self):
+ client = GitClient(self.local_path)
+ client.checkout(self.remote_path)
+ self.assertEqual(client.get_branches(True), ['master'])
+ self.assertEqual(client.get_branches(),
+ ['master', 'remotes/origin/master',
+ 'remotes/origin/test_branch'])
+ subprocess.check_call('git checkout test_branch', shell=True,
+ cwd=self.local_path, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ self.assertEqual(client.get_branches(True), ['master', 'test_branch'])
+ self.assertEqual(client.get_branches(),
+ ['master', 'test_branch', 'remotes/origin/master',
+ 'remotes/origin/test_branch'])
+
class GitTimeoutTest(unittest.TestCase):
class MuteHandler(BaseRequestHandler):
def handle(self):
- self.request.recv(1024)
+ data = True
+ while data:
+ data = self.request.recv(1024)
@classmethod
def setUpClass(self):
@@ -785,9 +924,10 @@ class GitTimeoutTest(unittest.TestCase):
def test_checkout_timeout(self):
## SSH'ing to a mute server will hang for a very long time
- url = 'ssh://test@localhost:{0}/test'.format(self.mute_port)
+ url = 'ssh://test@127.0.0.1:{0}/test'.format(self.mute_port)
client = GitClient(self.local_path)
start = time.time()
+
self.assertFalse(client.checkout(url, timeout=2.0))
stop = time.time()
self.assertTrue(stop - start > 1.9)
@@ -806,4 +946,3 @@ class GitTimeoutTest(unittest.TestCase):
def tearDown(self):
if os.path.exists(self.local_path):
shutil.rmtree(self.local_path)
-
diff --git a/test/test_hg.py b/test/test_hg.py
index 7835f72..6770112 100644
--- a/test/test_hg.py
+++ b/test/test_hg.py
@@ -54,10 +54,12 @@ class HGClientTestSetups(unittest.TestCase):
os.makedirs(self.remote_path)
# create a "remote" repo
- subprocess.check_call("hg init", shell=True, cwd=self.remote_path)
- subprocess.check_call("touch fixed.txt", shell=True, cwd=self.remote_path)
- subprocess.check_call("hg add fixed.txt", shell=True, cwd=self.remote_path)
- subprocess.check_call("hg commit -m initial", shell=True, cwd=self.remote_path)
+ for cmd in [
+ "hg init",
+ "touch fixed.txt",
+ "hg add fixed.txt",
+ "hg commit -m initial"]:
+ subprocess.check_call(cmd, shell=True, cwd=self.remote_path)
po = subprocess.Popen("hg log --template '{node|short}' -l1", shell=True, cwd=self.remote_path, stdout=subprocess.PIPE)
self.local_version_init = po.stdout.read().decode('UTF-8').rstrip("'").lstrip("'")
@@ -65,23 +67,41 @@ class HGClientTestSetups(unittest.TestCase):
subprocess.check_call("hg tag test_tag", shell=True, cwd=self.remote_path)
# files to be modified in "local" repo
- subprocess.check_call("touch modified.txt", shell=True, cwd=self.remote_path)
- subprocess.check_call("touch modified-fs.txt", shell=True, cwd=self.remote_path)
- subprocess.check_call("hg add modified.txt modified-fs.txt", shell=True, cwd=self.remote_path)
- subprocess.check_call("hg commit -m initial", shell=True, cwd=self.remote_path)
+ for cmd in [
+ "touch modified.txt",
+ "touch modified-fs.txt",
+ "hg add modified.txt modified-fs.txt",
+ "hg commit -m initial"]:
+ subprocess.check_call(cmd, shell=True, cwd=self.remote_path)
+
po = subprocess.Popen("hg log --template '{node|short}' -l1", shell=True, cwd=self.remote_path, stdout=subprocess.PIPE)
self.local_version_second = po.stdout.read().decode('UTF-8').rstrip("'").lstrip("'")
- subprocess.check_call("touch deleted.txt", shell=True, cwd=self.remote_path)
- subprocess.check_call("touch deleted-fs.txt", shell=True, cwd=self.remote_path)
- subprocess.check_call("hg add deleted.txt deleted-fs.txt", shell=True, cwd=self.remote_path)
- subprocess.check_call("hg commit -m modified", shell=True, cwd=self.remote_path)
+ for cmd in [
+ "touch deleted.txt",
+ "touch deleted-fs.txt",
+ "hg add deleted.txt deleted-fs.txt",
+ "hg commit -m modified"]:
+ subprocess.check_call(cmd, shell=True, cwd=self.remote_path)
+
po = subprocess.Popen("hg log --template '{node|short}' -l1", shell=True, cwd=self.remote_path, stdout=subprocess.PIPE)
self.local_version = po.stdout.read().decode('UTF-8').rstrip("'").lstrip("'")
self.local_path = os.path.join(self.root_directory, "local")
self.local_url = self.remote_path
+ # create a hg branch
+ for cmd in [
+ "hg branch test_branch",
+ "touch test.txt",
+ "hg add test.txt",
+ "hg commit -m test"]:
+ subprocess.check_call(cmd, shell=True, cwd=self.remote_path)
+
+ po = subprocess.Popen("hg log --template '{node|short}' -l1", shell=True, cwd=self.remote_path, stdout=subprocess.PIPE)
+ self.branch_version = po.stdout.read().decode('UTF-8').rstrip("'").lstrip("'")
+
+
@classmethod
def tearDownClass(self):
for d in self.directories:
@@ -183,6 +203,31 @@ class HGClientTest(HGClientTestSetups):
self.assertTrue(client.update(''))
self.assertEqual(client.get_version(), self.local_version)
+ def test_get_current_version_label(self):
+ url = self.local_url
+ version = self.local_version
+ client = HgClient(self.local_path)
+ client.checkout(url, version='test_tag')
+ self.assertEqual(client.get_current_version_label(), 'default')
+ client.update(version='default')
+ self.assertEqual(client.get_current_version_label(), 'default')
+ client.update(version='test_branch')
+ self.assertEqual(client.get_current_version_label(), 'test_branch')
+
+
+ def test_get_remote_version(self):
+ url = self.local_url
+ version = self.local_version
+ client = HgClient(self.local_path)
+ client.checkout(url)
+ self.assertEqual(client.get_remote_version(fetch=True), self.local_version)
+ client.checkout(url, version='test_tag')
+ self.assertEqual(client.get_remote_version(fetch=True), self.local_version)
+ client.update(version='default')
+ self.assertEqual(client.get_remote_version(fetch=True), self.local_version)
+ client.update(version='test_branch')
+ self.assertEqual(client.get_remote_version(fetch=True), self.branch_version)
+
def testDiffClean(self):
client = HgClient(self.remote_path)
self.assertEquals('', client.get_diff())
@@ -301,6 +346,32 @@ class HGDiffStatClientTest(HGClientTestSetups):
self.assertEqual(_hg_diff_path_change(None, '/tmp/dummy'), None)
+class HGRemoteFetchTest(HGClientTestSetups):
+
+ def test_get_remote_version(self):
+ url = self.local_url
+ version = self.local_version
+ client = HgClient(self.local_path)
+ client.checkout(url, version='default')
+ self.assertEqual(client.get_remote_version(fetch=True), self.local_version)
+ self.assertEqual(client.get_version(), self.local_version)
+
+ for cmd in [
+ "hg checkout default",
+ "touch remote_new.txt",
+ "hg add remote_new.txt",
+ "hg commit -m remote_new"]:
+ subprocess.check_call(cmd, shell=True, cwd=self.remote_path)
+ po = subprocess.Popen("hg log --template '{node|short}' -l1", shell=True, cwd=self.remote_path, stdout=subprocess.PIPE)
+ remote_new_version = po.stdout.read().decode('UTF-8').rstrip("'").lstrip("'")
+ self.assertNotEqual(self.local_version, remote_new_version)
+
+ self.assertEqual(client.get_remote_version(fetch=False), self.local_version)
+ self.assertEqual(client.get_remote_version(fetch=True), remote_new_version)
+ self.assertEqual(client.get_remote_version(fetch=False), remote_new_version)
+ self.assertEqual(client.get_version(), self.local_version)
+
+
class HGExportRepositoryClientTest(HGClientTestSetups):
@classmethod
@@ -324,3 +395,37 @@ class HGExportRepositoryClientTest(HGClientTestSetups):
self.assertTrue(os.path.exists(self.basepath_export + '.tar.gz'))
self.assertFalse(os.path.exists(self.basepath_export + '.tar'))
self.assertFalse(os.path.exists(self.basepath_export))
+
+
+class HGGetBranchesClientTest(HGClientTestSetups):
+
+ @classmethod
+ def setUpClass(self):
+ HGClientTestSetups.setUpClass()
+ url = self.local_url
+ client = HgClient(self.local_path)
+ client.checkout(url)
+
+ def tearDown(self):
+ pass
+
+ def test_get_branches(self):
+ client = HgClient(self.local_path)
+ # Make a local branch
+ subprocess.check_call('hg branch test_branch2', shell=True,
+ cwd=self.local_path, stdout=subprocess.PIPE)
+ subprocess.check_call('hg commit -m "Making test_branch2"', shell=True,
+ cwd=self.local_path, stdout=subprocess.PIPE)
+ self.assertEqual(client.get_branches(), ['test_branch2', 'test_branch', 'default'])
+
+ # Make a remote branch
+ subprocess.check_call('hg branch remote_branch', shell=True,
+ cwd=self.remote_path, stdout=subprocess.PIPE)
+ subprocess.check_call("touch fixed.txt", shell=True,
+ cwd=self.remote_path)
+ subprocess.check_call("hg add fixed.txt", shell=True,
+ cwd=self.remote_path)
+ subprocess.check_call("hg commit -m initial", shell=True,
+ cwd=self.remote_path)
+ self.assertEqual(client.get_branches(local_only=True), ['test_branch2', 'test_branch', 'default'])
+ self.assertEqual(client.get_branches(), ['remote_branch', 'test_branch2', 'test_branch', 'default'])
diff --git a/test/test_svn.py b/test/test_svn.py
index 342a441..0c661d8 100644
--- a/test/test_svn.py
+++ b/test/test_svn.py
@@ -40,7 +40,72 @@ import subprocess
import tempfile
import shutil
import re
-from vcstools.svn import SvnClient
+from vcstools.svn import SvnClient, canonical_svn_url_split, get_remote_contents
+
+
+class SvnClientUtilTest(unittest.TestCase):
+
+ def test_canonical_svn_url_split(self):
+ self.assertEqual({'root': 'foo',
+ 'type': None,
+ 'name': None, 'subfolder': None,
+ 'query': None, 'fragment': None},
+ canonical_svn_url_split('foo'))
+ self.assertEqual({'root': None,
+ 'type': None,
+ 'name': None, 'subfolder': None,
+ 'query': None, 'fragment': None},
+ canonical_svn_url_split(None))
+ self.assertEqual({'root': 'svn://gcc.gnu.org/svn/gcc',
+ 'type': 'branches',
+ 'name': 'foo', 'subfolder': None,
+ 'query': None, 'fragment': None},
+ canonical_svn_url_split('svn://gcc.gnu.org/svn/gcc/branches/foo'))
+ self.assertEqual({'root': 'svn://gcc.gnu.org/svn/gcc',
+ 'type': 'branches',
+ 'name': 'foo', 'subfolder': None,
+ 'query': None, 'fragment': None},
+ canonical_svn_url_split('svn://gcc.gnu.org/svn/gcc/branches/foo/'))
+ self.assertEqual({'root': 'svn://gcc.gnu.org/svn/gcc',
+ 'type': 'branches',
+ 'name': 'foo', 'subfolder': 'sub/bar',
+ 'query': None, 'fragment': None},
+ canonical_svn_url_split('svn://gcc.gnu.org/svn/gcc/branches/foo/sub/bar'))
+ self.assertEqual({'root': 'svn://gcc.gnu.org/svn/gcc',
+ 'type': 'trunk',
+ 'name': None, 'subfolder': None,
+ 'query': None, 'fragment': None},
+ canonical_svn_url_split('svn://gcc.gnu.org/svn/gcc/trunk'))
+ self.assertEqual({'root': 'svn://gcc.gnu.org/svn/gcc',
+ 'type': 'trunk',
+ 'name': None, 'subfolder': 'sub',
+ 'query': None, 'fragment': None},
+ canonical_svn_url_split('svn://gcc.gnu.org/svn/gcc/trunk/sub'))
+ self.assertEqual({'root': 'svn://gcc.gnu.org/svn/gcc',
+ 'type': 'trunk',
+ 'name': None, 'subfolder': 'sub/foo',
+ 'query': None, 'fragment': None},
+ canonical_svn_url_split('svn://gcc.gnu.org/svn/gcc/trunk/sub/foo'))
+ self.assertEqual({'root': 'svn://gcc.gnu.org/svn/gcc',
+ 'type': 'tags',
+ 'name': '1.2.3', 'subfolder': None,
+ 'query': None, 'fragment': None},
+ canonical_svn_url_split('svn://gcc.gnu.org/svn/gcc/tags/1.2.3'))
+ self.assertEqual({'root': 'svn://gcc.gnu.org/svn/gcc',
+ 'type': 'tags',
+ 'name': '1.2.3', 'subfolder': 'sub/foo',
+ 'query': None, 'fragment': None},
+ canonical_svn_url_split('svn://gcc.gnu.org/svn/gcc/tags/1.2.3/sub/foo'))
+ self.assertEqual({'root': 'file://localhost/svn/gcc',
+ 'type': 'tags',
+ 'name': '1.2.3', 'subfolder': 'sub/foo',
+ 'query': None, 'fragment': None},
+ canonical_svn_url_split('file://localhost/svn/gcc/tags/1.2.3/sub/foo'))
+ self.assertEqual({'root': 'https://frodo@gcc.gnu.org/svn/gcc',
+ 'type': 'tags',
+ 'name': '1.2.3', 'subfolder': 'sub/foo',
+ 'query': 'pw=guest', 'fragment': 'today'},
+ canonical_svn_url_split('https://frodo@gcc.gnu.org/svn/gcc/tags/1.2.3/sub/foo?pw=guest#today'))
class SvnClientTestSetups(unittest.TestCase):
@@ -88,6 +153,18 @@ class SvnClientTestSetups(unittest.TestCase):
"svn commit -m modified"]:
subprocess.check_call(cmd, shell=True, cwd=self.init_path)
+ self.local_version_master = "-r3"
+
+ # files to be modified in "local" repo
+ for cmd in [
+ "mkdir branches/foo",
+ "touch branches/foo/modified.txt",
+ "svn add branches/foo",
+ "svn commit -m 'foo branch'"]:
+ subprocess.check_call(cmd, shell=True, cwd=self.init_path)
+ self.branch_url = self.local_root_url + "/branches/foo"
+ self.local_version_foo_branch = "-r4"
+
self.local_path = os.path.join(self.root_directory, "local")
@classmethod
@@ -108,7 +185,7 @@ class SvnClientTest(SvnClientTestSetups):
self.assertTrue(client.path_exists())
self.assertTrue(client.detect_presence())
self.assertEqual(self.local_url, client.get_url())
- #self.assertEqual(client.get_version(), self.local_version)
+ self.assertEqual(client.get_version(), self.local_version_master)
self.assertEqual(client.get_version("PREV"), "-r2")
self.assertEqual(client.get_version("2"), "-r2")
self.assertEqual(client.get_version("-r2"), "-r2")
@@ -176,6 +253,25 @@ class SvnClientTest(SvnClientTestSetups):
self.assertTrue(client.update(new_version))
self.assertEqual(client.get_version(), "-r2")
+ def test_get_remote_version(self):
+ url = self.local_url
+ client = SvnClient(self.local_path)
+ client.checkout(url)
+ self.assertEqual(client.get_remote_version(fetch=True),
+ self.local_version_master)
+ self.assertEqual(client.get_remote_version(fetch=False),
+ None)
+
+ def test_get_remote_branch_version(self):
+ url = self.branch_url
+ client = SvnClient(self.local_path)
+ client.checkout(url)
+ self.assertEqual(client.get_remote_version(fetch=True),
+ self.local_version_foo_branch)
+ self.assertEqual(client.get_remote_version(fetch=False),
+ None)
+
+
def testDiffClean(self):
client = SvnClient(self.remote_path)
self.assertEquals('', client.get_diff())
@@ -352,3 +448,59 @@ class SvnExportRepositoryClientTest(SvnClientTestSetups):
self.assertTrue(os.path.exists(self.basepath_export + '.tar.gz'))
self.assertFalse(os.path.exists(self.basepath_export + '.tar'))
self.assertFalse(os.path.exists(self.basepath_export))
+
+
+class SvnGetBranchesClientTest(SvnClientTestSetups):
+
+ @classmethod
+ def setUpClass(self):
+ SvnClientTestSetups.setUpClass()
+ client = SvnClient(self.local_path)
+ client.checkout(self.local_url)
+
+ # def tearDown(self):
+ # pass
+
+ def test_get_remote_contents(self):
+ self.assertEqual(['branches', 'tags', 'trunk'], get_remote_contents(self.local_root_url))
+
+ def test_get_branches_non_canonical(self):
+ remote_path = os.path.join(self.root_directory, "remote_nc")
+ init_path = os.path.join(self.root_directory, "init_nc")
+ local_path = os.path.join(self.root_directory, "local_nc")
+ # create a "remote" repo
+ subprocess.check_call("svnadmin create %s" % remote_path, shell=True, cwd=self.root_directory)
+ local_root_url = "file://localhost/" + remote_path
+ local_url = local_root_url + "/footest"
+ # create an "init" repo to populate remote repo
+ subprocess.check_call("svn checkout %s %s" % (local_root_url, init_path), shell=True, cwd=self.root_directory)
+ for cmd in [
+ "mkdir footest",
+ "mkdir footest/foosub",
+ "touch footest/foosub/fixed.txt",
+ "svn add footest",
+ "svn commit -m initial"]:
+ subprocess.check_call(cmd, shell=True, cwd=init_path)
+ client = SvnClient(local_path)
+ client.checkout(local_url)
+ self.assertEqual([], client.get_branches())
+
+ def test_get_branches(self):
+ client = SvnClient(self.local_path)
+
+ self.assertEqual(['foo'], client.get_branches())
+
+ # slyly create some empty branches
+ subprocess.check_call("mkdir -p branches/foo2", shell=True, cwd=self.init_path)
+ subprocess.check_call("mkdir -p branches/bar", shell=True, cwd=self.init_path)
+ subprocess.check_call("svn add branches/foo2", shell=True, cwd=self.init_path)
+ subprocess.check_call("svn add branches/bar", shell=True, cwd=self.init_path)
+ subprocess.check_call("svn commit -m newbranches", shell=True, cwd=self.init_path)
+ self.assertEqual([], client.get_branches(local_only=True))
+ self.assertEqual(['bar', 'foo', 'foo2'], client.get_branches())
+
+ # checkout branch foo
+ local_path2 = os.path.join(self.root_directory, "local_foo")
+ client = SvnClient(local_path2)
+ client.checkout(self.local_root_url + '/branches/foo')
+ self.assertEqual(['foo'], client.get_branches(local_only=True))
diff --git a/test/test_tar.py b/test/test_tar.py
index 72d61c0..9d6668b 100644
--- a/test/test_tar.py
+++ b/test/test_tar.py
@@ -45,8 +45,8 @@ class TarClientTest(unittest.TestCase):
@classmethod
def setUpClass(self):
- self.remote_url = "https://code.ros.org/svn/release/download/stacks/exploration/exploration-0.3.0/exploration-0.3.0.tar.bz2"
- self.package_version = "exploration-0.3.0"
+ self.remote_url = "https://github.com/ros-gbp/ros_comm-release/archive/release/jade/roswtf/1.11.13-0.tar.gz"
+ self.package_version = "ros_comm-release-release-jade-roswtf-1.11.13-0"
def setUp(self):
self.directories = {}
@@ -98,7 +98,7 @@ class TarClientTest(unittest.TestCase):
# make sure the tarball subdirectory was promoted correctly.
self.assertTrue(os.path.exists(os.path.join(local_path,
self.package_version,
- 'stack.xml')))
+ 'package.xml')))
def test_checkout_dir_exists(self):
directory = tempfile.mkdtemp()
@@ -126,7 +126,7 @@ class TarClientTest(unittest.TestCase):
self.assertEqual(client.get_path(), local_path)
self.assertEqual(client.get_url(), self.remote_url)
# make sure the tarball subdirectory was promoted correctly.
- self.assertTrue(os.path.exists(os.path.join(local_path, 'stack.xml')))
+ self.assertTrue(os.path.exists(os.path.join(local_path, 'package.xml')))
def test_get_environment_metadata(self):
# Verify that metadata is generated
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/ros/ros-vcstools.git
More information about the debian-science-commits
mailing list