[ros-vcstools] 01/03: New upstream version 0.1.39
Jochen Sprickerhof
jspricke at moszumanska.debian.org
Sun Sep 18 07:34:40 UTC 2016
This is an automated email from the git hooks/post-receive script.
jspricke pushed a commit to annotated tag debian/0.1.39-1
in repository ros-vcstools.
commit 00247e9f5fd7ab0b66fb83e4753e614990e161aa
Author: Jochen Sprickerhof <git at jochen.sprickerhof.de>
Date: Sun Sep 18 09:29:00 2016 +0200
New upstream version 0.1.39
---
.gitignore | 1 +
.travis.yml | 4 +-
README.rst | 24 +-
doc/changelog.rst | 7 +
setup.py | 2 +
src/vcstools/__version__.py | 2 +-
src/vcstools/bzr.py | 12 +
src/vcstools/git.py | 59 +++--
src/vcstools/git_archive_all.py | 549 ++++++++++++++++++++++++++++++++++++++++
src/vcstools/hg.py | 8 +
src/vcstools/svn.py | 11 +
src/vcstools/tar.py | 7 +-
src/vcstools/vcs_base.py | 9 +
stdeb.cfg | 2 +-
test/test_bzr.py | 17 ++
test/test_code_format.py | 1 +
test/test_git.py | 22 ++
test/test_git_subm.py | 297 ++++++++++++++++++++--
test/test_hg.py | 19 ++
test/test_svn.py | 18 ++
20 files changed, 1020 insertions(+), 51 deletions(-)
diff --git a/.gitignore b/.gitignore
index 3a386ab..c981de3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ build
dist
*.egg-info
nosetests.xml
+.eggs
diff --git a/.travis.yml b/.travis.yml
index d9c5c7b..5772da7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,7 +7,7 @@ python:
- "3.4"
env:
# lucid
- - HG='1.4.2' BZR=2.1.4 GIT=v1.7.0.4 SVN=1.6.6 PYYAML=3.10
+ # - HG='1.4.2' BZR=2.1.4 GIT=v1.7.0.4 SVN=1.6.6 PYYAML=3.10
# natty
# - HG='1.6.3' BZR=2.3.4 GIT=v1.7.4.1 SVN=1.6.6 PYYAML=3.10
# - HG='1.7.5' BZR=2.3.4 GIT=v1.7.4.1 SVN=1.6.6 PYYAML=3.10
@@ -47,7 +47,7 @@ install:
- pip install pep8
- pip install "pyyaml<=$PYYAML" > pyaml-warnings.log 2>&1 || (cat pyaml-warnings.log && false)
- pip install python-dateutil
- - cd $HOME/builds && wget http://mercurial.selenic.com/release/mercurial-$HG.tar.gz && tar -xf mercurial-$HG.tar.gz && cd mercurial-$HG && sudo $PY2K setup.py install > hg_install.log 2>&1 || (cat hg_install.log && false)
+ - cd $HOME/builds && wget https://www.mercurial-scm.org/release/mercurial-$HG.tar.gz && tar -xf mercurial-$HG.tar.gz && cd mercurial-$HG && sudo $PY2K setup.py install > hg_install.log 2>&1 || (cat hg_install.log && false)
# did not find single source for old git tarballs
- cd $HOME/builds && git clone git://git.kernel.org/pub/scm/git/git.git && cd git && git checkout $GIT && make prefix=/usr all > git_install.log 2>&1 || (cat git_install.log && false) && sudo make prefix=/usr install && cd $HOME/builds
- cd $HOME/builds && wget http://archive.ubuntu.com/ubuntu/pool/main/b/bzr/bzr_$BZR.orig.tar.gz && tar -xf bzr_$BZR.orig.tar.gz && cd bzr-* && sudo $PY2K setup.py install build_ext --allow-python-fallback > bzr_install.log 2>&1 || (cat bzr_install.log && false) && cd $HOME/builds
diff --git a/README.rst b/README.rst
index 11cfbef..47f2f53 100644
--- a/README.rst
+++ b/README.rst
@@ -19,23 +19,37 @@ On other Systems, use the pypi package::
Developer Environment
---------------------
-source setup.sh to include the src folder in your PYTHONPATH.
+When testing or doing development on vcstools, use a `virtualenv <https://virtualenv.readthedocs.org/en/latest/>`_::
+
+ $ virtualenv ~/vcstools_venv
+ $ source ~/vcstools_venv/bin/activate
+ $ pip install --editable /path/to/vcstools_source
+
+At this point in any shell where you run ``source ~/vcstools_venv/bin/activate``, you can use vcstools and evny edits to files in the vcstools source will take effect immediately.
+This is the effect of ``pip install --editable``, see ``pip install --help``.
+
+To setup a virtualenv for Python3 simply do this (from a clean terminal)::
+
+ $ virtualenv --python=python3 ~/vcstools_venv_py3
+ $ source ~/vcstools_venv_py3
+
+When you're done developing, you can exit any shells where you did ``source .../bin/activate`` and delete the virtualenv folder, e.g. ``~/vcstools_venv``.
Testing
-------
Use the python library nose to test::
- $ nosetests
+ $ python setup.py test
To test with coverage, make sure to have python-coverage installed and run::
+ $ python setup.py test -n # this installs test dependencies only
$ nosetests --with-coverage --cover-package vcstools
-To run python3 compatibility tests, run either::
+To run python3 compatibility tests, run::
- $ nosetests3
- $ python3 -m unittest discover --pattern*.py
+ $ python3 setup.py test
Test Status
-----------
diff --git a/doc/changelog.rst b/doc/changelog.rst
index ab79686..992140c 100644
--- a/doc/changelog.rst
+++ b/doc/changelog.rst
@@ -4,6 +4,13 @@ Changelog
0.1
===
+0.1.39
+------
+
+- Added support for git submodule in export_repository
+- Add Wily Xenial Yakkety
+- Add get_affected_files for all vcss
+
0.1.38
------
diff --git a/setup.py b/setup.py
index ba8d642..0de6a98 100644
--- a/setup.py
+++ b/setup.py
@@ -21,6 +21,8 @@ setup(name='vcstools',
package_dir={'': 'src'},
scripts=[],
install_requires=['pyyaml', 'python-dateutil'],
+ tests_require=['nose', 'mock'],
+ test_suite="nose.collector",
author="Tully Foote, Thibault Kruse, Ken Conley",
author_email="tfoote at osrfoundation.org",
url="http://wiki.ros.org/vcstools",
diff --git a/src/vcstools/__version__.py b/src/vcstools/__version__.py
index b0b983b..a34e4a6 100644
--- a/src/vcstools/__version__.py
+++ b/src/vcstools/__version__.py
@@ -1 +1 @@
-version = '0.1.38'
+version = '0.1.39'
diff --git a/src/vcstools/bzr.py b/src/vcstools/bzr.py
index 6c2ab14..4fb7ec0 100644
--- a/src/vcstools/bzr.py
+++ b/src/vcstools/bzr.py
@@ -228,6 +228,18 @@ class BzrClient(VcsClientBase):
_, response, _ = run_shell_command(command, shell=True, cwd=basepath)
return response
+ def get_affected_files(self, revision):
+ cmd = "bzr status -c {0} -S -V".format(
+ revision)
+
+ code, output, _ = run_shell_command(cmd, shell=True, cwd=self._path)
+
+ affected = []
+ if code == 0:
+ for filename in output.splitlines():
+ affected.append(filename.split(" ")[2])
+ return affected
+
def get_log(self, relpath=None, limit=None):
response = []
diff --git a/src/vcstools/git.py b/src/vcstools/git.py
index 1dd91d7..7301dd0 100644
--- a/src/vcstools/git.py
+++ b/src/vcstools/git.py
@@ -56,13 +56,18 @@ disambiguation, and in some cases warns.
from __future__ import absolute_import, print_function, unicode_literals
import os
import sys
+import shutil
+import tempfile
import gzip
import dateutil.parser # For parsing date strings
from distutils.version import LooseVersion
+import logging
from vcstools.vcs_base import VcsClientBase, VcsError
from vcstools.common import sanitized, normalized_rel_path, run_shell_command
+from vcstools.git_archive_all import *
+
class GitError(Exception):
pass
@@ -216,7 +221,7 @@ class GitClient(VcsClientBase):
def _update_submodules(self, verbose=False, timeout=None):
- # update and or init submodules too
+ # update submodules ( and init if necessary ).
if LooseVersion(self.gitversion) > LooseVersion('1.7'):
cmd = "git submodule update --init --recursive"
value, _, _ = run_shell_command(cmd,
@@ -298,8 +303,10 @@ class GitClient(VcsClientBase):
(branch_parent, remote) = self._get_branch_parent(current_branch=current_branch)
if remote != default_remote:
# if remote is not origin, must not fast-forward (because based on origin)
- sys.stderr.write("vcstools only handles branches tracking default remote," +
- " branch '%s' tracks remote '%s'\n" % (current_branch, remote))
+ logger = logging.getLogger('vcstools')
+ logger.warn("vcstools only handles branches tracking default remote,"
+ " branch '%s' tracks remote '%s'.\nRepository path is '%s'."
+ % (current_branch, remote, self._path))
branch_parent = None
# already on correct branch, fast-forward if there is a parent
if branch_parent:
@@ -448,6 +455,17 @@ class GitClient(VcsClientBase):
response += _git_diff_path_submodule_change(output, rel_path)
return response
+ def get_affected_files(self, revision):
+ cmd = "git show {0} --pretty='format:' --name-only".format(
+ revision)
+ code, output, _ = run_shell_command(cmd, shell=True, cwd=self._path)
+ affected = []
+ if code == 0:
+ for filename in output.splitlines():
+ if filename not in ('', None, ):
+ affected.append(filename)
+ return affected
+
def get_log(self, relpath=None, limit=None):
response = []
@@ -718,23 +736,28 @@ class GitClient(VcsClientBase):
return False
def export_repository(self, version, basepath):
- # Use the git archive function
- cmd = "git archive -o {0}.tar {1}".format(basepath, version)
- result, _, _ = run_shell_command(cmd, shell=True, cwd=self._path)
- if result:
+ if not self.detect_presence():
return False
+
try:
- # Gzip the tar file
- with open(basepath + '.tar', 'rb') as tar_file:
- gzip_file = gzip.open(basepath + '.tar.gz', 'wb')
- try:
- gzip_file.writelines(tar_file)
- finally:
- gzip_file.close()
- finally:
- # Clean up
- os.remove(basepath + '.tar')
- return True
+ # since version may relate to remote branch / tag we do not
+ # know about yet, do fetch if not already done
+ self._do_fetch()
+ tmpd_path = tempfile.mkdtemp()
+ try:
+ tmpgit = GitClient(tmpd_path)
+ if tmpgit.checkout(self._path, version=version, shallow=True):
+ archiver = GitArchiver(main_repo_abspath=tmpgit.get_path(), force_sub=True)
+ filepath = '{0}.tar.gz'.format(basepath)
+ archiver.create(filepath)
+ return filepath
+ else:
+ return False
+ finally:
+ shutil.rmtree(tmpd_path)
+
+ except GitError:
+ return False
def get_branches(self, local_only=False):
cmd = 'git branch --no-color'
diff --git a/src/vcstools/git_archive_all.py b/src/vcstools/git_archive_all.py
new file mode 100644
index 0000000..8969b2c
--- /dev/null
+++ b/src/vcstools/git_archive_all.py
@@ -0,0 +1,549 @@
+#! /usr/bin/env python
+# coding=utf-8
+
+# The MIT License (MIT)
+#
+# Copyright (c) 2010 Ilya Kulakov
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+# from
+# https://github.com/Kentzo/git-archive-all/blob/497049571f1cfe1c183cd3513b69914fa7379824/git_archive_all.py
+
+
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import logging
+from os import extsep, path, readlink, curdir
+from subprocess import CalledProcessError, Popen, PIPE
+import sys
+import tarfile
+from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED
+
+__version__ = "1.12"
+
+
+class GitArchiver(object):
+ """
+ GitArchiver
+
+ Scan a git repository and export all tracked files, and submodules.
+ Checks for .gitattributes files in each directory and uses 'export-ignore'
+ pattern entries for ignore files in the archive.
+
+ >>> archiver = GitArchiver(main_repo_abspath='my/repo/path')
+ >>> archiver.create('output.zip')
+ """
+ LOG = logging.getLogger('GitArchiver')
+
+ def __init__(self, prefix='', exclude=True, force_sub=False, extra=None, main_repo_abspath=None):
+ """
+ @param prefix: Prefix used to prepend all paths in the resulting archive.
+ Extra file paths are only prefixed if they are not relative.
+ E.g. if prefix is 'foo' and extra is ['bar', '/baz'] the resulting archive will look like this:
+ /
+ baz
+ foo/
+ bar
+ @type prefix: string
+
+ @param exclude: Determines whether archiver should follow rules specified in .gitattributes files.
+ @type exclude: bool
+
+ @param force_sub: Determines whether submodules are initialized and updated before archiving.
+ @type force_sub: bool
+
+ @param extra: List of extra paths to include in the resulting archive.
+ @type extra: list
+
+ @param main_repo_abspath: Absolute path to the main repository (or one of subdirectories).
+ If given path is path to a subdirectory (but not a submodule directory!) it will be replaced
+ with abspath to top-level directory of the repository.
+ If None, current cwd is used.
+ @type main_repo_abspath: string
+ """
+ if extra is None:
+ extra = []
+
+ if main_repo_abspath is None:
+ main_repo_abspath = path.abspath('')
+ elif not path.isabs(main_repo_abspath):
+ raise ValueError("You MUST pass absolute path to the main git repository.")
+
+ try:
+ self.run_shell("[ -d .git ] || git rev-parse --git-dir > /dev/null 2>&1", main_repo_abspath)
+ except Exception as e:
+ raise ValueError("{0} not a git repository (or any of the parent directories).".format(main_repo_abspath))
+
+ main_repo_abspath = path.abspath(
+ self.read_git_shell('git rev-parse --show-toplevel', main_repo_abspath)
+ .rstrip()
+ )
+
+ self.prefix = prefix
+ self.exclude = exclude
+ self.extra = extra
+ self.force_sub = force_sub
+ self.main_repo_abspath = main_repo_abspath
+
+ def create(self, output_path, dry_run=False, output_format=None):
+ """
+ Create the archive at output_file_path.
+
+ Type of the archive is determined either by extension of output_file_path or by output_format.
+ Supported formats are: gz, zip, bz2, xz, tar, tgz, txz
+
+ @param output_path: Output file path.
+ @type output_path: string
+
+ @param dry_run: Determines whether create should do nothing but print what it would archive.
+ @type dry_run: bool
+
+ @param output_format: Determines format of the output archive. If None, format is determined from extension
+ of output_file_path.
+ @type output_format: string
+ """
+ if output_format is None:
+ file_name, file_ext = path.splitext(output_path)
+ output_format = file_ext[len(extsep):].lower()
+ self.LOG.debug("Output format is not explicitly set, determined format is {0}.".format(output_format))
+
+ if not dry_run:
+ if output_format == 'zip':
+ archive = ZipFile(path.abspath(output_path), 'w')
+
+ def add_file(file_path, arcname):
+ if not path.islink(file_path):
+ archive.write(file_path, arcname, ZIP_DEFLATED)
+ else:
+ i = ZipInfo(arcname)
+ i.create_system = 3
+ i.external_attr = 0xA1ED0000
+ archive.writestr(i, readlink(file_path))
+ elif output_format in ['tar', 'bz2', 'gz', 'xz', 'tgz', 'txz']:
+ if output_format == 'tar':
+ t_mode = 'w'
+ elif output_format == 'tgz':
+ t_mode = 'w:gz'
+ elif output_format == 'txz':
+ t_mode = 'w:xz'
+ else:
+ t_mode = 'w:{0}'.format(output_format)
+
+ archive = tarfile.open(path.abspath(output_path), t_mode)
+
+ def add_file(file_path, arcname):
+ archive.add(file_path, arcname)
+ else:
+ raise RuntimeError("Unknown format: {0}".format(output_format))
+
+ def archiver(file_path, arcname):
+ self.LOG.debug("Compressing {0} => {1}...".format(file_path, arcname))
+ add_file(file_path, arcname)
+ else:
+ archive = None
+
+ def archiver(file_path, arcname):
+ self.LOG.info("{0} => {1}".format(file_path, arcname))
+
+ self.archive_all_files(archiver) # this will take care of submodule init and update
+
+ if archive is not None:
+ archive.close()
+
+ def get_exclude_patterns(self, repo_abspath, repo_file_paths):
+ """
+ Returns exclude patterns for a given repo. It looks for .gitattributes files in repo_file_paths.
+
+ Resulting dictionary will contain exclude patterns per path (relative to the repo_abspath).
+ E.g. {('.', 'Catalyst', 'Editions', 'Base'), ['Foo*', '*Bar']}
+
+ @type repo_abspath: string
+ @param repo_abspath: Absolute path to the git repository.
+
+ @type repo_file_paths: list
+ @param repo_file_paths: List of paths relative to the repo_abspath that are under git control.
+
+ @rtype: dict
+ @return: Dictionary representing exclude patterns.
+ Keys are tuples of strings. Values are lists of strings.
+ Returns None if self.exclude is not set.
+ """
+ if not self.exclude:
+ return None
+
+ def read_attributes(attributes_abspath):
+ patterns = []
+ if path.isfile(attributes_abspath):
+ attributes = open(attributes_abspath, 'r').readlines()
+ patterns = []
+ for line in attributes:
+ tokens = line.strip().split()
+ if "export-ignore" in tokens[1:]:
+ patterns.append(tokens[0])
+ return patterns
+
+ exclude_patterns = {(): []}
+
+ # There may be no gitattributes.
+ try:
+ global_attributes_abspath = self.read_shell("git config --get core.attributesfile", repo_abspath).rstrip()
+ exclude_patterns[()] = read_attributes(global_attributes_abspath)
+ except:
+ # And it's valid to not have them.
+ pass
+
+ for attributes_abspath in [path.join(repo_abspath, f) for f in repo_file_paths if f.endswith(".gitattributes")]:
+ # Each .gitattributes affects only files within its directory.
+ key = tuple(self.get_path_components(repo_abspath, path.dirname(attributes_abspath)))
+ exclude_patterns[key] = read_attributes(attributes_abspath)
+
+ local_attributes_abspath = path.join(repo_abspath, ".git", "info", "attributes")
+ key = tuple(self.get_path_components(repo_abspath, repo_abspath))
+
+ if key in exclude_patterns:
+ exclude_patterns[key].extend(read_attributes(local_attributes_abspath))
+ else:
+ exclude_patterns[key] = read_attributes(local_attributes_abspath)
+
+ return exclude_patterns
+
+ def is_file_excluded(self, repo_abspath, repo_file_path, exclude_patterns):
+ """
+ Checks whether file at a given path is excluded.
+
+ @type repo_abspath: string
+ @param repo_abspath: Absolute path to the git repository.
+
+ @type repo_file_path: string
+ @param repo_file_path: Path to a file within repo_abspath.
+
+ @type exclude_patterns: dict
+ @param exclude_patterns: Exclude patterns with format specified for get_exclude_patterns.
+
+ @rtype: bool
+ @return: True if file should be excluded. Otherwise False.
+ """
+ if exclude_patterns is None or not len(exclude_patterns):
+ return False
+
+ from fnmatch import fnmatch
+
+ file_name = path.basename(repo_file_path)
+ components = self.get_path_components(repo_abspath, path.join(repo_abspath, path.dirname(repo_file_path)))
+
+ is_excluded = False
+ # We should check all patterns specified in intermediate directories to the given file.
+ # At the end we should also check for the global patterns (key '()' or empty tuple).
+ while not is_excluded:
+ key = tuple(components)
+ if key in exclude_patterns:
+ patterns = exclude_patterns[key]
+ for p in patterns:
+ if fnmatch(file_name, p) or fnmatch(repo_file_path, p):
+ self.LOG.debug("Exclude pattern matched {0}: {1}".format(p, repo_file_path))
+ is_excluded = True
+
+ if not len(components):
+ break
+
+ components.pop()
+
+ return is_excluded
+
+ def archive_all_files(self, archiver):
+ """
+ Archive all files using archiver.
+
+ @param archiver: Callable that accepts 2 arguments:
+ abspath to file on the system and relative path within archive.
+ """
+ for file_path in self.extra:
+ archiver(path.abspath(file_path), path.join(self.prefix, file_path))
+
+ for file_path in self.walk_git_files():
+ archiver(path.join(self.main_repo_abspath, file_path), path.join(self.prefix, file_path))
+
+ def walk_git_files(self, repo_path=''):
+ """
+ An iterator method that yields a file path relative to main_repo_abspath
+ for each file that should be included in the archive.
+ Skips those that match the exclusion patterns found in
+ any discovered .gitattributes files along the way.
+
+ Recurs into submodules as well.
+
+ @type repo_path: string
+ @param repo_path: Path to the git submodule repository relative to main_repo_abspath.
+
+ @rtype: iterator
+ @return: Iterator to traverse files under git control relative to main_repo_abspath.
+ """
+ repo_abspath = path.join(self.main_repo_abspath, repo_path)
+ repo_file_paths = self.read_git_shell(
+ "git ls-files --cached --full-name --no-empty-directory",
+ repo_abspath
+ ).splitlines()
+ exclude_patterns = self.get_exclude_patterns(repo_abspath, repo_file_paths)
+
+ for repo_file_path in repo_file_paths:
+ # Git puts path in quotes if file path has unicode characters.
+ repo_file_path = repo_file_path.strip('"') # file path relative to current repo
+ file_name = path.basename(repo_file_path)
+ main_repo_file_path = path.join(repo_path, repo_file_path) # file path relative to the main repo
+
+ # Only list symlinks and files that don't start with git.
+ if file_name.startswith(".git") or (
+ not path.islink(main_repo_file_path) and path.isdir(main_repo_file_path)
+ ):
+ continue
+
+ if self.is_file_excluded(repo_abspath, repo_file_path, exclude_patterns):
+ continue
+
+ yield main_repo_file_path
+
+ if self.force_sub:
+ self.run_shell("git submodule init", repo_abspath)
+ self.run_shell("git submodule update", repo_abspath)
+
+ for submodule_path in self.read_shell("git submodule --quiet foreach 'pwd -P'", repo_abspath).splitlines():
+ # Shell command returns absolute paths to submodules.
+ submodule_path = path.relpath(submodule_path, self.main_repo_abspath)
+ for file_path in self.walk_git_files(submodule_path):
+ yield file_path
+
+ @staticmethod
+ def get_path_components(repo_abspath, abspath):
+ """
+ Split given abspath into components relative to repo_abspath.
+ These components are primarily used as unique keys of files and folders within a repository.
+
+ E.g. if repo_abspath is '/Documents/Hobby/ParaView/' and abspath is
+ '/Documents/Hobby/ParaView/Catalyst/Editions/Base/', function will return:
+ ['.', 'Catalyst', 'Editions', 'Base']
+
+ First element is always '.' (concrete symbol depends on OS).
+
+ @param repo_abspath: Absolute path to the git repository. Normalized via os.path.normpath.
+ @type repo_abspath: string
+
+ @param abspath: Absolute path to a file within repo_abspath. Normalized via os.path.normpath.
+ @type abspath: string
+
+ @return: List of path components.
+ @rtype: list
+ """
+ repo_abspath = path.normpath(repo_abspath)
+ abspath = path.normpath(abspath)
+
+ if not path.isabs(repo_abspath):
+ raise ValueError("repo_abspath MUST be absolute path.")
+
+ if not path.isabs(abspath):
+ raise ValueError("abspath MUST be absoulte path.")
+
+ if not path.commonprefix([repo_abspath, abspath]):
+ raise ValueError(
+ "abspath (\"{0}\") MUST have common prefix with repo_abspath (\"{1}\")"
+ .format(abspath, repo_abspath)
+ )
+
+ components = []
+
+ while not abspath == repo_abspath:
+ abspath, tail = path.split(abspath)
+
+ if tail:
+ components.insert(0, tail)
+
+ components.insert(0, curdir)
+ return components
+
+ @staticmethod
+ def run_shell(cmd, cwd=None):
+ """
+ Runs shell command.
+
+ @type cmd: string
+ @param cmd: Command to be executed.
+
+ @type cwd: string
+ @param cwd: Working directory.
+
+ @rtype: int
+ @return: Return code of the command.
+
+ @raise CalledProcessError: Raises exception if return code of the command is non-zero.
+ """
+ p = Popen(cmd, shell=True, cwd=cwd)
+ p.wait()
+
+ if p.returncode:
+ raise CalledProcessError(returncode=p.returncode, cmd=cmd)
+
+ return p.returncode
+
+ @staticmethod
+ def read_shell(cmd, cwd=None, encoding='utf-8'):
+ """
+ Runs shell command and reads output.
+
+ @type cmd: string
+ @param cmd: Command to be executed.
+
+ @type cwd: string
+ @param cwd: Working directory.
+
+ @type encoding: string
+ @param encoding: Encoding used to decode bytes returned by Popen into string.
+
+ @rtype: string
+ @return: Output of the command.
+
+ @raise CalledProcessError: Raises exception if return code of the command is non-zero.
+ """
+ p = Popen(cmd, shell=True, stdout=PIPE, cwd=cwd)
+ output, _ = p.communicate()
+ output = output.decode(encoding)
+
+ if p.returncode:
+ if sys.version_info > (2, 6):
+ raise CalledProcessError(returncode=p.returncode, cmd=cmd, output=output)
+ else:
+ raise CalledProcessError(returncode=p.returncode, cmd=cmd)
+
+ return output
+
+ @staticmethod
+ def read_git_shell(cmd, cwd=None):
+ """
+ Runs git shell command, reads output and decodes it into unicode string
+
+ @type cmd: string
+ @param cmd: Command to be executed.
+
+ @type cwd: string
+ @param cwd: Working directory.
+
+ @rtype: string
+ @return: Output of the command.
+
+ @raise CalledProcessError: Raises exception if return code of the command is non-zero.
+ """
+ p = Popen(cmd, shell=True, stdout=PIPE, cwd=cwd)
+ output, _ = p.communicate()
+ output = output.decode('unicode_escape').encode('raw_unicode_escape').decode('utf-8')
+
+ if p.returncode:
+ if sys.version_info > (2, 6):
+ raise CalledProcessError(returncode=p.returncode, cmd=cmd, output=output)
+ else:
+ raise CalledProcessError(returncode=p.returncode, cmd=cmd)
+
+ return output
+
+
+def main():
+ from optparse import OptionParser
+
+ parser = OptionParser(
+ usage="usage: %prog [-v] [--prefix PREFIX] [--no-exclude] [--force-submodules]"
+ " [--extra EXTRA1 [EXTRA2]] [--dry-run] OUTPUT_FILE",
+ version="%prog {0}".format(__version__)
+ )
+
+ parser.add_option('--prefix',
+ type='string',
+ dest='prefix',
+ default=None,
+ help="""prepend PREFIX to each filename in the archive.
+ OUTPUT_FILE name is used by default to avoid tarbomb.
+ You can set it to '' in order to explicitly request tarbomb""")
+
+ parser.add_option('-v', '--verbose',
+ action='store_true',
+ dest='verbose',
+ help='enable verbose mode')
+
+ parser.add_option('--no-exclude',
+ action='store_false',
+ dest='exclude',
+ default=True,
+ help="don't read .gitattributes files for patterns containing export-ignore attrib")
+
+ parser.add_option('--force-submodules',
+ action='store_true',
+ dest='force_sub',
+ help='force a git submodule init && git submodule update at each level before iterating submodules')
+
+ parser.add_option('--extra',
+ action='append',
+ dest='extra',
+ default=[],
+ help="any additional files to include in the archive")
+
+ parser.add_option('--dry-run',
+ action='store_true',
+ dest='dry_run',
+ help="don't actually archive anything, just show what would be done")
+
+ options, args = parser.parse_args()
+
+ if len(args) != 1:
+ parser.error("You must specify exactly one output file")
+
+ output_file_path = args[0]
+
+ if path.isdir(output_file_path):
+ parser.error("You cannot use directory as output")
+
+ # avoid tarbomb
+ if options.prefix is not None:
+ options.prefix = path.join(options.prefix, '')
+ else:
+ import re
+
+ output_name = path.basename(output_file_path)
+ output_name = re.sub(
+ '(\.zip|\.tar|\.tgz|\.txz|\.gz|\.bz2|\.xz|\.tar\.gz|\.tar\.bz2|\.tar\.xz)$',
+ '',
+ output_name
+ ) or "Archive"
+ options.prefix = path.join(output_name, '')
+
+ try:
+ handler = logging.StreamHandler(sys.stdout)
+ handler.setFormatter(logging.Formatter('%(message)s'))
+ GitArchiver.LOG.addHandler(handler)
+ GitArchiver.LOG.setLevel(logging.DEBUG if options.verbose else logging.INFO)
+ archiver = GitArchiver(options.prefix,
+ options.exclude,
+ options.force_sub,
+ options.extra)
+ archiver.create(output_file_path, options.dry_run)
+ except Exception as e:
+ parser.exit(2, "{0}\n".format(e))
+
+ sys.exit(0)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/src/vcstools/hg.py b/src/vcstools/hg.py
index a776685..a38036e 100644
--- a/src/vcstools/hg.py
+++ b/src/vcstools/hg.py
@@ -276,6 +276,14 @@ class HgClient(VcsClientBase):
response = _hg_diff_path_change(response, rel_path)
return response
+ def get_affected_files(self, revision):
+ cmd = "hg log -r %s --template '{files}'" % revision
+ code, output, _ = run_shell_command(cmd, shell=True, cwd=self._path)
+ affected = []
+ if code == 0:
+ affected = output.split(" ")
+ return affected
+
def get_log(self, relpath=None, limit=None):
response = []
diff --git a/src/vcstools/svn.py b/src/vcstools/svn.py
index e338f39..e330872 100755
--- a/src/vcstools/svn.py
+++ b/src/vcstools/svn.py
@@ -291,6 +291,17 @@ class SvnClient(VcsClientBase):
cwd=basepath)
return response
+ def get_affected_files(self, revision):
+ cmd = "svn diff --summarize -c {0}".format(
+ revision)
+
+ code, output, _ = run_shell_command(cmd, shell=True, cwd=self._path)
+ affected = []
+ if code == 0:
+ for filename in output.splitlines():
+ affected.append(filename.split(" ")[7])
+ return affected
+
def get_log(self, relpath=None, limit=None):
response = []
diff --git a/src/vcstools/tar.py b/src/vcstools/tar.py
index 85fe9d4..d27df9e 100644
--- a/src/vcstools/tar.py
+++ b/src/vcstools/tar.py
@@ -110,6 +110,7 @@ class TarClient(VcsClientBase):
temp_tarfile = tarfile.open(filename, 'r:*')
members = None # means all members in extractall
if version == '' or version is None:
+ subdir = tempdir
self.logger.warn("No tar subdirectory chosen via the 'version' argument for url: %s" % url)
else:
# getmembers lists all files contained in tar with
@@ -123,9 +124,9 @@ class TarClient(VcsClientBase):
subdirs.append(m.name.split('/')[0])
if not members:
raise VcsError("%s is not a subdirectory with contents in members %s" % (version, subdirs))
+ subdir = os.path.join(tempdir, version)
temp_tarfile.extractall(path=tempdir, members=members)
- subdir = os.path.join(tempdir, version)
if not os.path.isdir(subdir):
raise VcsError("%s is not a subdirectory\n" % subdir)
@@ -153,9 +154,9 @@ class TarClient(VcsClientBase):
"""
if not self.detect_presence():
return False
-
if version != self.get_version():
- sys.stderr.write("Tarball Client does not support updating with different version.\n")
+ sys.stderr.write("Tarball Client does not support updating with different version '%s' != '%s'\n"
+ % (version, self.get_version()))
return False
return True
diff --git a/src/vcstools/vcs_base.py b/src/vcstools/vcs_base.py
index 7b67429..9448fec 100644
--- a/src/vcstools/vcs_base.py
+++ b/src/vcstools/vcs_base.py
@@ -254,6 +254,15 @@ class VcsClientBase(object):
raise NotImplementedError("Base class get_status method must be overridden for client type %s " %
self._vcs_type_name)
+ def get_affected_files(self, revision):
+ """
+ Get the files that were affected by a specific revision
+ :param revision: SHA or revision number.
+ :returns: A list of strings with the files affected by a specific commit
+ """
+ raise NotImplemented(
+ "Base class get_affected_files method must be overriden")
+
def get_log(self, relpath=None, limit=None):
"""
Calls scm log command.
diff --git a/stdeb.cfg b/stdeb.cfg
index a166317..832b099 100644
--- a/stdeb.cfg
+++ b/stdeb.cfg
@@ -1,5 +1,5 @@
[DEFAULT]
Depends: subversion, mercurial, git-core, bzr, python-yaml, python-dateutil
Depends3: subversion, mercurial, git-core, bzr, python3-yaml
-Suite: oneiric precise quantal raring saucy trusty utopic vivid wheezy jessie
+Suite: oneiric precise quantal raring saucy trusty utopic vivid wily xenial yakkety wheezy jessie
X-Python3-Version: >= 3.2
diff --git a/test/test_bzr.py b/test/test_bzr.py
index 09a6be5..e9f67cf 100644
--- a/test/test_bzr.py
+++ b/test/test_bzr.py
@@ -260,6 +260,23 @@ class BzrClientLogTest(BzrClientTestSetups):
self.assertEquals('initial', log[0]['message'])
+class BzrClientAffectedFilesTest(BzrClientTestSetups):
+
+ @classmethod
+ def setUpClass(self):
+ BzrClientTestSetups.setUpClass()
+ client = BzrClient(self.local_path)
+ client.checkout(self.remote_path)
+
+ def test_get_log_defaults(self):
+ client = BzrClient(self.local_path)
+ client.checkout(self.remote_path)
+ log = client.get_log(limit=1)[0]
+ affected = client.get_affected_files(log['id'])
+ self.assertEqual(sorted(['deleted-fs.txt', 'deleted.txt']),
+ sorted(affected))
+
+
class BzrDiffStatClientTest(BzrClientTestSetups):
@classmethod
diff --git a/test/test_code_format.py b/test/test_code_format.py
index 066fb6b..fb98c00 100644
--- a/test/test_code_format.py
+++ b/test/test_code_format.py
@@ -24,6 +24,7 @@ def test_pep8_conformance():
pep8style = pep8.StyleGuide(max_line_length=120)
report = pep8style.options.report
report.start()
+ pep8style.options.exclude.append('git_archive_all.py')
pep8style.input_dir(os.path.join('..', 'vcstools', 'src'))
report.stop()
assert report.total_errors == 0, "Found '{0}' code style errors (and warnings).".format(report.total_errors)
diff --git a/test/test_git.py b/test/test_git.py
index e22c503..af6dc4c 100644
--- a/test/test_git.py
+++ b/test/test_git.py
@@ -595,6 +595,28 @@ class GitClientLogTest(GitClientTestSetups):
self.assertEquals(1, len(log))
+class GitClientAffectedFiles(GitClientTestSetups):
+
+ def setUp(self):
+ client = GitClient(self.local_path)
+ client.checkout(self.remote_path)
+ # Create some local untracking branch
+
+ subprocess.check_call("git checkout test_tag -b localbranch", shell=True, cwd=self.local_path)
+ subprocess.check_call("touch local_file", shell=True, cwd=self.local_path)
+ subprocess.check_call("git add local_file", shell=True, cwd=self.local_path)
+ subprocess.check_call("git commit -m \"local_file\"", shell=True, cwd=self.local_path)
+
+ def test_get_affected_files(self):
+ client = GitClient(self.local_path)
+ affected = client.get_affected_files(client.get_log()[0]['id'])
+
+ self.assertEqual(sorted(['local_file']),
+ sorted(affected))
+
+ self.assertEquals(['local_file'], affected)
+
+
class GitClientDanglingCommitsTest(GitClientTestSetups):
def setUp(self):
diff --git a/test/test_git_subm.py b/test/test_git_subm.py
index 97ffa83..a5354f4 100644
--- a/test/test_git_subm.py
+++ b/test/test_git_subm.py
@@ -38,8 +38,12 @@ import unittest
import subprocess
import tempfile
import shutil
+import tarfile
+import filecmp
+from contextlib import closing
+from distutils.version import LooseVersion
-from vcstools.git import GitClient
+from vcstools.git import GitClient, _get_git_version
class GitClientTestSetups(unittest.TestCase):
@@ -57,6 +61,12 @@ class GitClientTestSetups(unittest.TestCase):
self.sublocal_path = os.path.join(self.local_path, "submodule")
self.sublocal2_path = os.path.join(self.local_path, "submodule2")
self.subsublocal_path = os.path.join(self.sublocal_path, "subsubmodule")
+ self.subsublocal2_path = os.path.join(self.sublocal2_path, "subsubmodule")
+ self.export_path = os.path.join(self.root_directory, "export")
+ self.subexport_path = os.path.join(self.export_path, "submodule")
+ self.subexport2_path = os.path.join(self.export_path, "submodule2")
+ self.subsubexport_path = os.path.join(self.subexport_path, "subsubmodule")
+ self.subsubexport2_path = os.path.join(self.subexport2_path, "subsubmodule")
os.makedirs(self.remote_path)
os.makedirs(self.submodule_path)
os.makedirs(self.subsubmodule_path)
@@ -67,8 +77,9 @@ class GitClientTestSetups(unittest.TestCase):
subprocess.check_call("git add fixed.txt", shell=True, cwd=self.remote_path)
subprocess.check_call("git commit -m initial", shell=True, cwd=self.remote_path)
subprocess.check_call("git tag test_tag", shell=True, cwd=self.remote_path)
- subprocess.check_call("git branch test_branch", 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)
+ subprocess.check_call("git branch initial_branch", 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)
self.version_init = po.stdout.read().decode('UTF-8').rstrip('"').lstrip('"')
# create a submodule repo
@@ -92,41 +103,57 @@ class GitClientTestSetups(unittest.TestCase):
subprocess.check_call("git submodule update", shell=True, cwd=self.submodule_path)
subprocess.check_call("git commit -m subsubmodule", shell=True, cwd=self.submodule_path)
- po = subprocess.Popen("git log -n 1 --pretty=format:\"%H\"", shell=True, cwd=self.subsubmodule_path, stdout=subprocess.PIPE)
+ po = subprocess.Popen("git log -n 1 --pretty=format:\"%H\"", shell=True,
+ cwd=self.subsubmodule_path, stdout=subprocess.PIPE)
self.subsubversion_final = po.stdout.read().decode('UTF-8').rstrip('"').lstrip('"')
- po = subprocess.Popen("git log -n 1 --pretty=format:\"%H\"", shell=True, cwd=self.submodule_path, stdout=subprocess.PIPE)
+ po = subprocess.Popen("git log -n 1 --pretty=format:\"%H\"", shell=True,
+ cwd=self.submodule_path, stdout=subprocess.PIPE)
self.subversion_final = po.stdout.read().decode('UTF-8').rstrip('"').lstrip('"')
- # attach submodule to remote
- subprocess.check_call("git submodule add %s %s" % (self.submodule_path, "submodule"),
- shell=True, cwd=self.remote_path)
+ # attach submodule somewhere, only in test_branch first
+ subprocess.check_call("git checkout master -b test_branch", shell=True, cwd=self.remote_path)
+ subprocess.check_call("git submodule add %s %s" % (self.submodule_path,
+ "submodule2"), shell=True, cwd=self.remote_path)
+
+ # this is needed only if git <= 1.7, during the time when submodules were being introduced (from 1.5.3)
subprocess.check_call("git submodule init", shell=True, cwd=self.remote_path)
subprocess.check_call("git submodule update", shell=True, cwd=self.remote_path)
+
subprocess.check_call("git commit -m submodule", 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)
- self.version_final = po.stdout.read().decode('UTF-8').rstrip('"').lstrip('"')
- subprocess.check_call("git tag last_tag", 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)
+ self.version_test = po.stdout.read().decode('UTF-8').rstrip('"').lstrip('"')
+
+ # attach submodule to remote on master. CAREFUL : submodule2 is still in working tree (git does not clean it)
+ subprocess.check_call("git checkout master", shell=True, cwd=self.remote_path)
+ subprocess.check_call("git submodule add %s %s" % (self.submodule_path, "submodule"),
+ shell=True, cwd=self.remote_path)
- # attach submodule somewhere else in test_branch
- subprocess.check_call("git checkout master -b test_branch2", shell=True, cwd=self.remote_path)
- subprocess.check_call("git submodule add %s %s" % (self.submodule_path, "submodule2"), shell=True, cwd=self.remote_path)
+ # this is needed only if git <= 1.7, during the time when submodules were being introduced (from 1.5.3)
subprocess.check_call("git submodule init", shell=True, cwd=self.remote_path)
subprocess.check_call("git submodule update", shell=True, cwd=self.remote_path)
+
subprocess.check_call("git commit -m submodule", shell=True, cwd=self.remote_path)
- # go back to master else clients will checkout test_branch
- subprocess.check_call("git checkout master", 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)
+ self.version_final = po.stdout.read().decode('UTF-8').rstrip('"').lstrip('"')
+ subprocess.check_call("git tag last_tag", shell=True, cwd=self.remote_path)
@classmethod
def tearDownClass(self):
for d in self.directories:
shutil.rmtree(self.directories[d])
+ pass
def tearDown(self):
if os.path.exists(self.local_path):
shutil.rmtree(self.local_path)
+ if os.path.exists(self.export_path):
+ shutil.rmtree(self.export_path)
+ pass
class GitClientTest(GitClientTestSetups):
@@ -149,18 +176,191 @@ class GitClientTest(GitClientTestSetups):
self.assertTrue(subsubclient.detect_presence())
self.assertEqual(self.subsubversion_final, subsubclient.get_version())
- def test_checkout_branch_with_subs(self):
+ def test_export_master(self):
url = self.remote_path
client = GitClient(self.local_path)
subclient = GitClient(self.sublocal_path)
subsubclient = GitClient(self.subsublocal_path)
self.assertFalse(client.path_exists())
self.assertFalse(client.detect_presence())
- self.assertTrue(client.checkout(url, version='test_branch'))
+ self.assertFalse(os.path.exists(self.export_path))
+ self.assertTrue(client.checkout(url))
+ self.assertTrue(client.path_exists())
+ self.assertTrue(subclient.path_exists())
+ self.assertTrue(subsubclient.path_exists())
+ tarpath = client.export_repository("master", self.export_path)
+ self.assertEqual(tarpath, self.export_path + '.tar.gz')
+ os.mkdir(self.export_path)
+ with closing(tarfile.open(tarpath, "r:gz")) as tarf:
+ tarf.extractall(self.export_path)
+ subsubdirdiff = filecmp.dircmp(self.subsubexport_path, self.subsublocal_path, ignore=['.git', '.gitmodules'])
+ self.assertEqual(subsubdirdiff.left_only, [])
+ self.assertEqual(subsubdirdiff.right_only, [])
+ self.assertEqual(subsubdirdiff.diff_files, [])
+ subdirdiff = filecmp.dircmp(self.subexport_path, self.sublocal_path, ignore=['.git', '.gitmodules'])
+ self.assertEqual(subdirdiff.left_only, [])
+ self.assertEqual(subdirdiff.right_only, [])
+ self.assertEqual(subdirdiff.diff_files, [])
+ dirdiff = filecmp.dircmp(self.export_path, self.local_path, ignore=['.git', '.gitmodules'])
+ self.assertEqual(dirdiff.left_only, [])
+ self.assertEqual(dirdiff.right_only, [])
+ self.assertEqual(dirdiff.diff_files, [])
+
+ def test_export_branch(self):
+ url = self.remote_path
+ client = GitClient(self.local_path)
+ subclient = GitClient(self.sublocal_path)
+ subclient2 = GitClient(self.sublocal2_path)
+ subsubclient = GitClient(self.subsublocal_path)
+ subsubclient2 = GitClient(self.subsublocal2_path)
+ self.assertFalse(client.path_exists())
+ self.assertFalse(client.detect_presence())
+ self.assertFalse(os.path.exists(self.export_path))
+ self.assertTrue(client.checkout(url, version='master'))
+ self.assertTrue(client.path_exists())
+ self.assertTrue(subclient.path_exists())
+ self.assertTrue(subsubclient.path_exists())
+ self.assertFalse(subclient2.path_exists())
+ self.assertFalse(subsubclient2.path_exists())
+ # we need first to retrieve locally the branch we want to export
+ self.assertTrue(client.update(version='test_branch'))
+ self.assertTrue(client.path_exists())
+ # git leaves old submodule around by default
+ self.assertTrue(subclient.path_exists())
+ self.assertTrue(subsubclient.path_exists())
+ # new submodule should be there
+ self.assertTrue(subclient2.path_exists())
+ self.assertTrue(subsubclient2.path_exists())
+
+ tarpath = client.export_repository("test_branch", self.export_path)
+ self.assertEqual(tarpath, self.export_path + '.tar.gz')
+ os.mkdir(self.export_path)
+ with closing(tarfile.open(tarpath, "r:gz")) as tarf:
+ tarf.extractall(self.export_path)
+
+ # Checking that we have only submodule2 in our export
+ self.assertFalse(os.path.exists(self.subexport_path))
+ self.assertFalse(os.path.exists(self.subsubexport_path))
+ self.assertTrue(os.path.exists(self.subexport2_path))
+ self.assertTrue(os.path.exists(self.subsubexport2_path))
+
+ # comparing with test_branch version ( currently checked-out )
+ subsubdirdiff = filecmp.dircmp(self.subsubexport2_path, self.subsublocal_path, ignore=['.git', '.gitmodules'])
+ self.assertEqual(subsubdirdiff.left_only, []) # same subsubfixed.txt in both subsubmodule/
+ self.assertEqual(subsubdirdiff.right_only, [])
+ self.assertEqual(subsubdirdiff.diff_files, [])
+ subdirdiff = filecmp.dircmp(self.subexport2_path, self.sublocal_path, ignore=['.git', '.gitmodules'])
+ self.assertEqual(subdirdiff.left_only, [])
+ self.assertEqual(subdirdiff.right_only, [])
+ self.assertEqual(subdirdiff.diff_files, [])
+ dirdiff = filecmp.dircmp(self.export_path, self.local_path, ignore=['.git', '.gitmodules'])
+ self.assertEqual(dirdiff.left_only, [])
+ # submodule is still there on local_path (git default behavior)
+ self.assertEqual(dirdiff.right_only, ['submodule'])
+ self.assertEqual(dirdiff.diff_files, [])
+
+ def test_export_hash(self):
+ url = self.remote_path
+ client = GitClient(self.local_path)
+ subclient = GitClient(self.sublocal_path)
+ subclient2 = GitClient(self.sublocal2_path)
+ subsubclient = GitClient(self.subsublocal_path)
+ subsubclient2 = GitClient(self.subsublocal2_path)
+ self.assertFalse(client.path_exists())
+ self.assertFalse(client.detect_presence())
+ self.assertFalse(os.path.exists(self.export_path))
+ self.assertTrue(client.checkout(url, version='master'))
+ self.assertTrue(client.path_exists())
+ self.assertTrue(subclient.path_exists())
+ self.assertTrue(subsubclient.path_exists())
+ self.assertFalse(subclient2.path_exists())
+ self.assertFalse(subsubclient2.path_exists())
+ # we need first to retrieve locally the hash we want to export
+ self.assertTrue(client.update(version=self.version_test))
+ self.assertTrue(client.path_exists())
+ # git leaves old submodule around by default
+ self.assertTrue(subclient.path_exists())
+ self.assertTrue(subsubclient.path_exists())
+ # new submodule should be there
+ self.assertTrue(subclient2.path_exists())
+ self.assertTrue(subsubclient2.path_exists())
+
+ tarpath = client.export_repository(self.version_test, self.export_path)
+ self.assertEqual(tarpath, self.export_path + '.tar.gz')
+ os.mkdir(self.export_path)
+ with closing(tarfile.open(tarpath, "r:gz")) as tarf:
+ tarf.extractall(self.export_path)
+
+ # Checking that we have only submodule2 in our export
+ self.assertFalse(os.path.exists(self.subexport_path))
+ self.assertFalse(os.path.exists(self.subsubexport_path))
+ self.assertTrue(os.path.exists(self.subexport2_path))
+ self.assertTrue(os.path.exists(self.subsubexport2_path))
+
+ # comparing with version_test ( currently checked-out )
+ subsubdirdiff = filecmp.dircmp(self.subsubexport2_path, self.subsublocal_path, ignore=['.git', '.gitmodules'])
+ self.assertEqual(subsubdirdiff.left_only, []) # same subsubfixed.txt in both subsubmodule/
+ self.assertEqual(subsubdirdiff.right_only, [])
+ self.assertEqual(subsubdirdiff.diff_files, [])
+ subdirdiff = filecmp.dircmp(self.subexport2_path, self.sublocal_path, ignore=['.git', '.gitmodules'])
+ self.assertEqual(subdirdiff.left_only, [])
+ self.assertEqual(subdirdiff.right_only, [])
+ self.assertEqual(subdirdiff.diff_files, [])
+ dirdiff = filecmp.dircmp(self.export_path, self.local_path, ignore=['.git', '.gitmodules'])
+ self.assertEqual(dirdiff.left_only, [])
+ # submodule is still there on local_path (git default behavior)
+ self.assertEqual(dirdiff.right_only, ['submodule'])
+ self.assertEqual(dirdiff.diff_files, [])
+
+ def test_checkout_branch_without_subs(self):
+ url = self.remote_path
+ client = GitClient(self.local_path)
+ subclient = GitClient(self.sublocal_path)
+ subsubclient = GitClient(self.subsublocal_path)
+ self.assertFalse(client.path_exists())
+ self.assertFalse(client.detect_presence())
+ self.assertTrue(client.checkout(url, version='initial_branch'))
self.assertTrue(client.path_exists())
self.assertTrue(client.detect_presence())
self.assertEqual(self.version_init, client.get_version())
self.assertFalse(subclient.path_exists())
+ self.assertFalse(subsubclient.path_exists())
+
+ def test_checkout_test_branch_with_subs(self):
+ url = self.remote_path
+ client = GitClient(self.local_path)
+ subclient = GitClient(self.sublocal_path)
+ subsubclient = GitClient(self.subsublocal_path)
+ subclient2 = GitClient(self.sublocal2_path)
+ subsubclient2 = GitClient(self.subsublocal2_path)
+ self.assertFalse(client.path_exists())
+ self.assertFalse(client.detect_presence())
+ self.assertTrue(client.checkout(url, version='test_branch'))
+ self.assertTrue(client.path_exists())
+ self.assertTrue(client.detect_presence())
+ self.assertEqual(self.version_test, client.get_version())
+ self.assertFalse(subclient.path_exists())
+ self.assertFalse(subsubclient.path_exists())
+ self.assertTrue(subclient2.path_exists())
+ self.assertTrue(subsubclient2.path_exists())
+
+ def test_checkout_master_with_subs(self):
+ url = self.remote_path
+ client = GitClient(self.local_path)
+ subclient = GitClient(self.sublocal_path)
+ subsubclient = GitClient(self.subsublocal_path)
+ subclient2 = GitClient(self.sublocal2_path)
+ subsubclient2 = GitClient(self.subsublocal2_path)
+ self.assertFalse(client.path_exists())
+ self.assertFalse(client.detect_presence())
+ self.assertTrue(client.checkout(url, version='master'))
+ self.assertTrue(client.path_exists())
+ self.assertTrue(client.detect_presence())
+ self.assertEqual(self.version_final, client.get_version())
+ self.assertTrue(subclient.path_exists())
+ self.assertTrue(subsubclient.path_exists())
+ self.assertFalse(subclient2.path_exists())
+ self.assertFalse(subsubclient2.path_exists())
def test_switch_branches(self):
url = self.remote_path
@@ -168,6 +368,36 @@ class GitClientTest(GitClientTestSetups):
subclient = GitClient(self.sublocal_path)
subclient2 = GitClient(self.sublocal2_path)
subsubclient = GitClient(self.subsublocal_path)
+ subsubclient2 = GitClient(self.subsublocal2_path)
+ self.assertFalse(client.path_exists())
+ self.assertFalse(client.detect_presence())
+ self.assertTrue(client.checkout(url))
+ self.assertTrue(client.path_exists())
+ self.assertTrue(subclient.path_exists())
+ self.assertTrue(subsubclient.path_exists())
+ self.assertFalse(subclient2.path_exists())
+ new_version = "test_branch"
+ self.assertTrue(client.update(new_version))
+ # checking that update doesnt make submodule disappear (git default behavior)
+ self.assertTrue(subclient2.path_exists())
+ self.assertTrue(subsubclient2.path_exists())
+ self.assertTrue(subclient.path_exists())
+ self.assertTrue(subsubclient.path_exists())
+ oldnew_version = "master"
+ self.assertTrue(client.update(oldnew_version))
+ # checking that update doesnt make submodule2 disappear (git default behavior)
+ self.assertTrue(subclient2.path_exists())
+ self.assertTrue(subsubclient2.path_exists())
+ self.assertTrue(subclient.path_exists())
+ self.assertTrue(subsubclient.path_exists())
+
+ def test_switch_branches_retrieve_local_subcommit(self):
+ url = self.remote_path
+ client = GitClient(self.local_path)
+ subclient = GitClient(self.sublocal_path)
+ subclient2 = GitClient(self.sublocal2_path)
+ subsubclient = GitClient(self.subsublocal_path)
+ subsubclient2 = GitClient(self.subsublocal2_path)
self.assertFalse(client.path_exists())
self.assertFalse(client.detect_presence())
self.assertTrue(client.checkout(url))
@@ -175,9 +405,32 @@ class GitClientTest(GitClientTestSetups):
self.assertTrue(subclient.path_exists())
self.assertTrue(subsubclient.path_exists())
self.assertFalse(subclient2.path_exists())
- new_version = "test_branch2"
+ new_version = "test_branch"
+ self.assertTrue(client.update(new_version))
+ # checking that update doesnt make submodule disappear (git default behavior)
+ self.assertTrue(subclient2.path_exists())
+ self.assertTrue(subsubclient2.path_exists())
+ self.assertTrue(subclient.path_exists())
+ self.assertTrue(subsubclient.path_exists())
+ subprocess.check_call("touch submodif.txt", shell=True, cwd=self.sublocal2_path)
+ subprocess.check_call("git add submodif.txt", shell=True, cwd=self.sublocal2_path)
+ subprocess.check_call("git commit -m submodif", shell=True, cwd=self.sublocal2_path)
+ subprocess.check_call("git add submodule2", shell=True, cwd=self.local_path)
+ subprocess.check_call("git commit -m submodule2_modif", shell=True, cwd=self.local_path)
+ oldnew_version = "master"
+ self.assertTrue(client.update(oldnew_version))
+ # checking that update doesnt make submodule2 disappear (git default behavior)
+ self.assertTrue(subclient2.path_exists())
+ self.assertTrue(subsubclient2.path_exists())
+ self.assertTrue(subclient.path_exists())
+ self.assertTrue(subsubclient.path_exists())
self.assertTrue(client.update(new_version))
+ # checking that update still has submodule with submodif
self.assertTrue(subclient2.path_exists())
+ self.assertTrue(subsubclient2.path_exists())
+ self.assertTrue(subclient.path_exists())
+ self.assertTrue(subsubclient.path_exists())
+ self.assertTrue(os.path.exists(os.path.join(self.sublocal2_path, "submodif.txt")))
def test_status(self):
url = self.remote_path
@@ -197,10 +450,12 @@ class GitClientTest(GitClientTestSetups):
subprocess.check_call("touch subsubnew.txt", shell=True, cwd=self.subsublocal_path)
output = client.get_status()
- self.assertEqual(' M ./fixed.txt\n M ./submodule\n M ./subfixed.txt\n M ./subsubmodule\n M ./subsubfixed.txt', output.rstrip())
+ self.assertEqual(
+ ' M ./fixed.txt\n M ./submodule\n M ./subfixed.txt\n M ./subsubmodule\n M ./subsubfixed.txt', output.rstrip())
output = client.get_status(untracked=True)
- self.assertEqual(' M ./fixed.txt\n M ./submodule\n?? ./new.txt\n M ./subfixed.txt\n M ./subsubmodule\n?? ./subnew.txt\n M ./subsubfixed.txt\n?? ./subsubnew.txt', output.rstrip())
+ self.assertEqual(
+ ' M ./fixed.txt\n M ./submodule\n?? ./new.txt\n M ./subfixed.txt\n M ./subsubmodule\n?? ./subnew.txt\n M ./subsubfixed.txt\n?? ./subsubnew.txt', output.rstrip())
output = client.get_status(basepath=os.path.dirname(self.local_path), untracked=True)
self.assertEqual(' M local/fixed.txt\n M local/submodule\n?? local/new.txt\n M local/subfixed.txt\n M local/subsubmodule\n?? local/subnew.txt\n M local/subsubfixed.txt\n?? local/subsubnew.txt', output.rstrip())
diff --git a/test/test_hg.py b/test/test_hg.py
index 6770112..f45a7c6 100644
--- a/test/test_hg.py
+++ b/test/test_hg.py
@@ -276,6 +276,25 @@ class HGClientLogTest(HGClientTestSetups):
self.assertEquals('initial', log[0]['message'])
+class HGAffectedFilesTest(HGClientTestSetups):
+
+ @classmethod
+ def setUpClass(self):
+ HGClientTestSetups.setUpClass()
+ client = HgClient(self.local_path)
+ client.checkout(self.local_url)
+
+ def test_get_log_defaults(self):
+ client = HgClient(self.local_path)
+ client.checkout(self.local_url)
+ log = client.get_log(limit=1)[0]
+ affected = client.get_affected_files(log['id'])
+
+ self.assertEqual(sorted(['deleted-fs.txt', 'deleted.txt']),
+ sorted(affected))
+
+
+
class HGDiffStatClientTest(HGClientTestSetups):
@classmethod
diff --git a/test/test_svn.py b/test/test_svn.py
index 7035d55..ee4f067 100644
--- a/test/test_svn.py
+++ b/test/test_svn.py
@@ -322,6 +322,24 @@ class SvnClientLogTest(SvnClientTestSetups):
self.assertEquals('initial', log[0]['message'])
+class SVNClientAffectedFiles(SvnClientTestSetups):
+
+ @classmethod
+ def setUpClass(self):
+ SvnClientTestSetups.setUpClass()
+ client = SvnClient(self.local_path)
+ client.checkout(self.local_url)
+
+ def test_get_affected_files(self):
+ client = SvnClient(self.local_path)
+ client.checkout(self.local_url)
+ log = client.get_log(limit=1)[0]
+ affected = client.get_affected_files(log['id'])
+
+ self.assertEqual(sorted(['deleted-fs.txt', 'deleted.txt']),
+ sorted(affected))
+
+
class SvnDiffStatClientTest(SvnClientTestSetups):
@classmethod
--
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