[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