pax_global_header00006660000000000000000000000064127074703600014520gustar00rootroot0000000000000052 comment=2b37211fabca12486fcb5af26a6897de6978d457 wstool-0.1.13/000077500000000000000000000000001270747036000131315ustar00rootroot00000000000000wstool-0.1.13/.gitignore000066400000000000000000000001721270747036000151210ustar00rootroot00000000000000*.orig *.swp *.pyc *.DS_Store *~ *.log src/wstool.egg-info/* build/* dist/* .coverage *.deb *.tgz doc-pak description-pak wstool-0.1.13/.travis.yml000066400000000000000000000014571270747036000152510ustar00rootroot00000000000000language: python python: - "2.6" - "2.7" - "3.2" - "3.3" - "3.4" # command to install dependencies install: # develop seems to be required by travis since 02/2013 - python setup.py build develop - sudo apt-get clean - sudo apt-get install -qq zsh - sudo pip install vcstools nose-cov coverage pyyaml coveralls # Set git config to silence some stuff in the tests - git config --global user.email "foo@example.com" - git config --global user.name "Foo Bar" # Set the hg user - echo -e "[ui]\nusername = Your Name " >> ~/.hgrc # Set the bzr user - bzr whoami "Your Name " # command to run tests script: - python -c 'import sys; print(sys.path)' - nosetests --with-coverage --cover-package=wstool notifications: email: false after_success: - coveralls wstool-0.1.13/LICENSE000066400000000000000000000031051270747036000141350ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2010, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. wstool-0.1.13/MANIFEST.in000066400000000000000000000000671270747036000146720ustar00rootroot00000000000000recursive-include completion * recursive-include doc * wstool-0.1.13/Makefile000066400000000000000000000010631270747036000145710ustar00rootroot00000000000000.PHONY: all setup clean_dist distro clean install testsetup test NAME='wstool' VERSION=$(shell grep version ./src/wstool/__version__.py | sed 's,version = ,,') OUTPUT_DIR=deb_dist all: echo "noop for debbuild" setup: echo "building version ${VERSION}" clean_dist: -rm -rf src/wstool.egg-info -rm -rf dist -rm -rf deb_dist distro: setup clean_dist python setup.py sdist clean: clean_dist install: distro sudo checkinstall python setup.py install testsetup: echo "running tests" test: testsetup nosetests --with-coverage --cover-package=wstool wstool-0.1.13/README.rst000066400000000000000000000003011270747036000146120ustar00rootroot00000000000000wstool ========== Command-line tools for maintaining a workspace of projects from multiple version-control systems. Installing ---------- Using the pypi package:: $ pip install -U wstool wstool-0.1.13/completion/000077500000000000000000000000001270747036000153025ustar00rootroot00000000000000wstool-0.1.13/completion/_wstool000066400000000000000000000033201270747036000167110ustar00rootroot00000000000000#compdef wstool # Software License Agreement (BSD License) # # Copyright (c) 2010, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. _wstool () { local e e=$(dirname ${funcsourcetrace[1]%:*})/wstool-completion.bash if [ -f $e ]; then . $e fi } wstool-0.1.13/completion/wstool-completion.bash000066400000000000000000000167521270747036000216520ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2010, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # Programmable completion for the wstool command under bash. Source # this file (or on some systems add it to ~/.bash_completion and start a new # shell) # ZSH support if [[ -n ${ZSH_VERSION-} ]]; then autoload -U +X bashcompinit && bashcompinit fi # put here to be extendable if [ -z "$WSTOOL_BASE_COMMANDS" ]; then _WSTOOL_BASE_COMMANDS="help init set merge info remove diff status update --version" fi # Based originally on the bzr/svn bash completition scripts. _wstool_complete() { local cur cmds cmdOpts opt helpCmds optBase i COMPREPLY=() cur=${COMP_WORDS[COMP_CWORD]} cmds=$_WSTOOL_BASE_COMMANDS if [[ $COMP_CWORD -eq 1 ]] ; then COMPREPLY=( $( compgen -W "$cmds" -- $cur ) ) return 0 fi # if not typing an option, or if the previous option required a # parameter, then fallback on ordinary filename expansion helpCmds='help|--help|h|\?' if [[ ${COMP_WORDS[1]} != @@($helpCmds) ]] && \ [[ "$cur" != -* ]] ; then case ${COMP_WORDS[1]} in info|diff|di|status|st|remove|rm|update|up) cmdOpts=`wstool info --only=localname 2> /dev/null | sed 's,:, ,g'` COMPREPLY=( $( compgen -W "$cmdOpts" -- $cur ) ) ;; set) if [[ $COMP_CWORD -eq 2 ]]; then cmdOpts=`wstool info --only=localname 2> /dev/null | sed 's,:, ,g'` COMPREPLY=( $( compgen -W "$cmdOpts" -- $cur ) ) elif [[ $COMP_CWORD -eq 3 ]]; then cmdOpts=`wstool info ${COMP_WORDS[2]} --only=uri 2> /dev/null` COMPREPLY=( $( compgen -W "$cmdOpts" -- $cur ) ) else if [[ ${COMP_WORDS[$(( $COMP_CWORD - 1 ))]} == "--version-new" ]]; then cmdOpts=`wstool info ${COMP_WORDS[2]} --only=version 2> /dev/null|sed 's/,$//'` COMPREPLY=( $( compgen -W "$cmdOpts" -- $cur ) ) fi fi ;; esac return 0 fi cmdOpts= case ${COMP_WORDS[1]} in status|st) cmdOpts="-t --target-workspace --untracked" ;; diff|di) cmdOpts="-t --target-workspace" ;; init) cmdOpts="-t --target-workspace --continue-on-error" ;; merge) cmdOpts="-t --target-workspace -y --confirm-all -r --merge-replace -k --merge-keep -a --merge-kill-append" ;; set) cmdOpts="-t --target-workspace --git --svn --bzr --hg --uri -v --version-new --detached -y --confirm" ;; remove|rm) cmdOpts="-t --target-workspace" ;; update|up) cmdOpts="-t --target-workspace --delete-changed-uris --abort-changed-uris --backup-changed-uris" ;; snapshot) cmdOpts="-t --target-workspace" ;; info) cmdOpts="-t --target-workspace --data-only --no-pkg-path --pkg-path-only --only --yaml" ;; *) ;; esac cmdOpts="$cmdOpts --help -h" # take out options already given for (( i=2; i<=$COMP_CWORD-1; ++i )) ; do opt=${COMP_WORDS[$i]} case $opt in --*) optBase=${opt/=*/} ;; -*) optBase=${opt:0:2} ;; esac cmdOpts=" $cmdOpts " cmdOpts=${cmdOpts/ ${optBase} / } # take out alternatives case $optBase in -h) cmdOpts=${cmdOpts/ --help / } ;; --help) cmdOpts=${cmdOpts/ -h / } ;; -t) cmdOpts=${cmdOpts/ --target-workspace / } ;; --target-workspace) cmdOpts=${cmdOpts/ -t / } ;; --delete-changed-uris) cmdOpts=${cmdOpts/ --abort-changed-uris / } cmdOpts=${cmdOpts/ --backup-changed-uris / } ;; --abort-changed-uris) cmdOpts=${cmdOpts/ --delete-changed-uris / } cmdOpts=${cmdOpts/ --backup-changed-uris / } ;; --backup-changed-uris) cmdOpts=${cmdOpts/ --delete-changed-uris / } cmdOpts=${cmdOpts/ --abort-changed-uris / } ;; # scm options --svn) cmdOpts=${cmdOpts/ --git / } cmdOpts=${cmdOpts/ --hg / } cmdOpts=${cmdOpts/ --bzr / } cmdOpts=${cmdOpts/ --detached / } ;; --git) cmdOpts=${cmdOpts/ --svn / } cmdOpts=${cmdOpts/ --hg / } cmdOpts=${cmdOpts/ --bzr / } cmdOpts=${cmdOpts/ --detached / } ;; --hg) cmdOpts=${cmdOpts/ --git / } cmdOpts=${cmdOpts/ --svn / } cmdOpts=${cmdOpts/ --bzr / } cmdOpts=${cmdOpts/ --detached / } ;; --bzr) cmdOpts=${cmdOpts/ --git / } cmdOpts=${cmdOpts/ --hg / } cmdOpts=${cmdOpts/ --svn / } cmdOpts=${cmdOpts/ --detached / } ;; --detached) cmdOpts=${cmdOpts/ --git / } cmdOpts=${cmdOpts/ --hg / } cmdOpts=${cmdOpts/ --bzr / } cmdOpts=${cmdOpts/ --svn / } ;; # merge options --merge-replace) cmdOpts=${cmdOpts/ --merge-keep / } cmdOpts=${cmdOpts/ --merge-kill-append / } cmdOpts=${cmdOpts/ -r / } cmdOpts=${cmdOpts/ -a / } cmdOpts=${cmdOpts/ -k / } ;; --merge-keep) cmdOpts=${cmdOpts/ --merge-replace / } cmdOpts=${cmdOpts/ --merge-kill-append / } cmdOpts=${cmdOpts/ -r / } cmdOpts=${cmdOpts/ -a / } cmdOpts=${cmdOpts/ -k / } ;; --merge-kill-append) cmdOpts=${cmdOpts/ --merge-keep / } cmdOpts=${cmdOpts/ --merge-replace / } cmdOpts=${cmdOpts/ -r / } cmdOpts=${cmdOpts/ -a / } cmdOpts=${cmdOpts/ -k / } ;; -r) cmdOpts=${cmdOpts/ --merge-keep / } cmdOpts=${cmdOpts/ --merge-kill-append / } cmdOpts=${cmdOpts/ --merge-replace / } cmdOpts=${cmdOpts/ -a / } cmdOpts=${cmdOpts/ -k / } ;; -a) cmdOpts=${cmdOpts/ --merge-keep / } cmdOpts=${cmdOpts/ --merge-kill-append / } cmdOpts=${cmdOpts/ --merge-replace / } cmdOpts=${cmdOpts/ -r / } cmdOpts=${cmdOpts/ -k / } ;; -k) cmdOpts=${cmdOpts/ --merge-keep / } cmdOpts=${cmdOpts/ --merge-kill-append / } cmdOpts=${cmdOpts/ --merge-replace / } cmdOpts=${cmdOpts/ -a / } cmdOpts=${cmdOpts/ -r / } ;; esac # skip next option if this one requires a parameter if [[ $opt == @@($optsParam) ]] ; then ((++i)) fi done COMPREPLY=( $( compgen -W "$cmdOpts" -- $cur ) ) return 0 } complete -F _wstool_complete -o default wstool wstool-0.1.13/doc/000077500000000000000000000000001270747036000136765ustar00rootroot00000000000000wstool-0.1.13/doc/Makefile000066400000000000000000000155121270747036000153420ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/wstool.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/wstool.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/wstool" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/wstool" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." upload: html # set write permission for group so that everybody can overwrite existing files on the webserver chmod -R g+w _build/html/ scp -pr _build/html/ rosbot@ros.osuosl.org:/home/rosbot/docs/independent/api/wstool xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." wstool-0.1.13/doc/changelog.rst000066400000000000000000000034271270747036000163650ustar00rootroot000000000000000.1.13 ------ - Fix to avoid errors due to installing man pages with OS X's 10.11's new SIP settings. - Added option to show a simplified version info table. - Added the -m (timeout), -v (verbose), and -j (parallel jobs) options to each command. - Contributors: @NikolausDemmel, @wkentaro 0.1.12 ------ - Fix command line arguments of ``wstool scrape``. 0.1.11 ------ - Changed the way ``.bak`` files are created when overwriting existing configurations. - Added the Scrape command. - Added default git branch and status to ``wstool fetch --info``. - Added versioned dependency on vcstools ``0.1.38`` to make use of new API features. 0.1.10 ------ - Fix regression which broke the -j option. - Enable pretty printing of the ``.rosinstall`` file's YAML. 0.1.9 ----- - Fix for zsh completion. - Fixed version dependency on vcstools for debian. 0.1.8 ----- - Fix for installation issue. 0.1.7 ----- - Added installation of generated man pages. - Added installation of shell completion for wstool. - Improved output of wstool info with the new get_current_version_label in vcstools. - Added a foreach command. - Added a ``--root`` option to wstool info. - Enhanced the ``--update`` option for wstool set. - Now uses multiple threads for network operations by default. - Some other minor fixes and improvements and docs. 0.1.5 ----- - Releasing to allow changes for new platform vivid. - Fix svn diff for change in output with svn 1.7.9. - info command shows information about unmanaged paths. 0.1.4 ----- - Fix detection of path conflicts #24 (https://github.com/vcstools/wstool/pull/24). 0.0.3 ----- - not using ROS_WORKSPACE anymore - fix to "wstool cmd --help" 0.0.2 ----- - fix #2 creating "wstool2 file instaed of ".rosinstall" 0.0.1 ----- - Initial creation based on functions inrosinstall wstool-0.1.13/doc/conf.py000066400000000000000000000206741270747036000152060ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # wstool documentation build configuration file, created by # sphinx-quickstart on Tue Aug 11 08:44:18 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os import imp file = None try: file, pathname, description = imp.find_module('__version__', ['../src/wstool']) vermod = imp.load_module('__version__', file, pathname, description) version = vermod.version finally: if file is not None: file.close() # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.intersphinx'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'wstool' copyright = u'2011, Willow Garage' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = version # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'haiku' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'wstooldoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'wstool.tex', u'wstool Documentation', u'Tully Foote, Thibault Kruse, Ken Conley, Brian Gerkey', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Additional stuff for the LaTeX preamble. # latex_preamble = '' # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'wstool', u'wstool Documentation', [u'Tully Foote, Thibault Kruse, Ken Conley, Brian Gerkey'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'wstool', u'wstool Documentation', u'Tully Foote, Thibault Kruse, Ken Conley, Brian Gerkey', 'wstool', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} wstool-0.1.13/doc/developers_guide.rst000066400000000000000000000032651270747036000177630ustar00rootroot00000000000000Developer's Guide ================= Changelog --------- .. toctree:: :maxdepth: 1 changelog Bug reports and feature requests -------------------------------- - `Submit a bug report `_ Developer Setup --------------- The wstool source can be downloaded using Mercurial:: $ git clone https://github.com/vcstools/wstool.git You will also need vcstools, which you can either install using pip or download using:: $ git clone https://github.com/vcstools/vcstools.git $ cd vcstools $ python develop wstool uses `setuptools `_, 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 environment: $ cd wstool $ python develop Testing ------- Install test dependencies :: $ pip install nose $ pip install mock wstool uses `Python nose `_ for testing, which is a fairly simple and straightforward test framework. The wstool mainly use :mod:`unittest` to construct test fixtures, but with nose you can also just write a function that starts with the name ``test`` and use normal ``assert`` statements. wstool also uses `mock `_ to create mocks for testing. You can run the tests, including coverage, as follows: :: $ cd wstool $ make test Documentation ------------- Sphinx is used to provide API documentation for wstool. The documents are stored in the ``doc`` sub-directory. You can build the docs as follows: :: $ cd wstool/doc $ make html wstool-0.1.13/doc/index.rst000066400000000000000000000025521270747036000155430ustar00rootroot00000000000000wstool ====== .. module:: wstool .. moduleauthor:: Tully Foote , Thibault Kruse , Ken Conley Using wstool you can update several folders using a variety of SCMs (SVN, Mercurial, git, Bazaar) with just one command. That way you can more effectively manage source code workspaces. The wstool package provides a Python API for interacting with a source code workspace as well as a group of command line tools. Rosinstall leverages the :mod:`vcstools` package for source control and stores its state in .rosinstall files. Command Line Tools: =================== .. toctree:: :maxdepth: 2 wstool_usage Installation ============ Ubuntu ------ On Ubuntu the recommended way to install rosinstall is to use apt. :: sudo apt-get install python-wstool Other Platforms --------------- On other platforms rosinstall is available on pypi and can be installed via ``pip`` :: pip install -U wstool or ``easy_install``: :: easy_install -U wstool vcstools Rosinstall File Format: ======================= .. toctree:: :maxdepth: 2 rosinstall_file_format Advanced: rosinstall developers/contributors ============================================ .. toctree:: :maxdepth: 2 developers_guide Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` wstool-0.1.13/doc/make.bat000066400000000000000000000150661270747036000153130ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source set I18NSPHINXOPTS=%SPHINXOPTS% source if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\wstool.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\wstool.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end wstool-0.1.13/doc/rosinstall_file_format.rst000066400000000000000000000025061270747036000211740ustar00rootroot00000000000000rosinstall file format ====================== Format ------ The rosinstall file format is a yaml document. It is a list of top level dictionaries. Each top level dictionary is expected to have one of the vcs type keys and no other keys. Inside every top level dictionary there is one required key, ``local-name`` this represents the path where to install files. It will support both workspace relative paths as well as absolute paths. Each of the vcs type keys requires a ``uri`` key, and optionally takes a ``version`` key. Top Level Keys -------------- The valid keys are ``svn``, ``hg``, ``git``, ``bzr``. Each key represents a form of version control system to use. These are supported from the vcstools module. Example rosinstall syntax: -------------------------- Below is an example rosinstall syntax with examples of most of the possible permutations: :: - svn: {local-name: some/local/path2, uri: /some/local/uri} - hg: {local-name: some/local/path3, uri: http://some/uri, version: 123} - git: {local-name: /some/local/aboslute/path, uri: http://some/uri, version: 123} - bzr: {local-name: some/local/path4, uri: http://some/uri, version: 123} Things to note are: - ``version`` is optional though recommended. - Absolute or relative paths are valid for ``local-name`` - ``uri`` can be a local file path to a repository. wstool-0.1.13/doc/wstool_usage.rst000066400000000000000000000177441270747036000171600ustar00rootroot00000000000000wstool: A tool for managing source code workspaces ================================================== wstool allows manipulation of a set of version-controlled folders as specified in a workspace definition file. .. contents:: Contents :depth: 3 Usage ----- :: wstool is a command to manipulate multiple version controlled folders. Official usage: wstool CMD [ARGS] [OPTIONS] wstool will try to infer install path from context Type 'wstool help' for usage. Options: help provide help for commands init set up a directory as workspace set add or changes one entry from your workspace config merge merges your workspace with another config set remove (rm) remove an entry from your workspace config, without deleting files update (up) update or check out some of your config elements info Overview of some entries status (st) print the change status of files in some SCM controlled entries diff (di) print a diff over some SCM controlled entries init ~~~~ set up a directory as workspace wstool init does the following: 1. Reads folder/file/web-uri SOURCE_PATH looking for a rosinstall yaml 2. Creates new .rosinstall file at TARGET-PATH configured SOURCE_PATH can e.g. be a folder like /opt/ros/electric If PATH is not given, uses current folder. :: Usage: wstool init [TARGET_PATH [SOURCE_PATH]]? Options:: -h, --help show this help message and exit --continue-on-error Continue despite checkout errors -j JOBS, --parallel=JOBS How many parallel threads to use for installing Examples:: $ wstool init ~/jade /opt/ros/jade set ~~~ add or changes one entry from your workspace config The command will infer whether you want to add or modify an entry. If you modify, it will only change the details you provide, keeping those you did not provide. if you only provide a uri, will use the basename of it as localname unless such an element already exists. The command only changes the configuration, to checkout or update the element, run wstool update afterwards. :: Usage: wstool set [localname] [SCM-URI]? [--(detached|svn|hg|git|bzr)] [--version=VERSION]] Options: -h, --help show this help message and exit --detached make an entry unmanaged (default for new element) -v VERSION, --version-new=VERSION point SCM to this version --git make an entry a git entry --svn make an entry a subversion entry --hg make an entry a mercurial entry --bzr make an entry a bazaar entry -y, --confirm Do not ask for confirmation -u, --update update repository after set -t WORKSPACE, --target-workspace=WORKSPACE which workspace to use Examples:: $ wstool set robot_model --hg https://kforge.ros.org/robotmodel/robot_model $ wstool set robot_model --version robot_model-1.7.1 $ wstool set robot_model --detached merge ~~~~~ The command merges config with given other rosinstall element sets, from files or web uris. The default workspace will be inferred from context, you can specify one using -t. By default, when an element in an additional URI has the same local-name as an existing element, the existing element will be replaced. In order to ensure the ordering of elements is as provided in the URI, use the option ``--merge-kill-append``. :: Usage: wstool merge [URI] [OPTIONS] Options: -h, --help show this help message and exit -a, --merge-kill-append merge by deleting given entry and appending new one -k, --merge-keep (default) merge by keeping existing entry and discarding new one -r, --merge-replace merge by replacing given entry with new one maintaining ordering -y, --confirm-all do not ask for confirmation unless strictly necessary -t WORKSPACE, --target-workspace=WORKSPACE which workspace to use Examples:: $ wstool merge someother.rosinstall You can use '-' to pipe in input, as an example:: $ roslocate info robot_mode | wstool merge - update ~~~~~~ update or check out some of your config elements This command calls the SCM provider to pull changes from remote to your local filesystem. In case the url has changed, the command will ask whether to delete or backup the folder. :: Usage: wstool update [localname]* Options: -h, --help show this help message and exit --delete-changed-uris Delete the local copy of a directory before changing uri. --abort-changed-uris Abort if changed uri detected --continue-on-error Continue despite checkout errors --backup-changed-uris=BACKUP_CHANGED backup the local copy of a directory before changing uri to this directory. -j JOBS, --parallel=JOBS How many parallel threads to use for installing -v, --verbose Whether to print out more information -t WORKSPACE, --target-workspace=WORKSPACE which workspace to use Examples:: $ wstool update -t ~/jade $ wstool update robot_model geometry info ~~~~ Overview of some entries The Status (S) column shows x for missing L for uncommited (local) changes V for difference in version and/or remote URI C for difference in local and remote versions The 'Version-Spec' column shows what tag, branch or revision was given in the .rosinstall file. The 'UID' column shows the unique ID of the current (and specified) version. The 'URI' column shows the configured URL of the repo. If status is V, the difference between what was specified and what is real is shown in the respective column. For SVN entries, the url is split up according to standard layout (trunk/tags/branches). The ROS_PACKAGE_PATH follows the order of the table, earlier entries overlay later entries. When given one localname, just show the data of one element in list form. This also has the generic properties element which is usually empty. The ``--only`` option accepts keywords: ['path', 'localname', 'version', 'revision', 'cur_revision', 'uri', 'cur_uri', 'scmtype'] :: Usage: wstool info [localname]* [OPTIONS] Options: -h, --help show this help message and exit --root Show workspace root path --data-only Does not provide explanations --only=ONLY Shows comma-separated lists of only given comma- separated attribute(s). --yaml Shows only version of single entry. Intended for scripting. --fetch When used, retrieves version information from remote (takes longer). -u, --untracked Also show untracked files as modifications -t WORKSPACE, --target-workspace=WORKSPACE which workspace to use -m, --managed-only only show managed elements Examples:: $ wstool info -t ~/ros/jade $ wstool info robot_model $ wstool info --yaml $ wstool info --only=path,cur_uri,cur_revision robot_model geometry status ~~~~~~ print the change status of files in some SCM controlled entries. The status columns meanings are as the respective SCM defines them. :: Usage: wstool status [localname]* Options: -h, --help show this help message and exit --untracked Also shows untracked files -t WORKSPACE, --target-workspace=WORKSPACE which workspace to use diff ~~~~ print a diff over some SCM controlled entries :: Usage: wstool diff [localname]* Options: -h, --help show this help message and exit --untracked Also shows untracked files -t WORKSPACE, --target-workspace=WORKSPACE which workspace to use wstool-0.1.13/scripts/000077500000000000000000000000001270747036000146205ustar00rootroot00000000000000wstool-0.1.13/scripts/wstool000077500000000000000000000047401270747036000161020ustar00rootroot00000000000000#!/usr/bin/env python # Software License Agreement (BSD License) # # Copyright (c) 2010, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # # Revision $Id: rosws 14389 2011-07-20 18:38:40Z tfoote $ # $Author: tfoote $ """%(prog)s is a command to manipulate ROS workspaces. Official usage: %(prog)s CMD [ARGS] [OPTIONS] %(prog)s will try to infer install path from context "%(prog)s init" replaces the rosinstall command Type '%(prog)s --help' for usage. """ from __future__ import print_function import sys try: import wstool.wstool_cli from wstool.common import MultiProjectException except ImportError as exc: sys.exit("ERROR: Cannot find required rosinstall library version, \ check your installation (also of vcstools) is up-to-date. One frequent cause \ is that rosinstall 0.5 is still installed in /usr/local/lib.\n%s" % exc) if __name__ == "__main__": try: sys.exit(wstool.wstool_cli.wstool_main(sys.argv)) except MultiProjectException as mpe: sys.exit("ERROR in config: %s" % str(mpe)) wstool-0.1.13/setup.py000066400000000000000000000104121270747036000146410ustar00rootroot00000000000000from setuptools import setup from distutils.command.build import build from distutils.command.build_py import build_py import os import sys import imp import argparse def get_version(): ver_file = None try: ver_file, pathname, description = imp.find_module('__version__', ['src/wstool']) vermod = imp.load_module('__version__', ver_file, pathname, description) version = vermod.version return version finally: if ver_file is not None: ver_file.close() def _resolve_prefix(prefix, type): # install to outside of system if OS X to avoid issues caused by # System Integrity Protection on El Caption # issue: https://github.com/vcstools/wstool/issues/81 osx_system_prefix = '/System/Library/Frameworks/Python.framework/Versions' if type == 'man': if prefix == '/usr': return '/usr/share' if sys.prefix.startswith(osx_system_prefix): return '/usr/local/share' elif type == 'bash_comp': if prefix == '/usr': return '/' if sys.prefix.startswith(osx_system_prefix): return '/usr/local' elif type == 'zsh_comp': if sys.prefix.startswith(osx_system_prefix): return '/usr/local' else: raise ValueError('not supported type') return prefix def get_data_files(prefix): data_files = [] bash_comp_dest = os.path.join(_resolve_prefix(prefix, 'bash_comp'), 'etc/bash_completion.d') data_files.append((bash_comp_dest, ['completion/wstool-completion.bash'])) zsh_comp_dest = os.path.join(_resolve_prefix(prefix, 'zsh_comp'), 'share/zsh/site-functions') data_files.append((zsh_comp_dest, ['completion/_wstool', 'completion/wstool-completion.bash'])) return data_files parser = argparse.ArgumentParser() parser.add_argument('--prefix', default='', help='prefix to install data files') opts, _ = parser.parse_known_args(sys.argv) prefix = opts.prefix data_files = get_data_files(prefix) # At present setuptools has no methods to resolve dependencies at build time, # so we need to check if sphinx is installed. # See: https://github.com/pypa/pip/issues/2381 try: from sphinx.setup_command import BuildDoc HAVE_SPHINX = True except: HAVE_SPHINX = False if HAVE_SPHINX: class WstoolBuildMan(BuildDoc): def initialize_options(self): BuildDoc.initialize_options(self) self.builder = 'man' class WstoolBuild(build): """Run additional commands before build command""" def run(self): self.run_command('build_man') build.run(self) class WstoolBuildPy(build_py): """Run additional commands before build_py command""" def run(self): self.run_command('build_man') build_py.run(self) cmdclass = dict( build=WstoolBuild, build_py=WstoolBuildPy, build_man=WstoolBuildMan, ) man_dest = os.path.join(_resolve_prefix(prefix, 'man'), 'man/man1') data_files.append((man_dest, ['build/sphinx/man/wstool.1'])) else: cmdclass = {} install_requires = ['vcstools>=0.1.38', 'pyyaml'] try: from collections import OrderedDict except ImportError: install_requires.append('ordereddict') # for python<=2.6 setup(name='wstool', version=get_version(), packages=['wstool'], package_dir={'': 'src'}, data_files=data_files, cmdclass=cmdclass, # rosinstall dependency to be kept in order not to break ros hydro install instructions install_requires=install_requires, scripts=["scripts/wstool"], author="Tully Foote", author_email="tfoote@osrfoundation.org", url="http://wiki.ros.org/wstool", download_url="http://download.ros.org/downloads/wstool/", keywords=["ROS"], classifiers=["Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "License :: OSI Approved :: BSD License"], description="workspace multi-SCM commands", long_description="""\ A tool for managing a workspace of multiple heterogenous SCM repositories """, license="BSD") wstool-0.1.13/src/000077500000000000000000000000001270747036000137205ustar00rootroot00000000000000wstool-0.1.13/src/wstool/000077500000000000000000000000001270747036000152475ustar00rootroot00000000000000wstool-0.1.13/src/wstool/__init__.py000066400000000000000000000000001270747036000173460ustar00rootroot00000000000000wstool-0.1.13/src/wstool/__version__.py000066400000000000000000000000231270747036000200750ustar00rootroot00000000000000version = '0.1.13' wstool-0.1.13/src/wstool/cli_common.py000066400000000000000000000423061270747036000177450ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2010, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. "Support for any command line interface (CLI) for wstool" try: from collections import OrderedDict except: from ordereddict import OrderedDict import os import re from optparse import OptionParser from wstool.common import samefile, MultiProjectException, select_elements ONLY_OPTION_VALID_ATTRS = ['path', 'localname', 'version', 'revision', 'cur_revision', 'uri', 'cur_uri', 'scmtype'] def get_workspace(argv, shell_path, config_filename=None, varname=None): """ If target option -t is given return value of that one. Else, if varname is given and exists, considers that one, plus, if config_filename is given, searches for a file named in config_filename in 'shell_path' and ancestors. In that case, if two solutions are found, asks the user. :param shell_path: where to look for relevant config_filename :param config_filename: optional, filename for files defining workspaces :param varname: optional, env var to be used as workspace folder :returns: abspath if a .rosinstall was found, error and exist else. """ parser = OptionParser() parser.add_option( "-t", "--target-workspace", dest="workspace", default=None, help="which workspace to use", action="store") # suppress errors based on any other options this parser is agnostic about argv2 = [x for x in argv if ((not x.startswith('-')) or x.startswith('--target-workspace=') or x.startswith('-t') or x == '--target-workspace')] (options, _) = parser.parse_args(argv2) if options.workspace is not None: if (config_filename is not None and not os.path.isfile(os.path.join(options.workspace, config_filename))): raise MultiProjectException("%s has no workspace configuration file '%s'" % (os.path.abspath(options.workspace), config_filename)) return os.path.abspath(options.workspace) varname_path = None if varname is not None and varname in os.environ: # workspace could be relative, maybe confusing, # but that's the users fault varname_path = os.environ[varname] if varname_path.strip() == '' or not os.path.isdir(varname_path): varname_path = None # use current dir current_path = None if config_filename is not None: while shell_path is not None and not shell_path == os.path.dirname(shell_path): if os.path.exists(os.path.join(shell_path, config_filename)): current_path = shell_path break shell_path = os.path.dirname(shell_path) if current_path is not None and varname_path is not None and not samefile(current_path, varname_path): raise MultiProjectException("Ambiguous workspace: %s=%s, %s" % (varname, varname_path, os.path.abspath(config_filename))) if current_path is None and varname_path is None: raise MultiProjectException("Command requires a target workspace.") if current_path is not None: return current_path else: return varname_path def _uris_match(basepath, uri1, uri2): """ True if uri2 is None or not None and same folder or equal string as uri1. Relative folders resolved using basepath """ if uri1 is None: uri1 = '' if uri2 is None: return True if ((uri1 == uri2) or (basepath is not None and os.path.isdir(os.path.join(basepath, uri1)) and os.path.realpath(os.path.join(basepath, uri2)) == os.path.realpath(os.path.join(basepath, uri1)))): return True return False def _get_svn_version_from_uri(uri): """ in case of SVN, we can use the final part of standard uri as spec version, if it follows canonical SVN layout :param uri: uri to extract version from :returns changed_uri: str, version extracted uri :returns version: str, extracted version :returns: (None, None), for empty uri or when there is no regex match for version info """ if uri is None: return None, None match = re.match('(.*/)((tags|branches|trunk)(/.*)*)', uri) if (match is not None and len(match.groups()) > 1 and uri == ''.join(match.groups()[0:2])): changed_uri = match.groups()[0] version = match.groups()[1] return changed_uri, version return None, None def _get_status_flags(basepath, elt_dict): """ returns a string where each char conveys status information about a config element entry :param basepath: path in which element lies :param elt_dict: a dict representing one elt_dict in a table :returns: str """ if 'exists' in elt_dict and elt_dict['exists'] is False: return 'x' mflag = '' if 'modified' in elt_dict and elt_dict['modified'] is True: mflag = 'M' if (('curr_uri' in elt_dict and not _uris_match(basepath, elt_dict['uri'], elt_dict['curr_uri'])) or ('specversion' in elt_dict and elt_dict['specversion'] is not None and elt_dict['actualversion'] is not None and elt_dict['specversion'] != elt_dict['actualversion'])): mflag += 'V' if (('remote_revision' in elt_dict and elt_dict['remote_revision'] != '' and elt_dict['remote_revision'] is not None and 'actualversion' in elt_dict and elt_dict['actualversion'] is not None and elt_dict['remote_revision'] != elt_dict['actualversion']) or (('version' not in elt_dict or elt_dict['version'] is None) and 'default_remote_label' in elt_dict and elt_dict['default_remote_label'] is not None and ('curr_version' not in elt_dict or elt_dict['curr_version'] != elt_dict['default_remote_label']))): mflag += 'C' return mflag def get_info_table_elements(basepath, entries, unmanaged=False): """returns a list of dict with refined information from entries""" outputs = [] for line in entries: if not 'curr_uri' in line: line['curr_uri'] = None if not 'specversion' in line: line['specversion'] = None if not 'actualversion' in line: line['actualversion'] = None if not 'curr_version' in line: line['curr_version'] = None if not 'version' in line: line['version'] = None if not 'remote_revision' in line: line['remote_revision'] = None if not 'curr_version_label' in line: line['curr_version_label'] = None output_dict = {'scm': line['scm'], 'uri': line['uri'], 'curr_uri': None, 'version': line['version'], 'localname': line['localname']} if line is None: print("Bug Warning, an element is missing") continue if line['scm'] == 'git': if (line['specversion'] is not None and len(line['specversion']) > 12): line['specversion'] = line['specversion'][0:12] if (line['actualversion'] is not None and len(line['actualversion']) > 12): line['actualversion'] = line['actualversion'][0:12] if (line['remote_revision'] is not None and len(line['remote_revision']) > 12): line['remote_revision'] = line['remote_revision'][0:12] if line['scm'] is not None: if line['scm'] == 'svn': (line['uri'], line['version']) = _get_svn_version_from_uri(uri=line['uri']) if line['curr_uri'] is not None: (line['curr_uri'], line['curr_version_label']) = _get_svn_version_from_uri( uri=line['curr_uri']) if line['scm'] in ['git', 'svn', 'hg']: line['curr_version'] = line['curr_version_label'] if line['curr_version'] is not None: output_dict['version'] = line['curr_version'] if output_dict['version'] is not None: if line['version'] != output_dict['version']: if line['version']: output_dict['version'] += " (%s)" % line['version'] else: if line['default_remote_label']: if output_dict['version'] == line['default_remote_label']: output_dict['version'] += " (=)" else: output_dict['version'] += " (%s)" % line['default_remote_label'] else: output_dict['version'] += " (-)" if (line['specversion'] is not None and line['specversion'] != '' and line['actualversion'] != line['specversion']): output_dict['matching'] = "%s (%s)" % (line['actualversion'], line['specversion']) else: output_dict['matching'] = line['actualversion'] common_prefixes = ["https://", "http://"] if line['uri'] is not None and unmanaged is False: for pre in common_prefixes: if line['uri'].startswith(pre): line['uri'] = line['uri'][len(pre):] break output_dict['uri'] = line['uri'] if line['curr_uri'] is not None: for pre in common_prefixes: if line['curr_uri'].startswith(pre): line['curr_uri'] = line['curr_uri'][len(pre):] break if (not _uris_match(basepath, line['uri'], line['curr_uri'])): output_dict['uri'] = "%s (%s)" % (line[ 'curr_uri'], line['uri']) else: output_dict['matching'] = " " output_dict['status'] = _get_status_flags(basepath, line) outputs.append(output_dict) return outputs def get_info_table(basepath, entries, data_only=False, reverse=False, unmanaged=False, selected_headers=None): """ return a refined textual representation of the entries. Provides column headers and processes data. """ headers = OrderedDict([ ('localname', "Localname"), ('status', "S"), ('scm', "SCM"), ('version', "Version (Spec)"), ('matching', "UID (Spec)"), ('uri', "URI (Spec) [http(s)://...]"), ]) # table design if unmanaged: selected_headers = ['localname', 'scm', 'uri'] elif selected_headers is None: selected_headers = headers.keys() # validate selected_headers invalid_headers = [h for h in selected_headers if h not in headers.keys()] if invalid_headers: raise ValueError('Invalid headers are passed: %s' % invalid_headers) outputs = get_info_table_elements( basepath=basepath, entries=entries, unmanaged=unmanaged) # adjust column width column_length = {} for header in list(headers.keys()): column_length[header] = len(headers[header]) for entry in outputs: if entry[header] is not None: column_length[header] = max(column_length[header], len(entry[header])) resultlines = [] if not data_only and len(outputs) > 0: header_line = ' ' for i, header in enumerate(selected_headers): output = headers[header] if i < len(selected_headers) - 1: output = output.ljust(column_length[header]) + " " header_line += output resultlines.append(header_line) header_line = ' ' for i, header in enumerate(selected_headers): output = '-' * len(headers[header]) if i < len(selected_headers) - 1: output = output.ljust(column_length[header]) + " " header_line += output resultlines.append(header_line) if reverse: outputs = reversed(outputs) for entry in outputs: if entry is None: print("Bug Warning, an element is missing") continue data_line = ' ' for i, header in enumerate(selected_headers): output = entry[header] if output is None: output = '' if i < len(selected_headers) - 1: output = output.ljust(column_length[header]) + " " data_line += output resultlines.append(data_line) return "\n".join(resultlines) def get_info_list(basepath, line, data_only=False): """ Info for a single config entry """ assert line is not None, "Bug Warning, an element is missing" headers = { 'uri': "URI:", 'curr_uri': "Current URI:", 'scm': "SCM:", 'localname': "Localname:", 'path': "Path", 'version': "Spec-Version:", 'curr_version_label': "Current-Version:", 'status': "Status:", 'specversion': "Spec-Revision:", 'actualversion': "Current-Revision:", 'properties': "Other Properties:"} # table design selected_headers = ['localname', 'path', 'status', 'scm', 'uri', 'curr_uri', 'version', 'curr_version_label', 'specversion', 'actualversion', 'properties'] line['status'] = _get_status_flags(basepath, line) header_length = 0 for header in list(headers.keys()): header_length = max(header_length, len(headers[header])) result = '' for header in selected_headers: if not data_only: title = "%s " % (headers[header].ljust(header_length)) else: title = '' if header in line: output = line[header] if output is None: output = '' result += "%s%s\n" % (title, output) return result def get_info_table_raw_csv(config, properties, localnames): """ returns raw data without decorations in comma-separated value format. allows to select properties. Given a config, collects all elements, and prints a line of each, with selected properties in the output :param properties: list of property ids to display :param localnames: which config elements to show .return: list of str, each a csv line """ lookup_required = False for attr in properties: if not attr in ONLY_OPTION_VALID_ATTRS: parser.error("Invalid --only option '%s', valids are %s" % (attr, ONLY_OPTION_VALID_ATTRS)) if attr in ['cur_revision', 'cur_uri', 'revision']: lookup_required = True elements = select_elements(config, localnames) result=[] for element in elements: if lookup_required and element.is_vcs_element(): spec = element.get_versioned_path_spec() else: spec = element.get_path_spec() output = [] for attr in properties: if 'localname' == attr: output.append(spec.get_local_name() or '') if 'path' == attr: output.append(spec.get_path() or '') if 'scmtype' == attr: output.append(spec.get_scmtype() or '') if 'uri' == attr: output.append(spec.get_uri() or '') if 'version' == attr: output.append(spec.get_version() or '') if 'revision' == attr: output.append(spec.get_revision() or '') if 'cur_uri' == attr: output.append(spec.get_curr_uri() or '') if 'cur_revision' == attr: output.append(spec.get_current_revision() or '') result.append(','.join(output)) return result wstool-0.1.13/src/wstool/common.py000066400000000000000000000343361270747036000171220ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import sys import traceback import os import copy try: from urlparse import urlparse except ImportError: from urllib.parse import urlparse # choosing multiprocessing over threading for clean Control-C # interrupts (provides terminate()) from multiprocessing import Process, Manager from vcstools.vcs_base import VcsError class MultiProjectException(Exception): pass def samefile(file1, file2): """ Test whether two pathnames reference the same actual file This is a workaround for the fact that some platforms do not have os.path.samefile (particularly windows). This is the patch that was integrated in python 3.0 (at which time we can probably remove this workaround). """ try: return os.path.samefile(file1, file2) except AttributeError: try: from nt import _getfinalpathname return _getfinalpathname(file1) == _getfinalpathname(file2) except (NotImplementedError, ImportError): # On Windows XP and earlier, two files are the same if their # absolute pathnames are the same. # Also, on other operating systems, fake this method with a # Windows-XP approximation. return os.path.abspath(file1) == os.path.abspath(file2) def conditional_abspath(uri): """ @param uri: The uri to check @return: abspath(uri) if local path otherwise pass through uri """ uri2 = urlparse(uri) # maybe it's a local file? if uri2.scheme == '': return os.path.abspath(uri) else: return uri def is_web_uri(source_uri): """ Uses heuristics to check whether uri is a web uri (as opposed to a file path) :param source_uri: string representing web uri or file path :returns: bool """ if source_uri is None or source_uri == '': return False parsed_uri = urlparse(source_uri) if (parsed_uri.scheme == '' and parsed_uri.netloc == '' and not '@' in parsed_uri.path.split('/')[0]): return False return True def normalize_uri(source_uri, base_path): """ If source_uri is none or a web uri, return it. If source_uri is a relative path, make it an absolute path. Else return it normalized :param source_uri: some uri to a file, folder, or web resource :param base_path: path to use to make relative paths absolute :returns: normalized string """ if source_uri is not None and not is_web_uri(source_uri): if os.path.isabs(source_uri): source_uri = os.path.normpath(source_uri) else: source_uri2 = os.path.normpath(os.path.join(base_path, source_uri)) # sys.stderr.write("Warning: Converted relative uri path %s to abspath %s\n" % # (source_uri, source_uri2)) source_uri = source_uri2 return source_uri def string_diff(str1_orig, str2_orig, maxlen=11, backtrack=7): """ Compares strings, returns a part of str2 depending on how many chars into the string the first difference can be found. If the difference is after maxlen, a prefix of str is removed so that only the 'backtrack'-last letters of the common prefix remain in str2. This only makes sense if str1 != str2, really. The purpose is to print str1 -> str2 without repeating a same long prefix :returns: a representation of where str2 differs from str1. """ result = str2_orig or '' if str1_orig is not None and str2_orig is not None: # we cannot be sure we have strings, might be lists, # gracefully fail convert to string str1 = str(str1_orig) str2 = str(str2_orig) result = str2 if len(str2) > len(str1): str1 = str1.ljust(len(str2)) charcompare = [x[0] == x[1] for x in zip(str(str2), str(str1))] if False in charcompare: commonprefix = str2[:charcompare.index(False)] if len(commonprefix) > maxlen: result = "...%s" % str2[len(commonprefix) - backtrack:] return result def normabspath(localname, path): """ if localname is absolute, return it normalized. If relative, return normalized join of path and localname """ # do not use realpath here as we want to keep symlinked path as such if os.path.isabs(localname) or path is None: return os.path.normpath(localname) abs_path = os.path.normpath(os.path.join(path, localname)) return abs_path def _is_parent_path(parent, child): """Return true if child is subdirectory of parent. Assumes both paths are absolute and don't contain symlinks. """ parent = os.path.normpath(parent) child = os.path.normpath(child) prefix = os.path.commonprefix([parent, child]) if prefix == parent: # Note: os.path.commonprefix operates on character basis, so # take extra care of situations like '/foo/ba' and '/foo/bar/baz' child_suffix = child[len(prefix):] child_suffix = child_suffix.lstrip(os.sep) if child == os.path.join(prefix, child_suffix): return True return False def realpath_relation(abspath1, abspath2): """ Computes the relationship abspath1 to abspath2 :returns: None, 'SAME_AS', 'PARENT_OF', 'CHILD_OF' """ assert os.path.isabs(abspath1), "Bug, %s is not absolute path" % abspath1 assert os.path.isabs(abspath2), "Bug, %s is not absolute path" % abspath2 realpath1 = os.path.realpath(abspath1) realpath2 = os.path.realpath(abspath2) if os.path.dirname(realpath1) == os.path.dirname(realpath2): if os.path.basename(realpath1) == os.path.basename(realpath2): return 'SAME_AS' return None else: if _is_parent_path(realpath1, realpath2): return 'PARENT_OF' if _is_parent_path(realpath2, realpath1): return 'CHILD_OF' return None def select_element(elements, localname): """ selects entry among elements where path or localname matches. Prefers localname matches in case of ambiguity. """ path_candidate = None if localname is not None: realpath = os.path.realpath(localname) for element in elements: if localname == element.get_local_name(): path_candidate = element break elif realpath == os.path.realpath(element.get_path()): path_candidate = element return path_candidate def select_elements(config, localnames): """ selects config elements with given localnames, returns in the order given in config If localnames has one element which is path of the config, return all elements """ if config is None: return [] if localnames is None: return config.get_config_elements() elements = config.get_config_elements() selected = [] notfound = [] for localname in localnames: element = select_element(elements, localname) if element is not None: selected.append(element) else: notfound.append(localname) if notfound != []: # if we just passed workspace path, return all workspace entries if (len(localnames) == 1 and os.path.realpath(localnames[0]) == os.path.realpath(config.get_base_path())): return config.get_config_elements() raise MultiProjectException("Unknown elements '%s'" % notfound) result = [] # select in order and remove duplicates for element in config.get_config_elements(): if element in selected: result.append(element) return result ## Multithreading The following classes help with distributing work ## over several instances, providing wrapping for starting, joining, ## collecting results, and catching Exceptions. Also they provide ## support for running groups of threads sequentially, for the case ## that some library is not thread-safe. class WorkerThread(Process): def __init__(self, worker, outlist, index): Process.__init__(self) self.worker = worker if worker is None or worker.element is None: raise MultiProjectException("Bug: Invalid Worker") self.outlist = outlist self.index = index def run(self): result = {} try: result = {'entry': self.worker.element.get_path_spec()} result_dict = self.worker.do_work() if result_dict is not None: result.update(result_dict) else: result.update( {'error': MultiProjectException("worker returned None")}) except MultiProjectException as mpe: result.update({'error': mpe}) except VcsError as vcse: result.update({'error': vcse}) except OSError as ose: result.update({'error': ose}) except Exception as exc: # this would be a bug, and we need trace to find them in # multithreaded cases. traceback.print_exc(file=sys.stderr) result.update({'error': exc}) self.outlist[self.index] = result class DistributedWork(): def __init__(self, capacity, num_threads=10, silent=True): # need managed array since we need the results later man = Manager() self.outputs = man.list([None for _ in range(capacity)]) self.threads = [] self.sequentializers = {} self.index = 0 self.num_threads = capacity if num_threads <= 0 else min(num_threads, capacity) self.silent = silent def add_thread(self, worker): thread = WorkerThread(worker, self.outputs, self.index) if self.index >= len(self.outputs): raise MultiProjectException( "Bug: Declared capacity exceeded %s >= %s" % (self.index, len(self.outputs))) self.index += 1 self.threads.append(thread) def run(self): """ Execute all collected workers, terminate all on KeyboardInterrupt """ if self.threads == []: return [] if (self.num_threads == 1): for thread in self.threads: thread.run() else: # The following code is rather delicate and may behave differently # using threading or multiprocessing. running_threads is # intentionally not used as a shrinking list because of al the # possible multithreading / interruption corner cases # Not using Pool because of KeyboardInterrupt cases try: waiting_index = 0 maxthreads = self.num_threads running_threads = [] missing_threads = copy.copy(self.threads) # we are done if all threads have finished while len(missing_threads) > 0: # we spawn more threads whenever some threads have finished if len(running_threads) < maxthreads: to_index = min( waiting_index + maxthreads - len(running_threads), len(self.threads)) for i in range(waiting_index, to_index): self.threads[i].start() running_threads.append(self.threads[i]) waiting_index = to_index # threads have exitcode only once they terminated missing_threads = [t for t in missing_threads if t.exitcode is None] running_threads = [t for t in running_threads if t.exitcode is None] if (not self.silent and len(running_threads) > 0): print("[%s] still active" % ",".join([th.worker.element.get_local_name() for th in running_threads])) for thread in running_threads: # this should prevent busy waiting thread.join(1) except KeyboardInterrupt as k: for thread in self.threads: if thread is not None and thread.is_alive(): print("[%s] terminated while active" % thread.worker.element.get_local_name()) thread.terminate() raise k self.outputs = [x for x in self.outputs if x is not None] message = '' for output in self.outputs: if "error" in output: if 'entry' in output: message += "Error processing '%s' : %s\n" % ( output['entry'].get_local_name(), output["error"]) else: message += "%s\n" % output["error"] if message != '': raise MultiProjectException(message) return self.outputs wstool-0.1.13/src/wstool/config.py000066400000000000000000000274011270747036000170720ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import os from wstool.config_elements import AVCSConfigElement, OtherConfigElement, SetupConfigElement from wstool.common import MultiProjectException, normabspath, realpath_relation, normalize_uri class Config: """ A config is a set of config elements, each of which defines a folder or file and possibly a VCS from which to update the folder. """ def __init__(self, path_specs, install_path, config_filename=None, extended_types=None, merge_strategy='KillAppend'): """ :param config_source_dict: A list (e.g. from yaml) describing the config, list of dict, each dict describing one element. :param config_filename: When given a folder, Config :param merge_strategy: how to deal with entries with equivalent path. See insert_element will look in folder for file of that name for more config source, str. """ assert install_path is not None, "Install path is None" if path_specs is None: raise MultiProjectException("Passed empty source to create config") # All API operations must grant that elements in trees have unique local_name and paths # Also managed (VCS) entries must be disjunct (meaning one cannot be in a child folder of another managed one) # The idea is that managed entries can safely be concurrently modified self.trees = [] self.base_path = os.path.abspath(install_path) self.config_filename = None if config_filename is not None: self.config_filename = os.path.basename(config_filename) # using a registry primarily for unit test design self.registry = {'svn': AVCSConfigElement, 'git': AVCSConfigElement, 'hg': AVCSConfigElement, 'bzr': AVCSConfigElement, 'tar': AVCSConfigElement} if extended_types is not None: self.registry = dict(list(self.registry.items()) + list(extended_types.items())) for path_spec in path_specs: action = self.add_path_spec(path_spec, merge_strategy) # Usual action in init should be 'Append', anything else is unusual if action == 'KillAppend': print("Replace existing entry %s by appending." % path_spec.get_local_name()) elif action == 'MergeReplace': print("Replace existing entry %s" % path_spec.get_local_name()) elif action == 'MergeKeep': print("Keep existing entry %s, discard later one" % path_spec.get_local_name()) def __str__(self): return str([str(x) for x in self.trees]) def add_path_spec(self, path_spec, merge_strategy='KillAppend'): """ add new element to this config with information provided in path spec :param merge_strategy: see insert_element :param path_specs: PathSpec objects :returns: merge action taken, see insert_element """ # compute the local_path for the config element local_path = normabspath( path_spec.get_local_name(), self.get_base_path()) if os.path.isfile(local_path): if path_spec.get_tags() is not None and 'setup-file' in path_spec.get_tags(): elem = SetupConfigElement(local_path, os.path.normpath(path_spec.get_local_name()), properties=path_spec.get_tags()) return self.insert_element(elem, merge_strategy) else: print("!!!!! Warning: Not adding file %s" % local_path) return None else: # scmtype == None kept for historic reasons here if (path_spec.get_scmtype() == None and self.config_filename is not None and os.path.exists(os.path.join(local_path, self.config_filename))): print("!!!!! Warning: Not recursing into other config folder %s containing file %s" % (local_path, self.config_filename)) return None if path_spec.get_scmtype() != None: return self._insert_vcs_path_spec(path_spec, local_path, merge_strategy) else: # keep the given local name (e.g. relative path) for other # elements, but normalize local_name = os.path.normpath(path_spec.get_local_name()) elem = OtherConfigElement(local_path, local_name, path_spec.get_uri(), path_spec.get_version(), properties=path_spec.get_tags()) return self.insert_element(elem, merge_strategy) def _insert_vcs_path_spec(self, path_spec, local_path, merge_strategy='KillAppend'): # Get the version and source_uri elements source_uri = normalize_uri(path_spec.get_uri(), self.get_base_path()) version = path_spec.get_version() try: local_name = os.path.normpath(path_spec.get_local_name()) elem = self._create_vcs_config_element( path_spec.get_scmtype(), local_path, local_name, source_uri, version, properties=path_spec.get_tags()) return self.insert_element(elem, merge_strategy) except LookupError as ex: raise MultiProjectException( "Abstracted VCS Config failed. Exception: %s" % ex) def insert_element(self, new_config_elt, merge_strategy='KillAppend'): """ Insert ConfigElement to self.trees, checking for duplicate local-name or path first. In case local_name matches, follow given strategy - KillAppend (default): remove old element, append new at the end - MergeReplace: remove first hit, insert new at that position. - MergeKeep: Discard new element In case local path matches but local name does not, raise Exception :returns: the action performed None, 'Append', 'KillAppend', 'MergeReplace', 'MergeKeep' """ removals = [] replaced = False for index, loop_elt in enumerate(self.trees): # if paths are os.path.realpath, no symlink problems. relationship = realpath_relation(loop_elt.get_path(), new_config_elt.get_path()) if relationship == 'SAME_AS': if os.path.normpath(loop_elt.get_local_name()) != os.path.normpath(new_config_elt.get_local_name()): raise MultiProjectException("Elements with different local_name target the same path: %s, %s" % (loop_elt, new_config_elt)) else: if (loop_elt == new_config_elt): return None if (merge_strategy == 'MergeReplace' or (merge_strategy == 'KillAppend' and index == len(self.trees) - 1)): self.trees[index] = new_config_elt # keep looping to check for overlap when replacing non- # scm with scm entry replaced = True if (loop_elt.is_vcs_element or not new_config_elt.is_vcs_element): return 'MergeReplace' elif merge_strategy == 'KillAppend': removals.append(loop_elt) elif merge_strategy == 'MergeKeep': return 'MergeKeep' else: raise LookupError( "No such merge strategy: %s" % str(merge_strategy)) elif ((relationship == 'CHILD_OF' and new_config_elt.is_vcs_element()) or (relationship == 'PARENT_OF' and loop_elt.is_vcs_element())): # we do not allow any elements to be children of scm elements # to allow for parallel updates and because wstool may # delete scm folders on update, and thus subfolders can be # deleted with their parents raise MultiProjectException( "Managed Element paths overlap: %s, %s" % (loop_elt, new_config_elt)) if replaced: return 'MergeReplace' for loop_elt in removals: self.trees.remove(loop_elt) self.trees.append(new_config_elt) if len(removals) > 0: return 'KillAppend' return 'Append' def remove_element(self, local_name): """ Removes element in the tree with the given local name (should be only one) :returns: True if such an element was found """ removals = [] for tree_el in self.trees: if tree_el.get_local_name() == local_name: removals.append(tree_el) if len(removals) > 0: for tree_el in removals: self.trees.remove(tree_el) return True return False def _create_vcs_config_element(self, scmtype, path, local_name, uri, version='', properties=None): try: eclass = self.registry[scmtype] except LookupError: raise MultiProjectException( "No VCS client registered for vcs type %s" % scmtype) return eclass(scmtype=scmtype, path=path, local_name=local_name, uri=uri, version=version, properties=properties) def get_base_path(self): return self.base_path def get_config_filename(self): return self.config_filename def get_source(self): """ :returns: all elements that got added by user keystrokes (CLI and changed .rosinstall) """ source_aggregate = [] for tree_el in self.trees: source_aggregate.append(tree_el.get_path_spec()) return source_aggregate def get_config_elements(self): source_aggregate = [] for tree_el in self.trees: source_aggregate.append(tree_el) return source_aggregate wstool-0.1.13/src/wstool/config_elements.py000066400000000000000000000467331270747036000207770ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import os import sys import shutil import datetime from vcstools.vcs_abstraction import get_vcs_client from vcstools.vcs_base import VcsError from wstool.common import samefile, MultiProjectException from wstool.config_yaml import PathSpec from wstool.ui import Ui # helper class class PreparationReport(object): """ Specifies after user interaction of how to perform install / update """ def __init__(self, element): self.config_element = element self.abort = False # abort ALL operations self.skip = False # skip this tree self.error = None # message self.verbose = False # verbosity self.checkout = True # checkout vs update self.backup = False # backup vs delete self.backup_path = None # where to move tree to self.inplace = False # whether to follow symlink or just delete self.timeout = None # maximum time for each checkout/update ## Each Config element provides actions on a local folder class ConfigElement(object): """ Base class for Config provides methods with not implemented exceptions. Also a few shared methods. """ def __init__(self, path, local_name, properties=None): self.path = path if path is None: raise MultiProjectException("Invalid empty path") self.local_name = local_name self.properties = properties def get_path(self): """A normalized absolute path""" return self.path def get_local_name(self): """What the user specified in his config""" return self.local_name def prepare_install(self, backup_path=None, arg_mode='abort', robust=False): """ Check whether install can be performed, asking user for decision if necessary. :param arg_mode: one of prompt, backup, delete, skip. Determines how to handle error cases :param backup_path: if arg_mode==backup, determines where to backup to :param robust: if true, operation will be aborted without changes to the filesystem and without user interaction :returns: A preparation_report instance, telling whether to checkout or to update, how to deal with existing tree, and where to backup to. """ preparation_report = PreparationReport(self) preparation_report.skip = True return preparation_report def install(self, checkout=True, backup=False, backup_path=None, inplace=False, verbose=False, timeout=None): """ Attempt to make it so that self.path is the result of checking out / updating from remote repo. No user Interaction allowed here (for concurrent mode). :param checkout: whether to checkout or update :param backup: if checking out, what to do if path exists. If true, backup_path must be set. """ raise NotImplementedError("ConfigElement install unimplemented") def get_path_spec(self): """PathSpec object with values as specified in file""" raise NotImplementedError("ConfigElement get_path_spec unimplemented") def get_properties(self): """Any meta information attached""" return self.properties def get_versioned_path_spec(self): """PathSpec where VCS elements have the version looked up""" raise NotImplementedError( "ConfigElement get_versioned_path_spec unimplemented") def is_vcs_element(self): # subclasses to override when appropriate return False def get_diff(self, basepath=None): raise NotImplementedError("ConfigElement get_diff unimplemented") def get_status(self, basepath=None, untracked=False): raise NotImplementedError("ConfigElement get_status unimplemented") def backup(self, backup_path): if not backup_path: raise MultiProjectException( "[%s] Cannot install %s. backup disabled." % (self.get_local_name(), self.get_path())) backup_path = os.path.join( backup_path, "%s_%s" % (os.path.basename(self.path), datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"))) print("[%s] Backing up %s to %s" % (self.get_local_name(), self.get_path(), backup_path)) shutil.move(self.path, backup_path) def __str__(self): return str(self.get_path_spec().get_legacy_yaml()) def __repr__(self): return str(self.get_path_spec().get_legacy_yaml()) def __eq__(self, other): if isinstance(other, self.__class__): return self.get_path_spec() == other.get_path_spec() else: return False class OtherConfigElement(ConfigElement): def __init__(self, path, local_name, uri=None, version='', properties=None): super(OtherConfigElement, self).__init__(path, local_name, properties) self.uri = uri self.version = version def install(self, checkout=True, backup=False, backup_path=None, inplace=False, verbose=False): return True def get_versioned_path_spec(self): raise MultiProjectException( "Cannot generate versioned outputs with non source types") def get_path_spec(self): "yaml as from source" version = self.version if version == '': version = None return PathSpec(local_name=self.get_local_name(), path=self.get_path(), scmtype=None, uri=self.uri, version=version, tags=self.get_properties()) def get_diff(self, basepath=None): return '' def get_status(self, basepath=None, untracked=False): return '' class SetupConfigElement(ConfigElement): """ A setup config element specifies a single file containing configuration data for a config. """ def install(self, checkout=True, backup=False, backup_path=None, inplace=False, verbose=False): return True def get_versioned_path_spec(self): raise MultiProjectException( "Cannot generate versioned outputs with non source types") def get_path_spec(self): return PathSpec(local_name=self.get_local_name(), path=self.get_path(), tags=['setup-file'] + (self.get_properties() or [])) def get_diff(self, basepath=None): return '' def get_status(self, basepath=None, untracked=False): return '' class VCSConfigElement(ConfigElement): def __init__(self, path, local_name, uri, version='', properties=None): """ Creates a config element for a VCS repository. :param path: absolute or relative path, str :param vcs_client: Object compatible with vcstools.VcsClientBase :param local_name: display name for the element, str :param uri: VCS uri to checkout/pull from, str :param version: optional revision spec (tagname, SHAID, ..., str) """ super(VCSConfigElement, self).__init__(path, local_name, properties) if uri is None: raise MultiProjectException( "Invalid scm entry having no uri attribute for path %s" % path) # strip trailing slashes if defined to not be too strict #3061 self.uri = uri.rstrip('/') self.version = version def _get_vcsc(self): raise NotImplementedError("VCSConfigElement _get_vcsc() unimplemented") def is_vcs_element(self): return True def get_vcs_type_name(self): # also see override in AVCSConfigElement return self._get_vcsc().get_vcs_type_name() def detect_presence(self): # also see override in AVCSConfigElement return self._get_vcsc().detect_presence() def path_exists(self): # we could use _get_vcsc().path_exit(), but this causes a time # penalty for initializing this is crucial for bash tab # completion return os.path.isdir(self.path) def prepare_install(self, backup_path=None, arg_mode='abort', robust=False): preparation_report = PreparationReport(self) present = self.detect_presence() if present or self.path_exists(): is_link = os.path.islink(self.path) # Directory exists see what we need to do error_message = None if not present: error_message = "Failed to detect %s presence at %s." % ( self.get_vcs_type_name(), self.path) if is_link: error_message += " Path is symlink, only symlink will be removed." else: cur_url = self._get_vcsc().get_url() if cur_url is not None: # strip trailing slashes for #3269 cur_url = cur_url.rstrip('/') if not cur_url or cur_url != self.uri.rstrip('/'): # local repositories get absolute pathnames if not (os.path.isdir(self.uri) and os.path.isdir(cur_url) and samefile(cur_url, self.uri)): if not self._get_vcsc().url_matches(cur_url, self.uri): error_message = "Url %s does not match %s requested." % ( cur_url, self.uri) if error_message is None: # update should be possible preparation_report.checkout = False else: # If robust ala continue-on-error, just error now and # it will be continued at a higher level if robust: raise MultiProjectException("Update Failed of %s: %s" % (self.path, error_message)) # prompt the user based on the error code if arg_mode == 'prompt': print("Prepare updating %s (version %s) to %s" % (self.uri, self.version, self.path)) mode = Ui.get_ui().prompt_del_abort_retry( error_message, allow_skip=True, allow_inplace=is_link) else: mode = arg_mode if mode == 'backup': preparation_report.backup = True if backup_path is None: print("Prepare updating %s (version %s) to %s" % (self.uri, self.version, self.path)) preparation_report.backup_path = \ Ui.get_ui().get_backup_path() else: preparation_report.backup_path = backup_path elif mode == 'abort': preparation_report.abort = True preparation_report.error = error_message elif mode == 'skip': preparation_report.skip = True preparation_report.error = error_message elif mode == 'delete': preparation_report.backup = False elif mode == 'inplace': preparation_report.inplace = True else: raise RuntimeError( 'Bug: Unknown option "%s" selected' % mode) return preparation_report def install(self, checkout=True, backup=True, backup_path=None, inplace=False, timeout=None, verbose=False): """ Runs the equivalent of SCM checkout for new local repos or update for existing. :param checkout: whether to use an update command or a checkout/clone command :param backup: if checkout is True and folder exists, if backup is false folder will be DELETED. :param backup_path: if checkout is true and backup is true, move folder to this location :param inplace: for symlinks, allows to delete contents at target location and checkout to there. """ if checkout is True: print("[%s] Fetching %s (version %s) to %s" % ( self.get_local_name(), self.uri, self.version, self.get_path())) if self.path_exists(): if os.path.islink(self.path): if inplace is False: # remove same as unlink os.remove(self.path) else: shutil.rmtree(os.path.realpath(self.path)) else: if backup is False: shutil.rmtree(self.path) else: self.backup(backup_path) if not self._get_vcsc().checkout(self.uri, self.version, timeout=timeout, verbose=verbose): raise MultiProjectException( "[%s] Checkout of %s version %s into %s failed." % ( self.get_local_name(), self.uri, self.version, self.get_path())) else: print("[%s] Updating %s" % (self.get_local_name(), self.get_path())) if not self._get_vcsc().update(self.version, verbose=verbose, timeout=timeout): raise MultiProjectException( "[%s] Update Failed of %s" % (self.get_local_name(), self.get_path())) print("[%s] Done." % self.get_local_name()) def get_path_spec(self): "yaml as from source" version = self.version if version == '': version = None return PathSpec(local_name=self.get_local_name(), path=self.get_path(), scmtype=self.get_vcs_type_name(), uri=self.uri, version=version, tags=self.get_properties()) def get_versioned_path_spec(self, fetch=False): "yaml looking up current version" version = self.version if version == '': version = None revision = None if version is not None: # revision is the UID of the version spec, can be them same revision = self._get_vcsc().get_version(self.version) if revision is None: sys.stderr.write("Warning: version '%s' not found for '%s'\n" % (self.version, self.local_name)) currevision = self._get_vcsc().get_version() remote_revision = self._get_vcsc().get_remote_version(fetch=fetch) curr_version = self._get_vcsc().get_current_version_label() uri = self.uri curr_uri = self._get_vcsc().get_url() # uri might be a shorthand notation equivalent to curr_uri if self._get_vcsc().url_matches(curr_uri, uri): curr_uri = uri return PathSpec(local_name=self.get_local_name(), path=self.get_path(), scmtype=self.get_vcs_type_name(), uri=self.uri, version=version, curr_version=curr_version, revision=revision, currevision=currevision, remote_revision=remote_revision, curr_uri=curr_uri, tags=self.get_properties()) def get_default_remote_label(self): """ check remote for e.g. default git branch """ return self._get_vcsc().get_default_remote_version_label() def get_diff(self, basepath=None): return self._get_vcsc().get_diff(basepath) def get_status(self, basepath=None, untracked=False): return self._get_vcsc().get_status(basepath, untracked) class AVCSConfigElement(VCSConfigElement): """ Implementation using vcstools vcsclient, works for types svn, git, hg, bzr, tar :raises: Lookup Exception for unknown types """ def __init__(self, scmtype, path, local_name, uri, version='', vcsc=None, properties=None): super(AVCSConfigElement, self).__init__(path, local_name=local_name, uri=uri, version=version, properties=properties) self.vcsc = vcsc self._scmtype = scmtype def get_vcs_type_name(self): return self._scmtype def _get_vcsc(self): # lazy initializer if self.vcsc is None: try: self.vcsc = get_vcs_client(self._scmtype, self.get_path()) except VcsError as exc: raise MultiProjectException( "Unable to create vcs client of type %s for %s: %s" % ( self._scmtype, self.get_path(), exc)) return self.vcsc def detect_presence(self): # to make more use of lazy initializer, do not instantiate # client for just this this is crucial for bash tab completion if self.get_vcs_type_name() == 'git': return os.path.exists(os.path.join(self.path, '.git')) elif self.get_vcs_type_name() == 'svn': return os.path.isdir(os.path.join(self.path, '.svn')) elif self.get_vcs_type_name() == 'hg': return os.path.isdir(os.path.join(self.path, '.hg')) elif self.get_vcs_type_name() == 'bzr': return os.path.isdir(os.path.join(self.path, '.bzr')) else: return self._get_vcsc().detect_presence() wstool-0.1.13/src/wstool/config_yaml.py000066400000000000000000000353231270747036000201160ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import os import yaml from vcstools.common import urlopen_netrc from wstool.common import MultiProjectException __REPOTYPES__ = ['svn', 'bzr', 'hg', 'git', 'tar'] __ALLTYPES__ = __REPOTYPES__ + ['other', 'setup-file'] ## The Path spec is a leightweigt object to transport the ## specification of a config element between functions, ## independently of yaml structure. ## Specifications are persisted in yaml, this file deals ## with manipulations of any such structures representing configs as ## yaml. ## get_path_spec_from_yaml turns yaml into path_spec, and pathspec ## get_legacy_yaml returns yaml. def get_yaml_from_uri(uri): """reads and parses yaml from a local file or remote uri""" stream = None try: try: if os.path.isfile(uri): try: stream = open(uri, 'r') except IOError as ioe: raise MultiProjectException( "Unable open file [%s]: %s" % (uri, ioe)) else: try: stream = urlopen_netrc(uri) except IOError as ioe2: raise MultiProjectException( "Unable to download URL [%s]: %s" % (uri, ioe2)) except ValueError as vae: raise MultiProjectException( "Is not a local file, nor a valid URL [%s] : %s" % (uri, vae)) if not stream: raise MultiProjectException("couldn't load config uri %s" % uri) try: yamldata = yaml.load(stream) except yaml.YAMLError as yame: raise MultiProjectException( "Invalid multiproject yaml format in [%s]: %s" % (uri, yame)) # we want a list or a dict, but pyyaml parses xml as string if type(yamldata) == 'str': raise MultiProjectException( "Invalid multiproject yaml format in [%s]: %s" % (uri, yamldata)) finally: if stream is not None: stream.close() return yamldata def get_path_specs_from_uri(uri, config_filename=None, as_is=False): """ Builds a list of PathSpec elements from several types of input locations, "uris". The function treats other workspace folders/files as special uris to prevent mutual conflicts. :param uri: a folder, a file, or a web url :param config_filename: name for files to be treated special as other workspaces :param as_is: do not rewrite, used for loading the current workspace config without rewriting """ if os.path.isdir(uri): if (config_filename is not None and os.path.isfile(os.path.join(uri, config_filename))): uri = os.path.join(uri, config_filename) else: # plain folders returned as themselves return [PathSpec(local_name=uri)] yaml_spec = get_yaml_from_uri(uri) if yaml_spec is None: return [] specs = [get_path_spec_from_yaml(x) for x in yaml_spec] if (config_filename is not None and not as_is and os.path.isfile(uri) and os.path.basename(uri) == config_filename): # treat config files and folders with such files special # to prevent 2 workspaces from interacting specs = rewrite_included_source(specs, os.path.dirname(uri)) return specs def rewrite_included_source(source_path_specs, source_dir): """ assumes source_path_specs is the contents of a config file in another directory source dir. It rewrites all elements, by changing any relative path relative to source dir and changing vcs types to non-vcs types types, to prevent two environments from conflicting """ for index, pathspec in enumerate(source_path_specs): local_name = os.path.normpath(os.path.join(source_dir, pathspec.get_local_name())) pathspec.set_local_name(local_name) if pathspec.get_path() is not None: path = os.path.normpath( os.path.join(source_dir, pathspec.get_path())) pathspec.set_path(path) pathspec.detach_vcs_info() source_path_specs[index] = pathspec return source_path_specs def aggregate_from_uris(config_uris, config_filename=None, allow_other_element=True): """ Builds a List of PathSpec from a list of location strings (uri, paths). If locations is a folder, attempts to find config_filename in it, and use "folder/config_filename" instead(rewriting element path and stripping scm nature), else add folder as PathSpec. Anything else, parse yaml at location, and add a PathSpec for each element. :param config_uris: source of yaml :param config_filename: file to use when given a folder :param allow_other_element: if False, discards elements to be added without SCM information """ aggregate_source_yaml = [] # build up a merged list of config elements from all given config_uris if config_uris is None: return [] for loop_uri in config_uris: source_path_specs = get_path_specs_from_uri( loop_uri, config_filename) # allow duplicates, dealt with in Config class if not allow_other_element: for spec in source_path_specs: if not spec.get_scmtype(): raise MultiProjectException( "Forbidden non-SCM element: %s (%s)" % (spec.get_local_name(), spec.get_legacy_type())) aggregate_source_yaml.extend(source_path_specs) return aggregate_source_yaml class PathSpec: def __init__(self, # localname is used as ID, currently also is used as path local_name, scmtype=None, uri=None, version=None, curr_version=None, tags=None, revision=None, currevision=None, remote_revision=None, path=None, curr_uri=None): """ Fills in local properties based on dict, unifies different syntaxes :param local-name: to be unique within config, filesystem path to folder :param scmtype: one of __ALLTYPES__ :param uri: uri from config file :param version: version label from config file (branchname, tagname, sha-id) :param cur_version: version information label(s) from VCS (branchname, remote, tracking branch) :param tags: arbirtrary meta-information (used for ROS package indexing) :param revision: unique id of label stored in version :param currrevision: unique id of actual version in file system :param path: path to folder (currently equivalent to local_name) :param curr_uri: actual remote uri used in local checkout """ self._local_name = local_name self._path = path self._uri = uri self._curr_uri = curr_uri self._version = version self._curr_version = curr_version self._scmtype = scmtype self._tags = tags or [] self._revision = revision self._currevision = currevision self._remote_revision = remote_revision def __str__(self): return str(self.get_legacy_yaml()) def __repr__(self): return "PathSpec(%s)" % self.__str__() def __eq__(self, other): if isinstance(other, self.__class__): return self.__dict__ == other.__dict__ else: return False def __ne__(self, other): return not self.__eq__(other) def detach_vcs_info(self): """if wrapper has VCS information, remove it to make it a plain folder""" if self._scmtype is not None: self._scmtype = None self._uri = None self._version = None self._curr_version = None self._revision = None self._currevision = None self._remote_revision = None def get_legacy_type(self): """return one of __ALLTYPES__""" if self._scmtype is not None: return self._scmtype elif self._tags is not None and 'setup-file' in self._tags: return 'setup-file' return 'other' def get_legacy_yaml(self): """ return something like {hg: {local-name: common, version: common-1.0.2, uri: https://kforge.org/common/}} """ # TODO switch to new syntax properties = {'local-name': self._local_name} if self._uri is not None: properties['uri'] = self._uri if self._version is not None: properties['version'] = self._version if self._tags is not None: for tag in self._tags: if tag != 'setup-file' and tag != []: if type(tag) == dict: properties.update(tag) else: properties[tag] = None yaml_dict = {self.get_legacy_type(): properties} return yaml_dict def get_local_name(self): return self._local_name def set_local_name(self, local_name): self._local_name = local_name def get_path(self): return self._path def set_path(self, path): self._path = path def get_tags(self): return self._tags def get_scmtype(self): return self._scmtype def get_version(self): return self._version def get_curr_version(self): return self._curr_version def get_revision(self): return self._revision def get_current_revision(self): return self._currevision def get_remote_revision(self): return self._remote_revision def get_uri(self): return self._uri def get_curr_uri(self): return self._curr_uri def get_path_spec_from_yaml(yaml_dict): """ Fills in local properties based on dict, unifies different syntaxes """ local_name = None uri = None version = None scmtype = None tags = [] if type(yaml_dict) != dict: raise MultiProjectException( "Yaml for each element must be in YAML dict form: %s " % yaml_dict) # old syntax: # - hg: {local-name: common_rosdeps, # version: common_rosdeps-1.0.2, # uri: https://kforge.ros.org/common/rosdepcore} # - setup-file: {local-name: /opt/ros/fuerte/setup.sh} # - other: {local-name: /opt/ros/fuerte/share/ros} # - other: {local-name: /opt/ros/fuerte/share} # - other: {local-name: /opt/ros/fuerte/stacks} if yaml_dict is None or len(yaml_dict) == 0: raise MultiProjectException("no element in yaml dict.") if len(yaml_dict) > 1: raise MultiProjectException( "too many keys in element dict %s" % (list(yaml_dict.keys()))) if not list(yaml_dict.keys())[0] in __ALLTYPES__: raise MultiProjectException( "Unknown element type '%s'" % (list(yaml_dict.keys())[0])) firstkey = list(yaml_dict.keys())[0] if firstkey in __REPOTYPES__: scmtype = list(yaml_dict.keys())[0] if firstkey == 'setup-file': tags.append('setup-file') values = yaml_dict[firstkey] if values is not None: for key, value in list(values.items()): if key == "local-name": local_name = value elif key == "meta": tags.append({key: value}) elif key == "uri": uri = value elif key == "version": version = value else: raise MultiProjectException( "Unknown key %s in %s" % (key, yaml_dict)) # global validation if local_name is None: raise MultiProjectException( "Config element without a local-name: %s" % (yaml_dict)) if scmtype != None: if uri is None: raise MultiProjectException( "scm type without declared uri in %s" % (values)) # local_name is fixed, path may be normalized, made absolute, etc. path = local_name return PathSpec(local_name=local_name, path=path, scmtype=scmtype, uri=uri, version=version, tags=tags) def generate_config_yaml(config, filename, header, pretty=False, sort_with_localname=False): """ Writes file filename with header first and then the config as yaml """ if not os.path.exists(config.get_base_path()): os.makedirs(config.get_base_path()) config_filepath = os.path.realpath(os.path.join(config.get_base_path(), filename)) with open(config_filepath, 'w+b') as f: if header is not None: f.write(header.encode('UTF-8')) if sort_with_localname: items = [x.get_legacy_yaml() for x in sorted(config.get_source(), key=lambda x:x.get_local_name())] else: items = [x.get_legacy_yaml() for x in config.get_source()] if not items: return if pretty: content = yaml.safe_dump(items, allow_unicode=True, default_flow_style=False) else: content = yaml.safe_dump(items) f.write(content.encode('UTF-8')) wstool-0.1.13/src/wstool/helpers.py000066400000000000000000000037641270747036000172750ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2010, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import os import sys import codecs import subprocess from wstool.config_elements import SetupConfigElement ROSINSTALL_FILENAME = ".rosinstall" def get_ros_package_path(config): """ Return the simplifed ROS_PACKAGE_PATH """ code_trees = [] for tree_el in reversed(config.get_config_elements()): if not os.path.isfile(tree_el.get_path()): code_trees.append(tree_el.get_path()) return code_trees wstool-0.1.13/src/wstool/multiproject_cli.py000066400000000000000000001621111270747036000211730ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2010, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function import os import sys import textwrap import shutil import datetime import yaml from optparse import OptionParser, IndentedHelpFormatter from wstool.cli_common import get_info_list, get_info_table, \ get_info_table_raw_csv, ONLY_OPTION_VALID_ATTRS from wstool.common import samefile, select_element, select_elements, \ MultiProjectException, normalize_uri, string_diff from wstool.config_yaml import PathSpec, get_path_spec_from_yaml import wstool.multiproject_cmd as multiproject_cmd from wstool.ui import Ui # implementation of single CLI commands (extracted for use in several # overlapping scripts) # usage help __MULTIPRO_CMD_DICT__ = { "help": "provide help for commands", "init": "set up a directory as workspace", "info": "Overview of some entries", "merge": "merges your workspace with another config set", "set": "add or changes one entry from your workspace config", "update": "update or check out some of your config elements", "remove": "remove an entry from your workspace config, without deleting files", "snapshot": "write a file specifying repositories to have the version they currently have", "diff": "print a diff over some SCM controlled entries", "foreach": "run shell command in given entries", "status": "print the change status of files in some SCM controlled entries", "scrape": "interactively add all found unmanaged VCS subfolders to workspace" } # usage help ordering and sections __MULTIPRO_CMD_HELP_LIST__ = ['help', 'init', None, 'set', 'merge', 'remove', 'scrape', None, 'update', None, 'info', 'status', 'diff', 'foreach'] # command aliases __MULTIPRO_CMD_ALIASES__ = {'update': 'up', 'remove': 'rm', 'status': 'st', 'diff': 'di'} def get_header(progname): config_header = ("# THIS IS AN AUTOGENERATED FILE, LAST GENERATED USING %s ON %s\n" % (progname, datetime.date.today().isoformat())) return config_header class IndentedHelpFormatterWithNL(IndentedHelpFormatter): def format_description(self, description): if not description: return "" desc_width = self.width - self.current_indent indent = " " * self.current_indent # the above is still the same bits = description.split('\n') formatted_bits = [ textwrap.fill(bit, desc_width, initial_indent=indent, subsequent_indent=indent) for bit in bits] result = "\n".join(formatted_bits) + "\n" return result def _get_mode_from_options(parser, options): mode = 'prompt' if options.delete_changed: mode = 'delete' if options.abort_changed: if mode == 'delete': parser.error("delete-changed-uris is mutually exclusive with abort-changed-uris") mode = 'abort' if options.backup_changed != '': if mode == 'delete': parser.error("delete-changed-uris is mutually exclusive with backup-changed-uris") if mode == 'abort': parser.error("abort-changed-uris is mutually exclusive with backup-changed-uris") mode = 'backup' return mode def _get_element_diff(new_path_spec, config_old, extra_verbose=False): """ :returns: a string telling what changed for element compared to old config """ if new_path_spec is None or config_old is None: return '' output = [' %s' % new_path_spec.get_local_name()] if extra_verbose: old_element = None if config_old is not None: old_element = select_element(config_old.get_config_elements(), new_path_spec.get_local_name()) if old_element is None: if new_path_spec.get_scmtype() is not None: output.append( " \t%s %s %s" % (new_path_spec.get_scmtype(), new_path_spec.get_uri(), new_path_spec.get_version() or '')) else: old_path_spec = old_element.get_path_spec() accessor_map = {PathSpec.get_scmtype: 'scmtype', PathSpec.get_version: 'version', PathSpec.get_revision: 'revision', PathSpec.get_current_revision: 'current revision', PathSpec.get_curr_uri: 'current_uri', PathSpec.get_uri: 'specified uri'} for accessor, label in list(accessor_map.items()): old_val = accessor(old_path_spec) new_val = accessor(new_path_spec) if old_val is not None and\ old_val != new_val: diff = string_diff(old_val, new_val) output.append(" \t%s: %s -> %s;" % (label, old_val, diff)) elif old_val is None and\ new_val is not None and\ new_val != "" and\ new_val != []: output.append(" %s = %s" % (label, new_val)) return ''.join(output) def prompt_merge(target_path, additional_uris, additional_specs, path_change_message=None, merge_strategy='KillAppend', confirmed=False, confirm=False, show_advanced=True, show_verbosity=True, config_filename=None, config=None, allow_other_element=True): """ Prompts the user for the resolution of a merge. Without further options, will prompt only if elements change. New elements are just added without prompt. :param target_path: Location of the config workspace :param additional_uris: uris from which to load more elements :param additional_specs: path specs for additional elements :param path_change_message: Something to tell the user about elements order :param merge_strategy: See Config.insert_element :param confirmed: Never ask :param confirm: Always ask, supercedes confirmed :param config: None or a Config object for target path if available :param show_advanced: if true allow to change merge strategy :param show_verbosity: if true allows to change verbosity :param allow_other_element: if False merge fails hwen it could cause other elements :returns: tupel (Config or None if no change, bool path_changed) """ if config is None: config = multiproject_cmd.get_config( target_path, additional_uris=[], config_filename=config_filename) elif config.get_base_path() != target_path: msg = "Config path does not match %s %s " % (config.get_base_path(), target_path) raise MultiProjectException(msg) local_names_old = [x.get_local_name() for x in config.get_config_elements()] extra_verbose = confirmed or confirm abort = False last_merge_strategy = None while not abort: if (last_merge_strategy is None or last_merge_strategy != merge_strategy): if not config_filename: # should never happen right now with rosinstall/rosws/wstool # TODO Need a better way to work with clones of original config raise ValueError('Cannot merge when no config filename is set') newconfig = multiproject_cmd.get_config( target_path, additional_uris=[], config_filename=config_filename) config_actions = multiproject_cmd.add_uris( config=newconfig, additional_uris=additional_uris, config_filename=None, merge_strategy=merge_strategy, allow_other_element=allow_other_element) for path_spec in additional_specs: action = newconfig.add_path_spec(path_spec, merge_strategy) config_actions[path_spec.get_local_name()] = (action, path_spec) last_merge_strategy = merge_strategy local_names_new = [x.get_local_name() for x in newconfig.get_config_elements()] path_changed = False ask_user = False output = "" new_elements = [] changed_elements = [] discard_elements = [] for localname, (action, new_path_spec) in list(config_actions.items()): index = -1 if localname in local_names_old: index = local_names_old.index(localname) if action == 'KillAppend': ask_user = True if (index > -1 and local_names_old[:index + 1] == local_names_new[:index + 1]): action = 'MergeReplace' else: changed_elements.append(_get_element_diff(new_path_spec, config, extra_verbose)) path_changed = True if action == 'Append': path_changed = True new_elements.append(_get_element_diff(new_path_spec, config, extra_verbose)) elif action == 'MergeReplace': changed_elements.append(_get_element_diff(new_path_spec, config, extra_verbose)) ask_user = True elif action == 'MergeKeep': discard_elements.append(_get_element_diff(new_path_spec, config, extra_verbose)) ask_user = True if len(changed_elements) > 0: output += "\n Change details of element (Use --merge-keep or --merge-replace to change):\n" if extra_verbose: output += " %s\n" % ("\n".join(sorted(changed_elements))) else: output += " %s\n" % (", ".join(sorted(changed_elements))) if len(new_elements) > 0: output += "\n Add new elements:\n" if extra_verbose: output += " %s\n" % ("\n".join(sorted(new_elements))) else: output += " %s\n" % (", ".join(sorted(new_elements))) if local_names_old != local_names_new[:len(local_names_old)]: old_order = ' '.join(reversed(local_names_old)) new_order = ' '.join(reversed(local_names_new)) output += "\n %s " % path_change_message or "Element order change" output += "(Use --merge-keep or --merge-replace to prevent) " output += "from\n %s\n to\n %s\n\n" % (old_order, new_order) ask_user = True if output == "": return (None, False) if not confirm and (confirmed or not ask_user): print(" Performing actions: ") print(output) return (newconfig, path_changed) else: print(output) showhelp = True while(showhelp): showhelp = False prompt = "Continue: (y)es, (n)o" if show_verbosity: prompt += ", (v)erbosity" if show_advanced: prompt += ", (a)dvanced options" prompt += ": " mode_input = Ui.get_ui().get_input(prompt) if mode_input == 'y': return (newconfig, path_changed) elif mode_input == 'n': abort = True elif show_advanced and mode_input == 'a': strategies = {'MergeKeep': "(k)eep", 'MergeReplace': "(s)witch in", 'KillAppend': "(a)ppending"} unselected = [v for k, v in list(strategies.items()) if k != merge_strategy] print("""New entries will just be appended to the config and appear at the beginning of your ROS_PACKAGE_PATH. The merge strategy decides how to deal with entries having a duplicate localname or path. "(k)eep" means the existing entry will stay as it is, the new one will be discarded. Useful for getting additional elements from other workspaces without affecting your setup. "(s)witch in" means that the new entry will replace the old in the same position. Useful for upgrading/downgrading. "switch (a)ppend" means that the existing entry will be removed, and the new entry appended to the end of the list. This maintains order of elements in the order they were given. Switch append is the default. """) prompt = "Change Strategy %s: " % (", ".join(unselected)) mode_input = Ui.get_ui().get_input(prompt) if mode_input == 's': merge_strategy = 'MergeReplace' elif mode_input == 'k': merge_strategy = 'MergeKeep' elif mode_input == 'a': merge_strategy = 'KillAppend' elif show_verbosity and mode_input == 'v': extra_verbose = not extra_verbose if abort: print("No changes made.") print('==========================================') return (None, False) def list_usage(progname, description, command_keys, command_helps, command_aliases): """ Constructs program usage for a list of commands with help and aliases. Contructs in the order given in command keys. Newlines can be used for command sections by adding None entries to command_keys list. Only one alias allowed per command. :param command_keys: list of keys or None to print help or empty lines :param command_helps: dict{key: help} :param command_aliases: dict{key: alias} :returns: usage string (multiline) """ dvars = {'prog': progname} dvars.update(vars()) result = [] result.append(description % dvars) for key in command_keys: if key in command_aliases: alias = ' (%s)' % command_aliases[key] else: alias = '' if key is not None: result.append(("%s%s" % (key, alias)).ljust(10) + ' \t' + command_helps[key]) else: result.append('') return '\n'.join(result) class MultiprojectCLI: def __init__(self, progname, config_filename=None, allow_other_element=False, config_generator=None): ''' creates the instance. Historically, rosinstall allowed "other" elements that went into the ROS_PACKAGE_PATH, but were ignored for vcs operations. A pure vcs tool has no use for such elements. :param progname: name to diplay in help :param config_filename: filename of files maintaining workspaces (.rosinstall) :param allow_other_element: bool, if True rosinstall semantics for "other" apply :param config_generator: function that writes config file ''' self.config_filename = config_filename self.config_generator = config_generator or multiproject_cmd.cmd_persist_config self.progname = progname self.allow_other_element = allow_other_element def cmd_init(self, argv): if self.config_filename is None: print('Error: Bug: config filename required for init') return 1 parser = OptionParser( usage="""usage: %s init [TARGET_PATH [SOURCE_PATH]]?""" % self.progname, formatter=IndentedHelpFormatterWithNL(), description=__MULTIPRO_CMD_DICT__["init"] + """ %(prog)s init does the following: 1. Reads folder/file/web-uri SOURCE_PATH looking for a rosinstall yaml 2. Creates new %(cfg_file)s file at TARGET-PATH SOURCE_PATH can e.g. be a web uri or a rosinstall file with vcs entries only If PATH is not given, uses current dir. Examples: $ %(prog)s init ~/fuerte /opt/ros/fuerte """ % {'cfg_file': self.config_filename, 'prog': self.progname}, epilog="See: http://www.ros.org/wiki/rosinstall for details\n") parser.add_option("--continue-on-error", dest="robust", default=False, help="Continue despite checkout errors", action="store_true") parser.add_option("-j", "--parallel", dest="jobs", default=1, help="How many parallel threads to use for installing", action="store") (options, args) = parser.parse_args(argv) if len(args) < 1: target_path = '.' else: target_path = args[0] if not os.path.isdir(target_path): if not os.path.exists(target_path): os.mkdir(target_path) else: print('Error: Cannot create in target path %s ' % target_path) if os.path.exists(os.path.join(target_path, self.config_filename)): print('Error: There already is a workspace config file %s at "%s". Use %s install/modify.' % (self.config_filename, target_path, self.progname)) return 1 if len(args) > 2: parser.error('Too many arguments') if len(args) == 2: print('Using initial elements from: %s' % args[1]) config_uris = [args[1]] else: config_uris = [] config = multiproject_cmd.get_config( basepath=target_path, additional_uris=config_uris, # catkin workspaces have no reasonable chaining semantics # config_filename=self.config_filename ) if config_uris and len(config.get_config_elements()) == 0: sys.stderr.write('WARNING: Not using any element from %s\n' % config_uris[0]) for element in config.get_config_elements(): if not element.is_vcs_element(): raise MultiProjectException("wstool does not allow elements without vcs information. %s" % element) # includes ROS specific files print("Writing %s" % os.path.join(config.get_base_path(), self.config_filename)) self.config_generator(config, self.config_filename, get_header(self.progname)) ## install or update each element install_success = multiproject_cmd.cmd_install_or_update( config, robust=False, num_threads=int(options.jobs)) if not install_success: print("Warning: installation encountered errors, but --continue-on-error was requested. Look above for warnings.") print("\nupdate complete.") return 0 def cmd_merge(self, target_path, argv, config=None): parser = OptionParser( usage="usage: %s merge [URI] [OPTIONS]" % self.progname, formatter=IndentedHelpFormatterWithNL(), description=__MULTIPRO_CMD_DICT__["merge"] + """. The command merges config with given other rosinstall element sets, from files or web uris. The default workspace will be inferred from context, you can specify one using -t. By default, when an element in an additional URI has the same local-name as an existing element, the existing element will be replaced. In order to ensure the ordering of elements is as provided in the URI, use the option --merge-kill-append. Examples: $ %(prog)s merge someother.rosinstall You can use '-' to pipe in input, as an example: $ roslocate info robot_model | %(prog)s merge - """ % {'prog': self.progname}, epilog="See: http://www.ros.org/wiki/rosinstall for details\n") # same options as for multiproject parser.add_option( "-a", "--merge-kill-append", dest="merge_kill_append", default=False, help="merge by deleting given entry and appending new one", action="store_true") parser.add_option("-k", "--merge-keep", dest="merge_keep", default=False, help="merge by keeping existing entry and discarding new one", action="store_true") parser.add_option("-r", "--merge-replace", dest="merge_replace", default=False, help="(default) merge by replacing given entry with new one maintaining ordering", action="store_true") parser.add_option("-y", "--confirm-all", dest="confirm_all", default='', help="do not ask for confirmation unless strictly necessary", action="store_true") # required here but used one layer above parser.add_option( "-t", "--target-workspace", dest="workspace", default=None, help="which workspace to use", action="store") (options, args) = parser.parse_args(argv) if len(args) > 1: print("Error: Too many arguments.") print(parser.usage) return -1 if len(args) == 0: print("Error: Too few arguments.") print(parser.usage) return -1 config_uris = args specs = [] if config_uris[0] == '-': pipedata = "".join(sys.stdin.readlines()) try: yamldicts = yaml.load(pipedata) except yaml.YAMLError as e: raise MultiProjectException( "Invalid yaml format: \n%s \n%s" % (pipedata, e)) if yamldicts is None: parser.error("No Input read from stdin") # cant have user interaction and piped input options.confirm_all = True specs.extend([get_path_spec_from_yaml(x) for x in yamldicts]) config_uris = [] merge_strategy = None count_mergeoptions = 0 if options.merge_kill_append: merge_strategy = 'KillAppend' count_mergeoptions += 1 if options.merge_keep: merge_strategy = 'MergeKeep' count_mergeoptions += 1 if options.merge_replace: merge_strategy = 'MergeReplace' count_mergeoptions += 1 if count_mergeoptions > 1: parser.error("You can only provide one merge-strategy") # default option if count_mergeoptions == 0: merge_strategy = 'MergeReplace' (newconfig, _) = prompt_merge( target_path, additional_uris=config_uris, additional_specs=specs, path_change_message="element order changed", merge_strategy=merge_strategy, confirmed=options.confirm_all, config_filename=self.config_filename, config=config, allow_other_element=self.allow_other_element) if newconfig is not None: print("Config changed, maybe you need run %s update to update SCM entries." % self.progname) print("Overwriting %s" % os.path.join(newconfig.get_base_path(), self.config_filename)) shutil.copy(os.path.join(newconfig.get_base_path(), self.config_filename), "%s.bak" % os.path.join(newconfig.get_base_path(), self.config_filename)) self.config_generator(newconfig, self.config_filename, get_header(self.progname)) print("\nupdate complete.") else: print("Merge caused no change, no new elements found") return 0 def cmd_diff(self, target_path, argv, config=None): parser = OptionParser(usage="usage: %s diff [localname]* " % self.progname, description=__MULTIPRO_CMD_DICT__["diff"], epilog="See: http://www.ros.org/wiki/rosinstall for details\n") # required here but used one layer above parser.add_option("-t", "--target-workspace", dest="workspace", default=None, help="which workspace to use", action="store") (_, args) = parser.parse_args(argv) if config is None: config = multiproject_cmd.get_config( target_path, additional_uris=[], config_filename=self.config_filename) elif config.get_base_path() != target_path: raise MultiProjectException( "Config path does not match %s %s " % (config.get_base_path(), target_path)) if len(args) > 0: difflist = multiproject_cmd.cmd_diff(config, localnames=args) else: difflist = multiproject_cmd.cmd_diff(config) alldiff = [] for entrydiff in difflist: if entrydiff['diff'] is not None and entrydiff['diff'] != '': alldiff.append(entrydiff['diff']) result = '\n'.join(alldiff) # result has no newline at end if result: print(result) return False def cmd_foreach(self, target_path, argv, config=None): """Run shell commands in each repository.""" parser = OptionParser( usage=('usage: %s foreach [[localname]* | [VCSFILTER]*]' ' [command] [OPTIONS]' % self.progname), formatter=IndentedHelpFormatterWithNL(), description=__MULTIPRO_CMD_DICT__['foreach'] + """. Example: $ %(progname)s foreach --git 'git status' """ % { 'progname': self.progname}, epilog='See: http://www.ros.org/wiki/rosinstall for details') parser.add_option('--shell', default=False, help='use the shell as the program to execute', action='store_true') parser.add_option('--no-stdout', dest='show_stdout', default=True, help='do not show stdout', action='store_false') parser.add_option('--no-stderr', dest='show_stderr', default=True, help='do not show stderr', action='store_false') parser.add_option("--git", dest="git", default=False, help="run command in git entries", action="store_true") parser.add_option("--svn", dest="svn", default=False, help="run command in svn entries", action="store_true") parser.add_option("--hg", dest="hg", default=False, help="run command in hg entries", action="store_true") parser.add_option("--bzr", dest="bzr", default=False, help="run command in bzr entries", action="store_true") parser.add_option("-m", "--timeout", dest="timeout", default=None, help="How long to wait for each repo before failing [seconds]", action="store", type=float) parser.add_option("-j", "--parallel", dest="jobs", default=1, help="How many parallel threads to use for running the custom commands", action="store") parser.add_option("-v", "--verbose", dest="verbose", default=False, help="Whether to print out more information", action="store_true") # -t option required here for help but used one layer above # see cli_common parser.add_option("-t", "--target-workspace", dest="workspace", default=None, help="which workspace to use", action="store") (options, args) = parser.parse_args(argv) if args: localnames, command = args[:-1], args[-1] localnames = localnames if localnames else None else: print("Error: Too few arguments.") print(parser.usage) return -1 scm_types = [] if options.git: scm_types.append('git') if options.svn: scm_types.append('svn') if options.hg: scm_types.append('hg') if options.bzr: scm_types.append('bzr') if not scm_types: scm_types = None if localnames and scm_types: sys.stderr.write("Error: Either localnames or scm-filters" " [--(git|svn|hg|bzr)] should be specified.\n") return -1 if config is None: config = multiproject_cmd.get_config( target_path, additional_uris=[], config_filename=self.config_filename) elif config.get_base_path() != target_path: raise MultiProjectException('Config path does not match %s %s' % (config.get_base_path(), target_path)) # run shell command outputs = multiproject_cmd.cmd_foreach(config, command=command, localnames=localnames, num_threads=int(options.jobs), timeout=options.timeout, scm_types=scm_types, shell=options.shell, verbose=options.verbose) def add_localname_prefix(localname, lines): return ['[%s] %s' % (localname, line) for line in lines] for output in outputs: localname = output['entry'].get_local_name() rc = output['returncode'] if options.show_stdout: if output['stdout'] is None: continue lines = output['stdout'].strip().split('\n') lines = add_localname_prefix(localname, lines) sys.stdout.write('\n'.join(lines)) sys.stdout.write('\n') if options.show_stderr: lines = [] if output['stderr'] is not None: lines += output['stderr'].strip().split('\n') if rc != 0: lines += ['Command failed with return code [%s]' % rc] if not lines: continue lines = add_localname_prefix(localname, lines) sys.stderr.write('\n'.join(lines)) sys.stderr.write('\n') return 0 if all([o['returncode'] == 0 for o in outputs]) else 1 def cmd_status(self, target_path, argv, config=None): parser = OptionParser(usage="usage: %s status [localname]* " % self.progname, description=__MULTIPRO_CMD_DICT__["status"] + ". The status columns meanings are as the respective SCM defines them.", epilog="""See: http://www.ros.org/wiki/rosinstall for details""") parser.add_option("--untracked", dest="untracked", default=False, help="Also shows untracked files", action="store_true") # -t option required here for help but used one layer above, see cli_common parser.add_option("-t", "--target-workspace", dest="workspace", default=None, help="which workspace to use", action="store") (options, args) = parser.parse_args(argv) if config is None: config = multiproject_cmd.get_config( target_path, additional_uris=[], config_filename=self.config_filename) elif config.get_base_path() != target_path: raise MultiProjectException( "Config path does not match %s %s " % (config.get_base_path(), target_path)) if len(args) > 0: statuslist = multiproject_cmd.cmd_status(config, localnames=args, untracked=options.untracked) else: statuslist = multiproject_cmd.cmd_status(config, untracked=options.untracked) allstatus = [] for entrystatus in statuslist: if entrystatus['status'] is not None: allstatus.append(entrystatus['status']) print(''.join(allstatus), end='') return 0 def cmd_set(self, target_path, argv, config=None): """ command for modifying/adding a single entry :param target_path: where to look for config :param config: config to use instead of parsing file anew """ usage = ("usage: %s set [localname] [[SCM-URI] --(%ssvn|hg|git|bzr) [--version=VERSION]?]?" % (self.progname, 'detached|' if self.allow_other_element else '')) parser = OptionParser( usage=usage, formatter=IndentedHelpFormatterWithNL(), description=__MULTIPRO_CMD_DICT__["set"] + """ The command will infer whether you want to add or modify an entry. If you modify, it will only change the details you provide, keeping those you did not provide. if you only provide a uri, will use the basename of it as localname unless such an element already exists. The command only changes the configuration, to checkout or update the element, run %(progname)s update afterwards. Examples: $ %(progname)s set robot_model --hg https://kforge.ros.org/robotmodel/robot_model $ %(progname)s set robot_model --version-new robot_model-1.7.1 %(detached)s """ % { 'progname': self.progname, 'detached': '$ %s set robot_model --detached' % (self.progname if self.allow_other_element else '')}, epilog="See: http://www.ros.org/wiki/rosinstall for details\n") if self.allow_other_element: parser.add_option("--detached", dest="detach", default=False, help="make an entry unmanaged (default for new element)", action="store_true") parser.add_option("-v", "--version-new", dest="version", default=None, help="point SCM to this version", action="store") parser.add_option("--git", dest="git", default=False, help="make an entry a git entry", action="store_true") parser.add_option("--svn", dest="svn", default=False, help="make an entry a subversion entry", action="store_true") parser.add_option("--hg", dest="hg", default=False, help="make an entry a mercurial entry", action="store_true") parser.add_option("--bzr", dest="bzr", default=False, help="make an entry a bazaar entry", action="store_true") parser.add_option("-y", "--confirm", dest="confirm", default='', help="Do not ask for confirmation", action="store_true") parser.add_option("-u", "--update", dest="do_update", default=False, help="update repository after set", action="store_true") # -t option required here for help but used one layer above, see cli_common parser.add_option( "-t", "--target-workspace", dest="workspace", default=None, help="which workspace to use", action="store") (options, args) = parser.parse_args(argv) if not self.allow_other_element: options.detach = False if len(args) > 2: print("Error: Too many arguments.") print(parser.usage) return -1 if config is None: config = multiproject_cmd.get_config( target_path, additional_uris=[], config_filename=self.config_filename) elif config.get_base_path() != target_path: raise MultiProjectException( "Config path does not match %s %s " % (config.get_base_path(), target_path)) scmtype = None count_scms = 0 if options.git: scmtype = 'git' count_scms += 1 if options.svn: scmtype = 'svn' count_scms += 1 if options.hg: scmtype = 'hg' count_scms += 1 if options.bzr: scmtype = 'bzr' count_scms += 1 if options.detach: count_scms += 1 if count_scms > 1: parser.error( "You cannot provide more than one scm provider option") if len(args) == 0: parser.error("Must provide a localname") element = select_element(config.get_config_elements(), args[0]) uri = None if len(args) == 2: uri = args[1] version = None if options.version is not None: version = options.version.strip("'\"") # create spec object if element is None: if scmtype is None and not self.allow_other_element: # for modification, not re-stating the scm type is # okay, for new elements not parser.error("You have to provide one scm provider option") # asssume is insert, choose localname localname = os.path.normpath(args[0]) rel_path = os.path.relpath(os.path.realpath(localname), os.path.realpath(config.get_base_path())) if os.path.isabs(localname): # use shorter localname for folders inside workspace if not rel_path.startswith('..'): localname = rel_path else: # got a relative path as localname, could point to a dir or be # meant relative to workspace if not samefile(os.getcwd(), config.get_base_path()): if os.path.isdir(localname): parser.error( "Cannot decide which one you want to add:\n%s\n%s" % ( os.path.abspath(localname), os.path.join(config.get_base_path(), localname))) if not rel_path.startswith('..'): localname = rel_path spec = PathSpec(local_name=localname, uri=normalize_uri(uri, config.get_base_path()), version=version, scmtype=scmtype) else: # modify localname = element.get_local_name() old_spec = element.get_path_spec() if options.detach: spec = PathSpec(local_name=localname) else: # '' evals to False, we do not want that if version is None: version = old_spec.get_version() spec = PathSpec(local_name=localname, uri=normalize_uri(uri or old_spec.get_uri(), config.get_base_path()), version=version, scmtype=scmtype or old_spec.get_scmtype(), path=old_spec.get_path()) if spec.get_legacy_yaml() == old_spec.get_legacy_yaml(): if not options.detach and spec.get_scmtype() is not None: parser.error( "Element %s already exists, did you mean --detached ?" % spec) parser.error("Element %s already exists" % spec) (newconfig, path_changed) = prompt_merge( target_path, additional_uris=[], additional_specs=[spec], merge_strategy='MergeReplace', confirmed=options.confirm, confirm=not options.confirm, show_verbosity=False, show_advanced=False, config_filename=self.config_filename, config=config, allow_other_element=self.allow_other_element) if newconfig is not None: print("Overwriting %s" % os.path.join( newconfig.get_base_path(), self.config_filename)) shutil.copy( os.path.join(newconfig.get_base_path(), self.config_filename), "%s.bak" % os.path.join(newconfig.get_base_path(), self.config_filename)) self.config_generator(newconfig, self.config_filename) if options.do_update: install_success = multiproject_cmd.cmd_install_or_update( newconfig, localnames=[localname]) if not install_success: print("Warning: installation encountered errors.") print("\nupdate complete.") elif (spec.get_scmtype() is not None): print("Config changed, remember to run '%s update %s' to update the folder from %s" % (self.progname, spec.get_local_name(), spec.get_scmtype())) else: print("New element %s could not be added, " % spec) return 1 # auto-install not a good feature, maybe make an option # for element in config.get_config_elements(): # if element.get_local_name() == spec.get_local_name(): # if element.is_vcs_element(): # element.install(checkout=not os.path.exists(os.path.join(config.get_base_path(), spec.get_local_name()))) # break return 0 def cmd_update(self, target_path, argv, config=None): parser = OptionParser(usage="usage: %s update [localname]*" % self.progname, formatter=IndentedHelpFormatterWithNL(), description=__MULTIPRO_CMD_DICT__["update"] + """ This command calls the SCM provider to pull changes from remote to your local filesystem. In case the url has changed, the command will ask whether to delete or backup the folder. Examples: $ %(progname)s update -t ~/fuerte $ %(progname)s update robot_model geometry """ % {'progname': self.progname}, epilog="See: http://www.ros.org/wiki/rosinstall for details\n") parser.add_option("--delete-changed-uris", dest="delete_changed", default=False, help="Delete the local copy of a directory before changing uri.", action="store_true") parser.add_option("--abort-changed-uris", dest="abort_changed", default=False, help="Abort if changed uri detected", action="store_true") parser.add_option("--continue-on-error", dest="robust", default=False, help="Continue despite checkout errors", action="store_true") parser.add_option("--backup-changed-uris", dest="backup_changed", default='', help="backup the local copy of a directory before changing uri to this directory.", action="store") parser.add_option("-m", "--timeout", dest="timeout", default=None, help="How long to wait for each repo before failing [seconds]", action="store", type=float) parser.add_option("-j", "--parallel", dest="jobs", default=1, help="How many parallel threads to use for installing", action="store") parser.add_option("-v", "--verbose", dest="verbose", default=False, help="Whether to print out more information", action="store_true") # -t option required here for help but used one layer above, see cli_common parser.add_option("-t", "--target-workspace", dest="workspace", default=None, help="which workspace to use", action="store") (options, args) = parser.parse_args(argv) if config is None: config = multiproject_cmd.get_config( target_path, additional_uris=[], config_filename=self.config_filename) elif config.get_base_path() != target_path: raise MultiProjectException("Config path does not match %s %s " % ( config.get_base_path(), target_path)) success = True mode = _get_mode_from_options(parser, options) if args == []: # None means no filter, [] means filter all args = None if success: install_success = multiproject_cmd.cmd_install_or_update( config, localnames=args, backup_path=options.backup_changed, mode=mode, robust=options.robust, num_threads=int(options.jobs), timeout=options.timeout, verbose=options.verbose) if install_success or options.robust: return 0 return 1 def cmd_remove(self, target_path, argv, config=None): parser = OptionParser(usage="usage: %s remove [localname]*" % self.progname, formatter=IndentedHelpFormatterWithNL(), description=__MULTIPRO_CMD_DICT__["remove"] + """ The command removes entries from your configuration file, it does not affect your filesystem. """, epilog="See: http://www.ros.org/wiki/rosinstall for details\n") (_, args) = parser.parse_args(argv) if len(args) < 1: print("Error: Too few arguments.") print(parser.usage) return -1 if config is None: config = multiproject_cmd.get_config( target_path, additional_uris=[], config_filename=self.config_filename) elif config.get_base_path() != target_path: raise MultiProjectException( "Config path does not match %s %s " % (config.get_base_path(), target_path)) success = True elements = select_elements(config, args) for element in elements: if not config.remove_element(element.get_local_name()): success = False print("Bug: No such element %s in config, aborting without changes" % (element.get_local_name())) break if success: print("Overwriting %s" % os.path.join(config.get_base_path(), self.config_filename)) shutil.copy(os.path.join(config.get_base_path(), self.config_filename), "%s.bak" % os.path.join(config.get_base_path(), self.config_filename)) self.config_generator(config, self.config_filename) print("Removed entries %s" % args) return 0 def cmd_info(self, target_path, argv, reverse=True, config=None): parser = OptionParser( usage="usage: %s info [localname]* [OPTIONS]" % self.progname, formatter=IndentedHelpFormatterWithNL(), description=__MULTIPRO_CMD_DICT__["info"] + """ The Status (S) column shows x for missing L for uncommited (local) changes V for difference in version and/or remote URI C for difference in local and remote versions The 'Version-Spec' column shows what tag, branch or revision was given in the .rosinstall file. The 'UID' column shows the unique ID of the current (and specified) version. The 'URI' column shows the configured URL of the repo. If status is V, the difference between what was specified and what is real is shown in the respective column. For SVN entries, the url is split up according to standard layout (trunk/tags/branches). When given one localname, just show the data of one element in list form. This also has the generic properties element which is usually empty. The --only option accepts keywords: %(opts)s Examples: $ %(prog)s info -t ~/ros/fuerte $ %(prog)s info robot_model $ %(prog)s info --yaml $ %(prog)s info --only=path,cur_uri,cur_revision robot_model geometry """ % {'prog': self.progname, 'opts': ONLY_OPTION_VALID_ATTRS}, epilog="See: http://www.ros.org/wiki/rosinstall for details\n") parser.add_option( "--root", dest="show_ws_root", default=False, help="Show workspace root path", action="store_true") parser.add_option( "--data-only", dest="data_only", default=False, help="Does not provide explanations", action="store_true") parser.add_option( "-s", "--short", dest="short", default=False, help="Shows simplified version info table.", action="store_true") parser.add_option( "--only", dest="only", default=False, help="Shows comma-separated lists of only given comma-separated attribute(s).", action="store") parser.add_option( "--yaml", dest="yaml", default=False, help="Shows only version of single entry. Intended for scripting.", action="store_true") parser.add_option( "--fetch", dest="fetch", default=False, help="When used, retrieves version information from remote (takes longer).", action="store_true") parser.add_option( "-u", "--untracked", dest="untracked", default=False, help="Also show untracked files as modifications", action="store_true") # -t option required here for help but used one layer above, see cli_common parser.add_option( "-t", "--target-workspace", dest="workspace", default=None, help="which workspace to use", action="store") parser.add_option( "-m", "--managed-only", dest="unmanaged", default=True, help="only show managed elements", action="store_false") (options, args) = parser.parse_args(argv) if config is None: config = multiproject_cmd.get_config( target_path, additional_uris=[], config_filename=self.config_filename) elif config.get_base_path() != target_path: raise MultiProjectException("Config path does not match %s %s " % (config.get_base_path(), target_path)) if options.show_ws_root: print(config.get_base_path()) return 0 if args == []: args = None if options.only: only_options = options.only.split(",") if only_options == '': parser.error('No valid options given') lines = get_info_table_raw_csv(config, properties=only_options, localnames=args) print('\n'.join(lines)) return 0 elif options.yaml: source_aggregate = multiproject_cmd.cmd_snapshot(config, localnames=args) print(yaml.safe_dump(source_aggregate), end='') return 0 # this call takes long, as it invokes scms. outputs = multiproject_cmd.cmd_info(config, localnames=args, untracked=options.untracked, fetch=options.fetch) if args and len(args) == 1: # if only one element selected, print just one line print(get_info_list(config.get_base_path(), outputs[0], options.data_only)) return 0 columns = None if options.short: columns = ['localname', 'status', 'version'] header = 'workspace: %s' % (target_path) print(header) table = get_info_table(config.get_base_path(), outputs, options.data_only, reverse=reverse, selected_headers=columns) if table is not None and table != '': print("\n%s" % table) if options.unmanaged: outputs2 = multiproject_cmd.cmd_find_unmanaged_repos(config) table2 = get_info_table(config.get_base_path(), outputs2, options.data_only, reverse=reverse, unmanaged=True, selected_headers=columns) if table2 is not None and table2 != '': print("\nAlso detected these repositories in the workspace, add using '%s scrape' or '%s set':\n\n%s" % (self.progname, self.progname, table2)) return 0 def cmd_scrape(self, target_path, argv, config=None): """ command for adding yet unamanaged repos under workspace root to managed repos. :param target_path: where to look for config :param config: config to use instead of parsing file anew """ usage = ("usage: %s scrape [OPTIONS]" % self.progname) parser = OptionParser( usage=usage, description=__MULTIPRO_CMD_DICT__["scrape"], epilog="See: http://www.ros.org/wiki/rosinstall for details\n") parser.add_option("-y", "--confirm", dest="confirm", default='', help="Do not ask for confirmation", action="store_true") # -t option required here for help but used one layer above, see cli_common parser.add_option( "-t", "--target-workspace", dest="workspace", default=None, help="which workspace to use", action="store") (options, args) = parser.parse_args(argv) if config is None: config = multiproject_cmd.get_config( target_path, additional_uris=[], config_filename=self.config_filename) elif config.get_base_path() != target_path: raise MultiProjectException( "Config path does not match %s %s " % (config.get_base_path(), target_path)) elems = multiproject_cmd.cmd_find_unmanaged_repos(config) if not elems: raise MultiProjectException( "No unmanaged repos found below '%s'" % (config.get_base_path())) for elem in elems: elem_abs_path = os.path.join(config.get_base_path(), elem['localname']) if os.path.isdir(elem_abs_path): args = [elem_abs_path, elem['scm'], elem['uri']] if (options.confirm): args.append('-y') self.cmd_set(target_path, args) return 0 wstool-0.1.13/src/wstool/multiproject_cmd.py000066400000000000000000000562231270747036000211750ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2010, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ The _cmd python files attempt to provide a reasonably complete level of abstraction to multiproject functionality. Client code will need to pass the Config element through, and may use the ConfigElement API in places. There are no guarantees at this time for the API to remain stable, but the cmd API probably will change least. A change to expect is abstraction of user interaction. """ import sys import os import shlex from wstool.common import MultiProjectException, DistributedWork, \ select_elements, normabspath from wstool.config import Config, realpath_relation from wstool.config_elements import AVCSConfigElement from wstool.config_yaml import aggregate_from_uris, generate_config_yaml, \ get_path_specs_from_uri, PathSpec import vcstools import vcstools.__version__ from vcstools.common import run_shell_command from vcstools.vcs_abstraction import get_vcs_client from vcstools.git import GitClient from vcstools.hg import HgClient from vcstools.bzr import BzrClient from vcstools.svn import SvnClient def get_config(basepath, additional_uris=None, config_filename=None, merge_strategy='KillAppend'): """ Create a Config element necessary for all other commands. The command will look at the uris in sequence, each can be a web resource, a filename or a folder. In case it is a folder, when a config_filename is provided, the folder will be searched for a file of that name, and that one will be used. Else the folder will be considered a target location for the config. All files will be parsed for config elements, thus conceptually the input to Config is an expanded list of config elements. Config takes this list and consolidates duplicate paths by keeping the last one in the list. :param basepath: where relative paths shall be resolved against :param additional_uris: the location of config specifications or folders :param config_filename: name of files which may be looked at for config information :param merge_strategy: One of 'KillAppend, 'MergeKeep', 'MergeReplace' :returns: a Config object :raises MultiProjectException: on plenty of errors """ if basepath is None: raise MultiProjectException("Need to provide a basepath for Config.") # print("source...........................", path_specs) ## Generate the config class with the uri and path if (config_filename is not None and basepath is not None and os.path.isfile(os.path.join(basepath, config_filename))): base_path_specs = get_path_specs_from_uri(os.path.join(basepath, config_filename), as_is=True) else: base_path_specs = [] config = Config(base_path_specs, basepath, config_filename=config_filename, merge_strategy=merge_strategy) add_uris(config=config, additional_uris=additional_uris, config_filename=config.get_config_filename(), merge_strategy=merge_strategy) return config def add_uris(config, additional_uris, # config_filename is not redundant with config.get_config_filename() # because in some cases a different config_filename is required config_filename=None, merge_strategy="KillAppend", allow_other_element=True): """ changes the given config by merging with the additional_uris :param config: a Config objects :param additional_uris: the location of config specifications or folders :param config_filename: name of files which may be looked at for config information :param merge_strategy: One of 'KillAppend, 'MergeKeep', 'MergeReplace' :param allow_other_element: if False, discards elements to be added with no SCM information :returns: a dict {: (, ), : ...} determined by the merge_strategy :raises MultiProjectException: on plenty of errors """ if config is None: raise MultiProjectException("Need to provide a Config.") if not additional_uris: return {} if config_filename is None: added_uris = additional_uris else: added_uris = [] # reject if the additional uri points to the same file as our # config is based on for uri in additional_uris: # check whether we try to merge with other workspace comp_uri = None if (os.path.isfile(uri) and os.path.basename(uri) == config_filename): # add from other workspace by file comp_uri = os.path.dirname(uri) if (os.path.isdir(uri) and os.path.isfile(os.path.join(uri, config_filename))): # add from other workspace by dir comp_uri = uri if (comp_uri is not None and realpath_relation(os.path.abspath(comp_uri), os.path.abspath(config.get_base_path())) == 'SAME_AS'): print('Warning: Discarding config basepath as additional uri: %s' % uri) continue added_uris.append(uri) actions = {} if len(added_uris) > 0: path_specs = aggregate_from_uris(added_uris, config_filename, allow_other_element) for path_spec in path_specs: action = config.add_path_spec(path_spec, merge_strategy) actions[path_spec.get_local_name()] = (action, path_spec) return actions def cmd_persist_config(config, filename, header=None, pretty=False, sort_with_localname=False): """writes config to given file in yaml syntax""" generate_config_yaml(config, filename, header, pretty, sort_with_localname) def cmd_version(): """Returns extensive version information""" def prettyversion(vdict): version = vdict.pop("version") return "%s; %s" % (version, ",".join(list(vdict.values()))) return """vcstools: %s SVN: %s Mercurial: %s Git: %s Tar: %s Bzr: %s """ % (vcstools.__version__.version, prettyversion(vcstools.SvnClient.get_environment_metadata()), prettyversion(vcstools.HgClient.get_environment_metadata()), prettyversion(vcstools.GitClient.get_environment_metadata()), prettyversion(vcstools.TarClient.get_environment_metadata()), prettyversion(vcstools.BzrClient.get_environment_metadata())) def cmd_status(config, localnames=None, untracked=False): """ calls SCM status for all SCM entries in config, relative to path :returns: List of dict {element: ConfigElement, diff: diffstring} :param untracked: also show files not added to the SCM :raises MultiProjectException: on plenty of errors """ class StatusRetriever(): def __init__(self, element, path, untracked): self.element = element self.path = path self.untracked = untracked def do_work(self): path_spec = self.element.get_path_spec() scmtype = path_spec.get_scmtype() status = self.element.get_status(self.path, self.untracked) # align other scm output to svn columns = -1 if scmtype == "git": columns = 3 elif scmtype == "hg": columns = 2 elif scmtype == "bzr": columns = 4 if columns > -1 and status is not None: status_aligned = '' for line in status.splitlines(): status_aligned = "%s%s%s\n" % (status_aligned, line[:columns].ljust(8), line[columns:]) status = status_aligned return {'status': status} path = config.get_base_path() # call SCM info in separate threads elements = config.get_config_elements() work = DistributedWork(capacity=len(elements), num_threads=-1) elements = select_elements(config, localnames) for element in elements: if element.is_vcs_element(): work.add_thread(StatusRetriever(element, path, untracked)) outputs = work.run() return outputs def cmd_diff(config, localnames=None): """ calls SCM diff for all SCM entries in config, relative to path :returns: List of dict {element: ConfigElement, diff: diffstring} :raises MultiProjectException: on plenty of errors """ class DiffRetriever(): def __init__(self, element, path): self.element = element self.path = path def do_work(self): return {'diff': self.element.get_diff(self.path)} path = config.get_base_path() elements = config.get_config_elements() work = DistributedWork(capacity=len(elements), num_threads=-1) elements = select_elements(config, localnames) for element in elements: if element.is_vcs_element(): work.add_thread(DiffRetriever(element, path)) outputs = work.run() return outputs def cmd_foreach( config, command, localnames=None, num_threads=1, timeout=None, scm_types=None, shell=False, verbose=False): """Run command in all SCM entries in config, relative to path""" class ForeachRetriever(object): def __init__(self, element, command, timeout, shell, verbose): self.element = element self.command = command self.timeout = timeout self.shell = shell self.verbose = verbose def do_work(self): command = self.command if not self.shell: command = shlex.split(command) returncode, stdout, stderr = run_shell_command( command, cwd=self.element.path, timeout=self.timeout, shell=self.shell, show_stdout=self.verbose) return {'returncode': returncode, 'stdout': stdout, 'stderr': stderr} elements = select_elements(config, localnames) work = DistributedWork(capacity=len(elements), num_threads=num_threads) for element in elements: if ((scm_types is not None) and (element.get_vcs_type_name() not in scm_types)): continue work.add_thread(ForeachRetriever(element, command, timeout, shell, verbose)) outputs = work.run() return outputs def cmd_install_or_update( config, backup_path=None, mode='abort', robust=False, localnames=None, num_threads=1, timeout=None, verbose=False): """ performs many things, generally attempting to make the local filesystem look like what the config specifies, pulling from remote sources the most recent changes. The command may have stdin user interaction (TODO abstract) :param backup_path: if and where to backup trees before deleting them :param robust: proceed to next element even when one element fails :returns: True on Success :raises MultiProjectException: on plenty of errors """ success = True if not os.path.exists(config.get_base_path()): os.mkdir(config.get_base_path()) # Prepare install operation check filesystem and ask user preparation_reports = [] elements = select_elements(config, localnames) for tree_el in elements: abs_backup_path = None if backup_path is not None: abs_backup_path = os.path.join(config.get_base_path(), backup_path) try: preparation_report = tree_el.prepare_install( backup_path=abs_backup_path, arg_mode=mode, robust=robust) if preparation_report is not None: if preparation_report.abort: raise MultiProjectException( "Aborting install because of %s" % preparation_report.error) if not preparation_report.skip: preparation_reports.append(preparation_report) else: if preparation_report.error is not None: print("Skipping install of %s because: %s" % (preparation_report.config_element.get_local_name(), preparation_report.error)) except MultiProjectException as exc: fail_str = ("Failed to install tree '%s'\n %s" % (tree_el.get_path(), exc)) if robust: success = False print("Continuing despite %s" % fail_str) else: raise MultiProjectException(fail_str) class Installer(): def __init__(self, report): self.element = report.config_element self.report = report def do_work(self): self.element.install(checkout=self.report.checkout, backup=self.report.backup, backup_path=self.report.backup_path, inplace=self.report.inplace, timeout=self.report.timeout, verbose=self.report.verbose) return {} work = DistributedWork(capacity=len(preparation_reports), num_threads=num_threads, silent=False) for report in preparation_reports: report.verbose = verbose report.timeout = timeout thread = Installer(report) work.add_thread(thread) try: work.run() except MultiProjectException as exc: print ("Exception caught during install: %s" % exc) success = False if not robust: raise return success # TODO go back and make sure that everything in options.path is # described in the yaml, and offer to delete otherwise? not sure, # but it could go here def cmd_snapshot(config, localnames=None): elements = select_elements(config, localnames) source_aggregate = [] for element in elements: if element.is_vcs_element(): spec = element.get_versioned_path_spec() export_spec = PathSpec( local_name=spec.get_local_name(), scmtype=spec.get_scmtype(), uri=spec.get_uri() or spec.get_curr_uri(), version=(spec.get_current_revision() or spec.get_revision() or spec.get_version()), path=spec.get_path()) if not export_spec.get_version(): sys.stderr.write( 'Warning, discarding non-vcs element %s\n' % element.get_local_name()) source = export_spec.get_legacy_yaml() source_aggregate.append(source) else: sys.stderr.write('Warning, discarding non-vcs element %s\n' % element.get_local_name()) return source_aggregate def cmd_info(config, localnames=None, untracked=False, fetch=False): """This function compares what should be (config_file) with what is (directories) and returns a list of dictionary giving each local path and all the state information about it available. """ class InfoRetriever(): """ Auxilliary class to perform IO-bound operations in individual threads """ def __init__(self, element, path, untracked, fetch): self.element = element self.path = path self.fetch = fetch self.untracked = untracked def do_work(self): localname = "" scm = None uri = "" curr_uri = None exists = False version = "" # what is given in config file curr_version_label = "" # e.g. branchname remote_revision = "" # UID on remote default_remote_label = None # git default branch display_version = '' modified = "" currevision = "" # revision number of version specversion = "" # actual revision number localname = self.element.get_local_name() path = self.element.get_path() or localname if localname is None or localname == "": raise MultiProjectException( "Missing local-name in element: %s" % self.element) if (os.path.exists(normabspath(path, self.path))): exists = True if self.element.is_vcs_element(): if not exists: path_spec = self.element.get_path_spec() version = path_spec.get_version() else: path_spec = self.element.get_versioned_path_spec(fetch=fetch) version = path_spec.get_version() remote_revision = path_spec.get_remote_revision() curr_version_label = path_spec.get_curr_version() if (curr_version_label is not None and version != curr_version_label): display_version = curr_version_label else: display_version = version curr_uri = path_spec.get_curr_uri() status = self.element.get_status(self.path, self.untracked) if (status is not None and status.strip() != ''): modified = True specversion = path_spec.get_revision() if (version is not None and version.strip() != '' and (specversion is None or specversion.strip() == '')): specversion = '"%s"' % version if (self.fetch and specversion == None and path_spec.get_scmtype() == 'git'): default_remote_label = self.element.get_default_remote_label() currevision = path_spec.get_current_revision() scm = path_spec.get_scmtype() uri = path_spec.get_uri() return {'scm': scm, 'exists': exists, 'localname': localname, 'path': path, 'uri': uri, 'curr_uri': curr_uri, 'version': version, 'remote_revision': remote_revision, 'default_remote_label': default_remote_label, 'curr_version_label': curr_version_label, 'specversion': specversion, 'actualversion': currevision, 'modified': modified, 'properties': self.element.get_properties()} path = config.get_base_path() # call SCM info in separate threads elements = config.get_config_elements() elements = select_elements(config, localnames) work = DistributedWork(capacity=len(elements), num_threads=-1) for element in elements: if element.get_properties() is None or not 'setup-file' in element.get_properties(): work.add_thread(InfoRetriever(element, path, untracked, fetch)) outputs = work.run() return outputs def cmd_find_unmanaged_repos(config): """ Auxilliary function to find SCM folders within workspace that have not been tracked. This allows quicker diagnosis of the general state in a workspace, where folders can be part of the build even when they are not mentioned in the .rosinstall file. Nested SCMs are not investigated. """ class UnmanagedInfoRetriever(): def __init__(self, path, localname, scm_type): self.path = path self.localname = localname self.scm_type = scm_type self.element = AVCSConfigElement(scm_type, os.path.join(self.path, self.localname), localname, '') def do_work(self): vcsc = get_vcs_client(self.scm_type, os.path.join(self.path, self.localname)) return {'scm': '--' + self.scm_type, # prefix '--' to allow copy&paste to set command 'localname': self.localname, 'path': self.path, 'uri': vcsc.get_url(), 'properties': self.element.get_properties()} path = config.get_base_path() # call SCM info in separate threads elements = config.get_config_elements() managed_paths = [os.path.join(path, e.get_local_name()) for e in elements] unmanaged_paths = [] scm_clients = {SvnClient: 'svn', GitClient: 'git', BzrClient:'bzr', HgClient:'hg'} for root, dirs, files in os.walk(path): if root in managed_paths: # remove it from the walk if it's managed del dirs[:] else: for client, key in scm_clients.items(): # check if it's a vcs dir if client.static_detect_presence(root): # add it to the unmanaged list unmanaged_paths.append((os.path.relpath(root, path), key)) # don't walk any other directories in this root del dirs[:] work = DistributedWork(capacity=len(unmanaged_paths), num_threads=-1) for localname, scm_type in sorted(unmanaged_paths, key=lambda up: up[0], reverse=True): work.add_thread(UnmanagedInfoRetriever(path, localname, scm_type)) outputs = work.run() return outputs wstool-0.1.13/src/wstool/ui.py000066400000000000000000000075121270747036000162430ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import sys class Ui(object): """ wrap user interaction, such that client libraries may provide own implementation """ # For now, primarily define this for replacement in unittests GLOBAL_UI = None @staticmethod def get_ui(): if Ui.GLOBAL_UI is None: return Ui() return Ui.GLOBAL_UI @staticmethod def set_ui(uiarg): Ui.GLOBAL_UI = uiarg def __init__(self): pass def get_backup_path(self): """Interactive function asking the user to choose a path for backup""" backup_path = self.get_input("Please enter backup pathname: ") print(("backing up to %s" % backup_path)) return backup_path def get_input(self, prompt): if sys.hexversion > 0x03000000: return input(prompt) else: return raw_input(prompt) def prompt_del_abort_retry(self, prompt, allow_skip=False, allow_inplace=False): """ Interactive function asking the user to choose a conflict resolution :param prompt: message to display, str :param allow_skip: whether to display skip option, bool :param inplace: whether to show option for inplace replacing (symlinks) :return: user choice one of backup, delete, abort, inplace, skip """ valid_modes = ['(d)elete and replace', '(a)bort'] if allow_inplace: valid_modes.append('(i)nplace delete and replace at symlink') else: valid_modes.append('(b)ackup and replace') if allow_skip: valid_modes.append('(s)kip') mode = "" full_prompt = "%s\n %s: " % (prompt, ", ".join(valid_modes)) while mode == "": mode_input = self.get_input(full_prompt) if not allow_inplace and mode_input == 'b': mode = 'backup' elif mode_input == 'd': mode = 'delete' elif mode_input == 'a': mode = 'abort' elif allow_inplace and mode_input == 'i': mode = 'inplace' elif allow_skip and mode_input == 's': mode = 'skip' return mode wstool-0.1.13/src/wstool/wstool_cli.py000066400000000000000000000146121270747036000200030ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2010, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """%(prog)s is a command to manipulate ROS workspaces. %(prog)s replaces its predecessor rosws. Official usage: %(prog)s CMD [ARGS] [OPTIONS] %(prog)s will try to infer install path from context Type '%(prog)s help' for usage. """ from __future__ import print_function import os import sys from optparse import OptionParser from wstool.cli_common import get_info_table, get_workspace import wstool.multiproject_cmd as multiproject_cmd import wstool.__version__ from wstool.helpers import ROSINSTALL_FILENAME from wstool.common import MultiProjectException from wstool.multiproject_cli import MultiprojectCLI, \ __MULTIPRO_CMD_DICT__, __MULTIPRO_CMD_ALIASES__, \ __MULTIPRO_CMD_HELP_LIST__, IndentedHelpFormatterWithNL, \ list_usage, get_header ## This file adds or extends commands from multiproject_cli where ROS ## specific output has to be generated. _PROGNAME = 'wstool' class WstoolCLI(MultiprojectCLI): def __init__(self, config_filename=ROSINSTALL_FILENAME, progname=_PROGNAME): def config_generator(config, filename, header=None): wstool.multiproject_cmd.cmd_persist_config( config, filename, header, pretty=True, sort_with_localname=True) MultiprojectCLI.__init__( self, progname=progname, config_filename=config_filename, config_generator=config_generator) def wstool_main(argv=None, usage=None): """ Calls the function corresponding to the first argument. :param argv: sys.argv by default :param usage: function printing usage string, multiproject_cli.list_usage by default """ if argv is None: argv = sys.argv if (sys.argv[0] == '-c'): sys.argv = [_PROGNAME] + sys.argv[1:] if '--version' in argv: print("%s: \t%s\n%s" % (_PROGNAME, wstool.__version__.version, multiproject_cmd.cmd_version())) sys.exit(0) if not usage: usage = lambda: print(list_usage(progname=_PROGNAME, description=__doc__, command_keys=__MULTIPRO_CMD_HELP_LIST__, command_helps=__MULTIPRO_CMD_DICT__, command_aliases=__MULTIPRO_CMD_ALIASES__)) workspace = None if len(argv) < 2: try: workspace = get_workspace(argv, os.getcwd(), config_filename=ROSINSTALL_FILENAME) argv.append('info') except MultiProjectException as e: print(str(e)) usage() return 0 if argv[1] in ['--help', '-h']: usage() return 0 try: command = argv[1] args = argv[2:] if command == 'help': if len(argv) < 3: usage() return 0 else: command = argv[2] args = argv[3:] args.insert(0, "--help") # help help if command == 'help': usage() return 0 cli = WstoolCLI(progname=_PROGNAME) # commands for which we do not infer target workspace commands = {'init': cli.cmd_init} # commands which work on a workspace ws_commands = { 'info': cli.cmd_info, 'remove': cli.cmd_remove, 'set': cli.cmd_set, 'merge': cli.cmd_merge, 'diff': cli.cmd_diff, 'foreach': cli.cmd_foreach, 'scrape': cli.cmd_scrape, 'status': cli.cmd_status, 'update': cli.cmd_update} for label in list(ws_commands.keys()): if label in __MULTIPRO_CMD_ALIASES__: ws_commands[__MULTIPRO_CMD_ALIASES__[label]] = ws_commands[label] if command not in commands and command not in ws_commands: if os.path.exists(command): args = ['-t', command] + args command = 'info' else: if command.startswith('-'): print("First argument must be name of a command: %s" % command) else: print("Error: unknown command: %s" % command) usage() return 1 if command in commands: return commands[command](args) else: if workspace is None and not '--help' in args and not '-h' in args: workspace = get_workspace(args, os.getcwd(), config_filename=ROSINSTALL_FILENAME) return ws_commands[command](workspace, args) except KeyboardInterrupt: return 1 wstool-0.1.13/stdeb.cfg000066400000000000000000000005451270747036000147170ustar00rootroot00000000000000[DEFAULT] Depends: subversion, mercurial, git-core, bzr, python-yaml, python-vcstools (>= 0.1.38) Depends3: subversion, mercurial, git-core, bzr, python3-yaml, python3-vcstools (>= 0.1.38) Conflicts: python3-wstool Conflicts3: python-wstool Suite: oneiric precise quantal raring saucy trusty utopic vivid wily xenial wheezy jessie X-Python3-Version: >= 3.2 wstool-0.1.13/test/000077500000000000000000000000001270747036000141105ustar00rootroot00000000000000wstool-0.1.13/test/__init__.py000066400000000000000000000000001270747036000162070ustar00rootroot00000000000000wstool-0.1.13/test/example.yaml000066400000000000000000000000271270747036000164260ustar00rootroot00000000000000text: foobar number: 2 wstool-0.1.13/test/example_dirs/000077500000000000000000000000001270747036000165645ustar00rootroot00000000000000wstool-0.1.13/test/example_dirs/ros/000077500000000000000000000000001270747036000173675ustar00rootroot00000000000000wstool-0.1.13/test/example_dirs/ros/stack.xml000066400000000000000000000000001270747036000212040ustar00rootroot00000000000000wstool-0.1.13/test/example_dirs/ros_comm/000077500000000000000000000000001270747036000204025ustar00rootroot00000000000000wstool-0.1.13/test/example_dirs/ros_comm/stack.xml000066400000000000000000000000001270747036000222170ustar00rootroot00000000000000wstool-0.1.13/test/example_dirs/roscpp/000077500000000000000000000000001270747036000200725ustar00rootroot00000000000000wstool-0.1.13/test/example_dirs/roscpp/manifest.xml000066400000000000000000000000001270747036000224100ustar00rootroot00000000000000wstool-0.1.13/test/io_wrapper.py000066400000000000000000000006061270747036000166330ustar00rootroot00000000000000class StringIO: """ StringIO.StringIO does not exist in python3 io.StringIO cannot cope with unicode """ def __init__(self): self.stream = '' def write(self, data): self.stream += data def flush(self): pass def __getattr__(self, attr): return getattr(self.stream, attr) def getvalue(self): return self.stream wstool-0.1.13/test/local/000077500000000000000000000000001270747036000152025ustar00rootroot00000000000000wstool-0.1.13/test/local/__init__.py000066400000000000000000000000001270747036000173010ustar00rootroot00000000000000wstool-0.1.13/test/local/mock_client.py000066400000000000000000000073271270747036000200540ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. class MockVcsClient(): """ Mocked vcs client. TODO: we should be using pathon magic mock instead. """ class MockVcsClient(): def __init__(self, scmtype='mocktype', path_exists=False, checkout_success=True, update_success=True, vcs_presence=False, url="mockurl", actualversion=None, specversion=None, remoteversion=None): self.scmtype = scmtype self.path_exists_flag = path_exists self.checkout_success = checkout_success self.update_success = update_success self.vcs_presence = vcs_presence self.mockurl = url self.checkedout = vcs_presence self.updated = False self.actualversion = actualversion self.specversion = specversion self.remoteversion = remoteversion def get_vcs_type_name(self): return self.scmtype def get_diff(self, basepath=None): return self.scmtype + "mockdiff%s" % basepath def get_version(self, revision=None): if revision == None: return self.actualversion else: return self.specversion def get_remote_version(self, fetch=False): return self.remoteversion def get_current_version_label(self): return self.scmtype + "mockcurrentversionlabel" def get_status(self, basepath=None, untracked=False): return self.scmtype + " mockstatus%s,%s" % (basepath, untracked) def path_exists(self): return self.path_exists_flag def checkout(self, uri=None, version=None, verbose=False, timeout=None): self.checkedout = True return self.checkout_success def update(self, version, verbose=False, timeout=None): self.updated = True return self.update_success def detect_presence(self): return self.vcs_presence def get_url(self): return self.mockurl def url_matches(self, url, url_or_shortcut): return (url == url_or_shortcut or url_or_shortcut is None or url_or_shortcut.endswith('_shortcut')) wstool-0.1.13/test/local/test_cli.py000066400000000000000000001316301270747036000173660ustar00rootroot00000000000000#!/usr/bin/env python # Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import os import sys import copy import tempfile import unittest import shutil import subprocess from mock import Mock import wstool.cli_common import wstool.multiproject_cmd import wstool.multiproject_cli from wstool.multiproject_cli import MultiprojectCLI, _get_element_diff import wstool.config from wstool.common import MultiProjectException from wstool.config import MultiProjectException, Config from wstool.config_yaml import PathSpec from test.scm_test_base import AbstractFakeRosBasedTest, _add_to_file, \ _nth_line_split, _create_yaml_file, _create_config_elt_dict from test.io_wrapper import StringIO from . import mock_client class MockConfigElement(): def __init__(self, local_name='', scmtype=None, path=None, uri=None, spec=None): self.scmtype = scmtype self.path = path self.uri = uri self.local_name = local_name self.spec = spec def get_path(self): return self.path def get_local_name(self): return self.local_name def get_path_spec(self): return self.spec def is_vcs_element(self): return True if self.scmtype else False class GetVersionTest(unittest.TestCase): def test_version(self): self.assertFalse(None == wstool.multiproject_cmd.cmd_version()) class GetWorkspaceTest(unittest.TestCase): @classmethod def setUpClass(self): self.environback = copy.copy(os.environ) self.new_environ = os.environ self.test_root_path = os.path.realpath(tempfile.mkdtemp()) self.install_path = os.path.join(self.test_root_path, "install") os.makedirs(self.install_path) self.install_path2 = os.path.join(self.test_root_path, "install2") os.makedirs(self.install_path2) _add_to_file(os.path.join(self.install_path, "configfile"), 'content') path = self.install_path for i in range(4): path = os.path.join(path, "path%s" % i) os.makedirs(path) @classmethod def tearDownClass(self): shutil.rmtree(self.test_root_path) os.environ.update(self.environback) def test_option_arg(self): argv = [] try: self.assertEqual(None, wstool.cli_common.get_workspace(argv, self.test_root_path)) self.fail("expected Exception") except MultiProjectException: pass argv = ["."] try: self.assertEqual(None, wstool.cli_common.get_workspace(argv, self.test_root_path)) self.fail("expected Exception") except MultiProjectException: pass abspath = os.path.abspath('good') argv = ['bad', '-a', "foo", '-t', 'good', '-b', 'bar', '--bad'] self.assertEqual(abspath, wstool.cli_common.get_workspace(argv, self.test_root_path)) argv = ['bad', '-a', "foo", '--target-workspace=good', '-b', 'bar', '--bad'] self.assertEqual(abspath, wstool.cli_common.get_workspace(argv, self.test_root_path)) argv = ['bad', '-a', "foo", '--target-workspace', 'good', '-b', 'bar', '--bad'] self.assertEqual(abspath, wstool.cli_common.get_workspace(argv, self.test_root_path)) argv = ['bad', '-a', "foo", '-tgood', '-b', 'bar', '--bad'] self.assertEqual(abspath, wstool.cli_common.get_workspace(argv, self.test_root_path)) # not supported by OptionParser # argv = ['bad', '-a', "foo", '-t=good', '-b', 'bar', '--bad'] # self.assertEqual(abspath, wstool.cli_common.get_workspace(argv, self.test_root_path)) argv = ['bad', '-a', "foo", '-t', 'good', '-b', 'bar', '--bad'] self.assertEqual(abspath, wstool.cli_common.get_workspace(argv, self.test_root_path)) def test_option_env(self): self.new_environ["VARNAME"] = "" self.new_environ.pop("VARNAME") argv = [] try: self.assertEqual(None, wstool.cli_common.get_workspace(argv, self.test_root_path, varname='VARNAME')) self.fail("expected Exception") except MultiProjectException: pass self.new_environ["VARNAME"] = '' argv = [] try: self.assertEqual(None, wstool.cli_common.get_workspace(argv, self.test_root_path, varname='VARNAME')) self.fail("expected Exception") except MultiProjectException: pass self.new_environ["VARNAME"] = self.install_path2 argv = [] self.assertEqual(self.install_path2, wstool.cli_common.get_workspace(argv, self.test_root_path, varname='VARNAME')) def test_option_path(self): path = self.install_path self.new_environ["VARNAME"] = self.install_path2 for i in range(4): path = os.path.join(path, "path%s"%i) argv = [] self.assertEqual(self.install_path, wstool.cli_common.get_workspace(argv, path, config_filename="configfile")) try: self.assertEqual(self.install_path, wstool.cli_common.get_workspace(argv, path, config_filename="configfile", varname='VARNAME')) self.fail("expected Exception") except MultiProjectException: pass class FunctionsTest(unittest.TestCase): def test_get_mode(self): class FakeOpts: def __init__(self, dele, ab, back): self.delete_changed = dele self.backup_changed = back self.abort_changed = ab class FakeErrors: def __init__(self): self.rerror = None def error(self, foo): self.rerror = foo opts = FakeOpts(dele=False, ab=False, back='') ferr = FakeErrors() self.assertEqual("prompt", wstool.multiproject_cli._get_mode_from_options(ferr, opts)) self.assertEqual(None, ferr.rerror) opts = FakeOpts(dele=True, ab=False, back='') ferr = FakeErrors() self.assertEqual("delete", wstool.multiproject_cli._get_mode_from_options(ferr, opts)) self.assertEqual(None, ferr.rerror) opts = FakeOpts(dele=False, ab=True, back='') ferr = FakeErrors() self.assertEqual("abort", wstool.multiproject_cli._get_mode_from_options(ferr, opts)) self.assertEqual(None, ferr.rerror) opts = FakeOpts(dele=False, ab=False, back='Foo') ferr = FakeErrors() self.assertEqual("backup", wstool.multiproject_cli._get_mode_from_options(ferr, opts)) self.assertEqual(None, ferr.rerror) opts = FakeOpts(dele=True, ab=True, back='') ferr = FakeErrors() wstool.multiproject_cli._get_mode_from_options(ferr, opts) self.assertFalse(None is ferr.rerror) opts = FakeOpts(dele=False, ab=True, back='Foo') ferr = FakeErrors() wstool.multiproject_cli._get_mode_from_options(ferr, opts) self.assertFalse(None is ferr.rerror) opts = FakeOpts(dele=True, ab=False, back='Foo') ferr = FakeErrors() wstool.multiproject_cli._get_mode_from_options(ferr, opts) self.assertFalse(None is ferr.rerror) def test_list_usage(self): #test function exists and does not fail usage = wstool.multiproject_cli.list_usage('foo', 'bardesc %(prog)s', ['cmd1', None, 'cmd2'], {'cmd1': 'help1', 'cmd2': 'help2'}, {'cmd1': 'cmd1a'}) tokens = [y.strip() for x in usage.split(' ') for y in x.splitlines()] self.assertEqual(['bardesc', 'foo', 'cmd1', '(cmd1a)', 'help1', '', 'cmd2', 'help2'], tokens) class FakeConfig(): def __init__(self, celts=[], elts=[], path=''): self.elts = elts self.celts = celts self.path = path def get_config_elements(self): return self.celts def get_source(self): return self.elts def get_base_path(self): return self.path class MockVcsConfigElement(wstool.config_elements.VCSConfigElement): def __init__(self, scmtype, path, local_name, uri, version='', actualversion='', specversion='', properties=None): self.scmtype = scmtype self.path = path self.local_name = local_name self.vcsc = mock_client.MockVcsClient( scmtype, actualversion=actualversion, specversion=specversion) self.uri = uri self.version = version self.install_success = True self.properties = properties def install(self, checkout=True, backup=False, backup_path=None, robust=False, verbose=False, inplace=False, timeout=None): if not self.install_success: raise MultiProjectException("Unittest Mock says install failed") def _get_vcsc(self): return self.vcsc class InstallTest(unittest.TestCase): def test_mock_install(self): test_root = os.path.realpath(tempfile.mkdtemp()) try: git1 = PathSpec('foo', 'git', 'git/uri', 'git.version') svn1 = PathSpec('foos', 'svn', 'svn/uri', '12345') hg1 = PathSpec('fooh', 'hg', 'hg/uri', 'hg.version') bzr1 = PathSpec('foob', 'bzr', 'bzr/uri', 'bzr.version') config = Config([git1, svn1, hg1, bzr1], test_root, None, {"svn": MockVcsConfigElement, "git": MockVcsConfigElement, "hg": MockVcsConfigElement, "bzr": MockVcsConfigElement}) wstool.multiproject_cmd.cmd_install_or_update(config) wstool.multiproject_cmd.cmd_install_or_update(config) wstool.multiproject_cmd.cmd_install_or_update(config, num_threads=10) wstool.multiproject_cmd.cmd_install_or_update(config, num_threads=10) wstool.multiproject_cmd.cmd_install_or_update(config, num_threads=1) wstool.multiproject_cmd.cmd_install_or_update(config, num_threads=1) finally: shutil.rmtree(test_root) def test_mock_install_fail(self): test_root = os.path.realpath(tempfile.mkdtemp()) try: # robust git1 = PathSpec('foo', 'git', 'git/uri', 'git.version') svn1 = PathSpec('foos', 'svn', 'svn/uri', '12345') hg1 = PathSpec('fooh', 'hg', 'hg/uri', 'hg.version') bzr1 = PathSpec('foob', 'bzr', 'bzr/uri', 'bzr.version') config = Config([git1, svn1, hg1, bzr1], install_path=test_root, config_filename=None, extended_types={"svn": MockVcsConfigElement, "git": MockVcsConfigElement, "hg": MockVcsConfigElement, "bzr": MockVcsConfigElement}) config.get_config_elements()[1].install_success = False wstool.multiproject_cmd.cmd_install_or_update( config, robust=True) try: wstool.multiproject_cmd.cmd_install_or_update( config, robust=False) self.fail("expected Exception") except MultiProjectException: pass finally: shutil.rmtree(test_root) class GetStatusDiffInfoCmdTest(unittest.TestCase): def test_status(self): self.mock_config = FakeConfig() result = wstool.multiproject_cmd.cmd_status(self.mock_config) self.assertEqual(len(result), 0) self.mock_config = FakeConfig( [MockVcsConfigElement('git', 'gitpath', 'gitname', None)]) result = wstool.multiproject_cmd.cmd_status(self.mock_config) self.assertEqual(len(result), 1) self.assertTrue(result[0]['status'] is not None) self.assertTrue(result[0]['entry'] is not None) self.mock_config = FakeConfig( [MockVcsConfigElement('git', 'gitpath', 'gitname', None), MockVcsConfigElement( 'svn', 'svnpath', 'svnname', None), MockVcsConfigElement( 'hg', 'hgpath', 'hgname', None), MockVcsConfigElement('bzr', 'bzrpath', 'bzrname', None)]) result = wstool.multiproject_cmd.cmd_status(self.mock_config) self.assertEqual(len(result), 4) self.assertEqual(result[0]['status'].count('git'), 1) self.assertEqual(result[1]['status'].count('svn'), 1) self.assertEqual(result[2]['status'].count('hg'), 1) self.assertEqual(result[3]['status'].count('bzr'), 1) def test_diff(self): self.mock_config = FakeConfig() result = wstool.multiproject_cmd.cmd_diff(self.mock_config) self.assertEqual(len(result), 0) self.mock_config = FakeConfig( [MockVcsConfigElement('git', 'gitpath', 'gitname', None)]) result = wstool.multiproject_cmd.cmd_diff(self.mock_config) self.assertEqual(1, len(result)) self.assertTrue(result[0]['diff'] is not None) self.assertTrue(result[0]['entry'] is not None) self.mock_config = FakeConfig( [MockVcsConfigElement('git', 'gitpath', 'gitname', None), MockVcsConfigElement( 'svn', 'svnpath', 'svnname', None), MockVcsConfigElement( 'hg', 'hgpath', 'hgname', None), MockVcsConfigElement('bzr', 'bzrpath', 'bzrname', None)]) result = wstool.multiproject_cmd.cmd_diff(self.mock_config) self.assertEqual(len(result), 4) self.assertEqual(result[0]['diff'].count('git'), 1) self.assertEqual(result[1]['diff'].count('svn'), 1) self.assertEqual(result[2]['diff'].count('hg'), 1) self.assertEqual(result[3]['diff'].count('bzr'), 1) def test_info(self): self.mock_config = FakeConfig([], [], 'foopath') result = wstool.multiproject_cmd.cmd_info(self.mock_config) self.assertEqual(len(result), 0) self.mock_config = FakeConfig( [MockVcsConfigElement( 'git', 'gitpath', 'gitname', None, version='version')], [], 'foopath') result = wstool.multiproject_cmd.cmd_info(self.mock_config) self.assertEqual(len(result), 1) self.assertEqual(result[0]['scm'], 'git') self.assertEqual(result[0]['version'], 'version') self.mock_config = FakeConfig( [MockVcsConfigElement('git', 'gitpath', 'gitname', None), MockVcsConfigElement( 'svn', 'svnpath', 'svnname', None), MockVcsConfigElement( 'hg', 'hgpath', 'hgname', None), MockVcsConfigElement( 'bzr', 'bzrpath', 'bzrname', None)], [], 'foopath') result = wstool.multiproject_cmd.cmd_info(self.mock_config) self.assertEqual(len(result), 4) self.assertEqual(result[0]['scm'], 'git') self.assertEqual(result[1]['scm'], 'svn') self.assertEqual(result[2]['scm'], 'hg') self.assertEqual(result[3]['scm'], 'bzr') def test_unmanaged(self): root_path = os.path.realpath(tempfile.mkdtemp()) ws_path = os.path.join(root_path, "ws") os.makedirs(ws_path) self.mock_config = FakeConfig([], [], ws_path) # empty folder result = wstool.multiproject_cmd.cmd_find_unmanaged_repos(self.mock_config) self.assertEqual(len(result), 0) # subfolders no vcs gitrepo_path = os.path.join(ws_path, "gitrepo") os.makedirs(gitrepo_path) svnrepo_path = os.path.join(ws_path, "svnrepo") os.makedirs(svnrepo_path) bzrrepo_path = os.path.join(ws_path, "bzrrepo") os.makedirs(bzrrepo_path) hgrepo_path = os.path.join(ws_path, "sub/hgrepo") os.makedirs(hgrepo_path) result = wstool.multiproject_cmd.cmd_find_unmanaged_repos(self.mock_config) self.assertEqual(len(result), 0) # vcs folders os.makedirs(os.path.join(gitrepo_path, ".git")) os.makedirs(os.path.join(hgrepo_path, ".hg")) os.makedirs(os.path.join(svnrepo_path, ".svn")) os.makedirs(os.path.join(bzrrepo_path, ".bzr")) result = wstool.multiproject_cmd.cmd_find_unmanaged_repos(self.mock_config) self.assertEqual(len(result), 4) # vcs folders covered mock = MockVcsConfigElement('git', gitrepo_path, 'gitrepo', None, version='version', actualversion='actual', specversion='spec') self.mock_config = FakeConfig([mock], [], ws_path) # empty folder result = wstool.multiproject_cmd.cmd_find_unmanaged_repos(self.mock_config) self.assertEqual(len(result), 3) def test_info_real_path(self): root_path = os.path.realpath(tempfile.mkdtemp()) el_path = os.path.join(root_path, "ros") os.makedirs(el_path) try: self.mock_config = FakeConfig([], [], 'foopath') result = wstool.multiproject_cmd.cmd_info(self.mock_config) self.assertEqual(len(result), 0) mock = MockVcsConfigElement('git', el_path, 'gitname', None, version='version', actualversion='actual', specversion='spec') self.mock_config = FakeConfig([mock], [], 'foopath') result = wstool.multiproject_cmd.cmd_info(self.mock_config) self.assertEqual(len(result), 1) self.assertEqual(result[0]['scm'], 'git') self.assertEqual(result[0]['version'], 'version') self.assertEqual(result[0]['specversion'], 'spec') self.assertEqual(result[0]['actualversion'], 'actual') mock = MockVcsConfigElement('git', el_path, 'gitname', None, version='version', actualversion='actual', specversion=None) # means scm does not know version self.mock_config = FakeConfig([mock], [], 'foopath') result = wstool.multiproject_cmd.cmd_info(self.mock_config) self.assertEqual(len(result), 1) self.assertEqual(result[0]['scm'], 'git') self.assertEqual(result[0]['version'], 'version') self.assertEqual(result[0]['specversion'], '"version"') self.assertEqual(result[0]['actualversion'], 'actual') finally: shutil.rmtree(root_path) def test_get_status(self): self.test_root_path = os.path.realpath(tempfile.mkdtemp()) try: basepath = '/foo/path' entry = {} self.assertEqual('', wstool.cli_common._get_status_flags(basepath, entry)) entry = {'exists': False} self.assertEqual('x', wstool.cli_common._get_status_flags(basepath, entry)) entry = {'exists': False, 'modified': True} self.assertEqual('x', wstool.cli_common._get_status_flags(basepath, entry)) entry = {'exists': True, 'modified': True} self.assertEqual('M', wstool.cli_common._get_status_flags(basepath, entry)) entry = {'modified': True} self.assertEqual('M', wstool.cli_common._get_status_flags(basepath, entry)) entry = {'actualversion': 'foo', 'specversion': 'bar'} self.assertEqual('V', wstool.cli_common._get_status_flags(basepath, entry)) entry = {'actualversion': 'foo', 'specversion': 'foo'} self.assertEqual('', wstool.cli_common._get_status_flags(basepath, entry)) entry = {'uri': 'foo', 'curr_uri': 'foo'} self.assertEqual('', wstool.cli_common._get_status_flags(basepath, entry)) entry = {'uri': 'foo', 'curr_uri': 'bar'} self.assertEqual('V', wstool.cli_common._get_status_flags(basepath, entry)) entry = {'uri': self.test_root_path, 'curr_uri': self.test_root_path} self.assertEqual('', wstool.cli_common._get_status_flags(basepath, entry)) entry = {'uri': self.test_root_path, 'curr_uri': self.test_root_path + '/foo/..'} self.assertEqual('', wstool.cli_common._get_status_flags(basepath, entry)) entry = {'actualversion': 'foo', 'specversion': 'bar', 'modified': True} self.assertEqual('MV', wstool.cli_common._get_status_flags(basepath, entry)) entry = {'version': 'foo', 'default_remote_label': 'bar'} self.assertEqual('', wstool.cli_common._get_status_flags(basepath, entry)) entry = {'version': None, 'default_remote_label': 'bar', 'curr_version': 'bar'} self.assertEqual('', wstool.cli_common._get_status_flags(basepath, entry)) entry = {'version': None, 'default_remote_label': 'bar', 'curr_version': 'foo'} self.assertEqual('C', wstool.cli_common._get_status_flags(basepath, entry)) entry = {'remote_revision': 'a1b2c3d4', 'actualversion': 'a1b2c3d4'} self.assertEqual('', wstool.cli_common._get_status_flags(basepath, entry)) entry = {'remote_revision': 'a1b2c3d4', 'actualversion': '999999'} self.assertEqual('C', wstool.cli_common._get_status_flags(basepath, entry)) finally: shutil.rmtree(self.test_root_path) def test_info_table(self): basepath = '/foo/path' entries = [] self.assertEqual('', wstool.cli_common.get_info_table(basepath, entries)) entries = [{'scm': 'scm', 'uri': 'uri', 'curr_uri': 'uri', 'version': 'version', 'localname': 'localname', 'specversion': None, 'actualversion': None}] self.assertEqual(["localname", "scm", "version", "uri"], _nth_line_split(-1, wstool.cli_common.get_info_table(basepath, entries))) entries = [{'scm': 'scm', 'uri': 'uri', 'curr_uri': 'uri', 'version': 'version', 'localname': 'localname', 'specversion': 'specversion', 'actualversion': 'actualversion'}] self.assertEqual(["localname", 'V', "scm", "version", "actualversion", "(specversion)", "uri"], _nth_line_split(-1, wstool.cli_common.get_info_table(basepath, entries))) entries = [{'scm': 'scm', 'uri': 'uri', 'curr_uri': 'curr_uri', 'version': 'version', 'localname': 'localname'}] self.assertEqual(["localname", 'V', "scm", "version", "curr_uri", "(uri)"], _nth_line_split(-1, wstool.cli_common.get_info_table(basepath, entries))) entries = [{'scm': 'scm', 'uri': 'uri', 'version': 'version', 'localname': 'localname', 'exists': False}] self.assertEqual(["localname", 'x', "scm", "version", "uri"], _nth_line_split(-1, wstool.cli_common.get_info_table(basepath, entries))) # shorten SHAIDs for git entries = [{'scm': 'git', 'uri': 'uri', 'actualversion': '01234567890123456789012345678', 'localname': 'localname', 'exists': False}] self.assertEqual(["localname", 'x', "git", "012345678901", "uri"], _nth_line_split(-1, wstool.cli_common.get_info_table(basepath, entries))) entries = [{'scm': 'git', 'uri': 'uri', 'actualversion': '01234567890123456789012345678', 'specversion': '1234567890123456789012345678', 'localname': 'localname'}] self.assertEqual(["localname", 'V', "git", "012345678901", "(123456789012)", "uri"], _nth_line_split(-1, wstool.cli_common.get_info_table(basepath, entries))) # recompute svn startdard layout entries = [{'scm': 'svn', 'uri': 'https://some.svn.tags.server/some/tags/tagname', 'curr_uri': None, 'version': 'version', 'localname': 'localname', 'specversion': None, 'actualversion': None}] self.assertEqual(["localname", "svn", "version", "(tags/tagname)", "some.svn.tags.server/some/"], _nth_line_split(-1, wstool.cli_common.get_info_table(basepath, entries))) entries = [{'scm': 'svn', 'uri': 'https://some.svn.tags.server/some/branches/branchname', 'curr_uri': None, 'version': 'version', 'localname': 'localname', 'specversion': None, 'actualversion': None}] self.assertEqual(["localname", "svn", "version", "(branches/branchname)", "some.svn.tags.server/some/"], _nth_line_split(-1, wstool.cli_common.get_info_table(basepath, entries))) entries = [{'scm': 'svn', 'uri': 'https://some.svn.tags.server/some/trunk', 'curr_uri': None, 'version': 'version', 'localname': 'localname', 'specversion': None, 'actualversion': None}] self.assertEqual(["localname", "svn", "version", "(trunk)", "some.svn.tags.server/some/"], _nth_line_split(-1, wstool.cli_common.get_info_table(basepath, entries))) entries = [{'scm': 'svn', 'uri': 'https://some.svn.tags.server/some/branches/branchname', 'curr_uri': 'https://some.svn.tags.server/some/tags/tagname', 'version': 'version', 'localname': 'localname', 'specversion': None, 'actualversion': None}] self.assertEqual(["localname", "svn", "tags/tagname", "(branches/branchname)", "some.svn.tags.server/some/"], _nth_line_split(-1, wstool.cli_common.get_info_table(basepath, entries))) entries = [{'scm': 'svn', 'uri': 'https://some.svn.tags.server/some/branches/branchname', 'curr_uri': 'https://some.svn.tags.server/some/tags/tagname', 'version': None, 'localname': 'localname', 'specversion': 'broken', 'actualversion': 'version'}] self.assertEqual(["localname", "V", "svn", "tags/tagname", "(branches/branchname)", "version", "(broken)", "some.svn.tags.server/some/"], _nth_line_split(-1, wstool.cli_common.get_info_table(basepath, entries))) entries = [{'scm': 'scm', 'uri': 'uri', 'curr_uri': 'uri', 'localname': 'localname', 'actualversion': 'actualversion', 'default_remote_label': 'default_remote_label', 'curr_version': 'curr_version'}] self.assertEqual(["localname", "C", "scm", "curr_version", "(default_remote_label)", "actualversion", "uri"], _nth_line_split(-1, wstool.cli_common.get_info_table(basepath, entries))) entries = [{'scm': 'scm', 'uri': 'uri', 'curr_uri': 'uri', 'localname': 'localname', 'actualversion': 'actualversion', 'default_remote_label': 'curr_version', 'curr_version': 'curr_version'}] self.assertEqual(["localname", "scm", "curr_version", "(=)", "actualversion", "uri"], _nth_line_split(-1, wstool.cli_common.get_info_table(basepath, entries))) entries = [{'scm': 'scm', 'uri': 'uri', 'curr_uri': 'uri', 'version': 'version', 'localname': 'localname', 'specversion': 'specversion', 'actualversion': 'actualversion'}] self.assertEqual(["localname", "scm", "uri"], _nth_line_split(-1, wstool.cli_common.get_info_table(basepath, entries, unmanaged=True))) def test_info_list(self): basepath = '/foo/path' entry = {'scm': 'somescm', 'uri': 'someuri', 'curr_uri': 'somecurr_uri', 'version': 'someversion', 'specversion': 'somespecversion', 'actualversion': 'someactualversion', 'localname': 'somelocalname', 'path': 'somepath'} result = wstool.cli_common.get_info_list(basepath, entry).split() for x in ['somepath', 'somelocalname', 'someactualversion', 'somespecversion', 'someversion', 'somecurr_uri', 'someuri', 'somescm']: self.assertTrue(x in result) class MultiprojectCLITest(AbstractFakeRosBasedTest): def test_cmd_init(self): self.local_path = os.path.join(self.test_root_path, "ws30") os.makedirs(self.local_path) cli = MultiprojectCLI(progname='multi_cli', config_filename='.rosinstall') self.assertEqual(0, cli.cmd_init([self.local_path])) self.assertFalse(os.path.exists(os.path.join(self.local_path, 'setup.sh'))) self.assertFalse(os.path.exists(os.path.join(self.local_path, 'setup.bash'))) self.assertFalse(os.path.exists(os.path.join(self.local_path, 'setup.zsh'))) self.assertTrue(os.path.exists(os.path.join(self.local_path, '.rosinstall'))) # self.assertEqual(0, cli.cmd_merge(self.local_path, [self.ros_path, "-y"])) # self.assertFalse(os.path.exists(os.path.join(self.local_path, 'setup.sh'))) # self.assertFalse(os.path.exists(os.path.join(self.local_path, 'setup.bash'))) # self.assertFalse(os.path.exists(os.path.join(self.local_path, 'setup.zsh'))) # self.assertTrue(os.path.exists(os.path.join(self.local_path, '.rosinstall'))) def test_init_parallel(self): self.local_path = os.path.join(self.test_root_path, "ws31") cli = MultiprojectCLI(progname='multi_cli', config_filename='.rosinstall') self.assertEqual(0, cli.cmd_init([self.local_path, self.simple_rosinstall, "--parallel=5"])) self.assertTrue(os.path.exists(os.path.join(self.local_path, '.rosinstall'))) self.assertTrue(os.path.exists(os.path.join(self.local_path, 'gitrepo'))) self.assertFalse(os.path.exists(os.path.join(self.local_path, 'hgrepo'))) self.assertEqual(0, cli.cmd_merge(self.local_path, [self.simple_changed_vcs_rosinstall, "-y"])) self.assertTrue(os.path.exists(os.path.join(self.local_path, 'gitrepo'))) self.assertFalse(os.path.exists(os.path.join(self.local_path, 'hgrepo'))) self.assertEqual(0, cli.cmd_update(self.local_path, ["--parallel=5"])) self.assertTrue(os.path.exists(os.path.join(self.local_path, 'gitrepo'))) self.assertTrue(os.path.exists(os.path.join(self.local_path, 'hgrepo'))) def test_cmd_info(self): self.local_path = os.path.join(self.test_root_path, "ws_test_cmd_info") cli = MultiprojectCLI(progname='multi_cli', config_filename='.rosinstall') self.assertEqual(0, cli.cmd_info(self.local_path, [])) self.assertEqual(0, cli.cmd_info(self.local_path, ['--root'])) self.assertEqual(0, cli.cmd_info(self.local_path, ['--yaml'])) self.assertEqual(0, cli.cmd_info(self.local_path, ['--untracked'])) self.assertEqual(0, cli.cmd_init([self.local_path, self.simple_rosinstall, "--parallel=5"])) self.assertEqual(0, cli.cmd_merge(self.local_path, [self.simple_changed_vcs_rosinstall, "-y"])) self.assertEqual(0, cli.cmd_info(self.local_path, [])) self.assertEqual(0, cli.cmd_info(self.local_path, ['gitrepo'])) self.assertEqual(0, cli.cmd_info(self.local_path, ['hgrepo'])) self.assertEqual(0, cli.cmd_info(self.local_path, ['--fetch'])) self.assertEqual(0, cli.cmd_info(self.local_path, ['gitrepo', '--fetch'])) self.assertEqual(0, cli.cmd_info(self.local_path, ['hgrepo', '--fetch'])) def test_cmd_set(self): self.local_path = os.path.join(self.test_root_path, "ws31b") cli = MultiprojectCLI(progname='multi_cli', config_filename='.rosinstall') self.assertEqual(0, cli.cmd_init([self.local_path, self.simple_rosinstall, "--parallel=5"])) self.assertEqual(0, cli.cmd_set(self.local_path, [os.path.join(self.local_path, 'hgrepo'), "--hg", 'http://some_uri', '-y'])) cli.cmd_set(self.local_path, [os.path.join(self.local_path, 'hgrepo'), self.hg_path, '--hg', '--update', '-y']) self.assertTrue(os.path.exists(os.path.join(self.local_path, 'hgrepo'))) self.assertRaises(SystemExit, cli.cmd_set, self.local_path, [os.path.join(self.local_path, 'hgrepo'), "--detached", '-y']) cli = MultiprojectCLI(progname='multi_cli', config_filename='.rosinstall', allow_other_element=True) self.assertEqual(0, cli.cmd_set(self.local_path, [os.path.join(self.local_path, 'hgrepo'), "--detached", '-y'])) def test_cmd_foreach(self): self.local_path = os.path.join(self.test_root_path, 'foreach') cli = MultiprojectCLI(progname='multi_cli', config_filename='.rosinstall') cli.cmd_init([self.local_path, self.simple_rosinstall]) # specified localname sys.stdout = f = StringIO() cli.cmd_foreach(self.local_path, argv=['gitrepo', 'pwd']) sys.stdout = sys.__stdout__ repo_path = lambda localname: os.path.join(self.local_path, localname) self.assertEqual('[gitrepo] %s' % repo_path('gitrepo'), f.getvalue().strip()) # --git option sys.stdout = f = StringIO() cli.cmd_foreach(self.local_path, argv=['--git', 'pwd']) sys.stdout = sys.__stdout__ expected_output = '[ros] %s\n[gitrepo] %s' % (repo_path('ros'), repo_path('gitrepo')) self.assertEqual(expected_output, f.getvalue().strip()) def test_cmd_remove(self): # wstool to create dir self.local_path = os.path.join(self.test_root_path, "ws32") cli = MultiprojectCLI(progname='multi_cli', config_filename='.rosinstall', allow_other_element=False) self.assertEqual(0, cli.cmd_init([self.local_path])) self.assertRaises(MultiProjectException, cli.cmd_merge, self.local_path, [self.git_path, "-y"]) self.assertRaises(MultiProjectException, cli.cmd_merge, self.local_path, [self.hg_path, "-y"]) cli = MultiprojectCLI(progname='multi_cli', config_filename='.rosinstall', allow_other_element=True) self.assertEqual(0, cli.cmd_merge(self.local_path, [self.git_path, "-y"])) self.assertEqual(0, cli.cmd_merge(self.local_path, [self.hg_path, "-y"])) config = wstool.multiproject_cmd.get_config(basepath=self.local_path, config_filename='.rosinstall') self.assertEqual(len(config.get_config_elements()), 2) self.assertEqual(0, cli.cmd_remove(self.local_path, [self.git_path])) config = wstool.multiproject_cmd.get_config(basepath=self.local_path, config_filename='.rosinstall') self.assertEqual(len(config.get_config_elements()), 1) def test_cmd_add_uris(self): # wstool to create dir self.local_path = os.path.join(self.test_root_path, "ws33") cli = MultiprojectCLI(progname='multi_cli', config_filename='.rosinstall') simple_rel_rosinstall = os.path.join(self.test_root_path, "simple_rel3.rosinstall") _create_yaml_file([_create_config_elt_dict(scmtype="git", uri=os.path.join(self.test_root_path, "ros"), localname='ros')], simple_rel_rosinstall) self.assertEqual(0, cli.cmd_init([self.local_path, simple_rel_rosinstall])) config = wstool.multiproject_cmd.get_config(basepath=self.local_path, config_filename='.rosinstall') self.assertEqual(1, len(config.get_config_elements())) self.assertEqual('git', config.get_config_elements()[0].get_path_spec().get_scmtype()) wstool.multiproject_cmd.add_uris(config, [self.local_path]) self.assertEqual(len(config.get_config_elements()), 1, config) self.assertEqual('git', config.get_config_elements()[0].get_path_spec().get_scmtype()) wstool.multiproject_cmd.add_uris(config, [os.path.join(self.local_path, '.rosinstall')]) self.assertEqual(len(config.get_config_elements()), 1, config) self.assertEqual('git', config.get_config_elements()[0].get_path_spec().get_scmtype()) def test_get_element_diff(self): self.assertEqual('', _get_element_diff(None, None)) self.assertEqual('', _get_element_diff(None, 42)) self.assertEqual('', _get_element_diff(42, None)) spec = PathSpec('foolocalname', scmtype='fooscm', uri='foouri', version='fooversion', path='foopath') spec2 = PathSpec('foolocalname') element2 = MockConfigElement(local_name='foolocalname', spec=spec2) elements = [element2] config = FakeConfig(celts=elements) output = _get_element_diff(spec, config) self.assertEqual(' foolocalname', output) output = _get_element_diff(spec, config, extra_verbose=True) snippets = [' foolocalname', 'version = fooversion', 'specified uri = foouri', 'scmtype = fooscm'] for s in snippets: self.assertTrue(s in output, "missing snippet: '%s' in '%s'" % (s, output)) def test_merge_dash(self): self.local_path = os.path.join(self.test_root_path, "ws35") cli = MultiprojectCLI(progname='multi_cli', config_filename='.rosinstall') self.assertEqual(0, cli.cmd_init([self.local_path, self.simple_rosinstall, "--parallel=5"])) self.assertTrue(os.path.exists(os.path.join(self.local_path, '.rosinstall'))) self.assertTrue(os.path.exists(os.path.join(self.local_path, 'gitrepo'))) self.assertFalse(os.path.exists(os.path.join(self.local_path, 'hgrepo'))) try: backup = sys.stdin with open(self.simple_changed_vcs_rosinstall, 'r') as fhand: contents = fhand.read() sys.stdin = Mock() sys.stdin.readlines.return_value = contents self.assertEqual(0, cli.cmd_merge(self.local_path, ["-"])) finally: sys.stdin = backup self.assertTrue(os.path.exists(os.path.join(self.local_path, 'gitrepo'))) self.assertFalse(os.path.exists(os.path.join(self.local_path, 'hgrepo'))) self.assertEqual(0, cli.cmd_update(self.local_path, ["--parallel=5"])) self.assertTrue(os.path.exists(os.path.join(self.local_path, 'gitrepo'))) self.assertTrue(os.path.exists(os.path.join(self.local_path, 'hgrepo'))) def test_export(self): root_path = os.path.realpath(tempfile.mkdtemp()) el_path = os.path.join(root_path, "ros") os.makedirs(el_path) try: # default test self.mock_config = FakeConfig([], [], 'foopath') result = wstool.multiproject_cmd.cmd_snapshot(self.mock_config) self.assertEqual(0, len(result)) mock = MockVcsConfigElement('git', el_path, 'gitname', None, version='version', actualversion='actual', specversion='spec') self.mock_config = FakeConfig([mock], [], 'foopath') result = wstool.multiproject_cmd.cmd_snapshot(self.mock_config) self.assertEqual(1, len(result)) self.assertEqual('actual', result[0]['git']['version']) # test other is discarded mock2 = wstool.config_elements.OtherConfigElement(el_path, 'othername') self.mock_config = FakeConfig([mock, mock2], [], 'foopath') result = wstool.multiproject_cmd.cmd_snapshot(self.mock_config) self.assertEqual(1, len(result)) # test fallbacks on specs if actual version is not known mock = MockVcsConfigElement('git', el_path, 'gitname', None, version='version', actualversion=None, specversion='spec') self.mock_config = FakeConfig([mock], [], 'foopath') result = wstool.multiproject_cmd.cmd_snapshot(self.mock_config) self.assertEqual(1, len(result)) self.assertEqual('spec', result[0]['git']['version']) mock = MockVcsConfigElement('git', el_path, 'gitname', None, version='version', actualversion=None, specversion=None) self.mock_config = FakeConfig([mock], [], 'foopath') result = wstool.multiproject_cmd.cmd_snapshot(self.mock_config) self.assertEqual(1, len(result)) self.assertEqual('version', result[0]['git']['version']) finally: shutil.rmtree(root_path) def test_scrape(self): self.local_path = os.path.join(self.test_root_path, "ws37") cli = MultiprojectCLI(progname='multi_cli', config_filename='.rosinstall') self.assertEqual(0, cli.cmd_init([self.local_path, self.simple_rosinstall, "--parallel=5"])) config = wstool.multiproject_cmd.get_config(basepath=self.local_path, config_filename='.rosinstall') try: cli.cmd_scrape(self.local_path, [], config) self.fail("expected Exception") except MultiProjectException: pass git_repo_path = os.path.join(self.local_path, 'gitrepo') hg_repo_path = os.path.join(self.local_path, 'hgrepo') subprocess.check_call(["git", "init", git_repo_path]) subprocess.check_call(["hg", "init", hg_repo_path]) for cmd in [["touch", "foo.txt"], ["hg", "add", hg_repo_path], ["hg", "commit", "-m", "foo"]]: subprocess.check_call(cmd, cwd=hg_repo_path) self.assertEqual(0, cli.cmd_scrape(self.local_path, ['-y'], config)) config = wstool.multiproject_cmd.get_config(basepath=self.local_path, config_filename='.rosinstall') # initial config has 1 element, "ros" self.assertEqual(len(config.get_config_elements()), 3, config.get_config_elements()) wstool-0.1.13/test/local/test_config.py000066400000000000000000000440631270747036000200670ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import os import tempfile import shutil import unittest import wstool.config from wstool.config import MultiProjectException, Config from wstool.config_yaml import PathSpec from . import mock_client _test_root = os.path.dirname(os.path.dirname(__file__)) class MockVcsConfigElement(wstool.config_elements.VCSConfigElement): def __init__(self, scmtype, path, local_name, uri, version='', installed=False, install_success=True, properties=None): self.scmtype = scmtype self.path = path self.local_name = local_name self.uri = uri self.version = version self.vcsc = mock_client.MockVcsClient() self.installed = installed self.install_success = install_success def install(self, backup_path=None, arg_mode='abort', robust=False): if not self.install_success: raise MultiProjectException("Unittest Mock says exception failed") self.installed = True class ConfigMock_Test(unittest.TestCase): def test_mock_vcs_element(self): yaml = [] install_path = 'install/path' config_filename = '.filename' config = Config(yaml, install_path, config_filename) try: config._create_vcs_config_element('mock', None, None, None) fail("expected Exception") except MultiProjectException: pass config = Config(yaml, install_path, config_filename, {"mock": MockVcsConfigElement}) self.assertTrue(config._create_vcs_config_element('mock', None, None, None)) class ConfigSimple_Test(unittest.TestCase): def _get_mock_config(self, yaml, install_path='/install/path', merge_strategy="KillAppend"): config_filename = '.filename' return Config(yaml, install_path, config_filename, {"mock": MockVcsConfigElement}, merge_strategy=merge_strategy) def test_init_fail(self): try: Config(None, "path", None) self.fail("expected Exception") except MultiProjectException: pass try: Config([PathSpec('foo', 'bar')], "path", None) self.fail("expected Exception") except MultiProjectException: pass def test_init(self): yaml = [] install_path = '/install/path' config_filename = '.filename' config = Config(yaml, install_path, config_filename) self.assertEqual(install_path, config.get_base_path()) self.assertEqual([], config.get_config_elements()) config = Config([PathSpec("foo"), PathSpec(os.path.join(_test_root, "example_dirs", "ros_comm")), PathSpec(os.path.join(_test_root, "example_dirs", "ros")), PathSpec(os.path.join(_test_root, "example_dirs", "roscpp")), PathSpec("bar")], ".", None) self.assertEqual(os.path.abspath('.'), config.get_base_path()) def test_config_simple1(self): mock1 = PathSpec('foo') config = self._get_mock_config([mock1]) self.assertEqual(1, len(config.get_config_elements())) self.assertEqual('foo', config.get_config_elements()[0].get_local_name()) self.assertEqual('/install/path/foo', config.get_config_elements()[0].get_path()) def test_config_simple1_with_setupfile(self): mock1 = PathSpec('setup.sh', tags='setup-file') config = self._get_mock_config([mock1]) self.assertEqual(1, len(config.get_config_elements())) self.assertEqual('setup.sh', config.get_config_elements()[0].get_local_name()) self.assertEqual('/install/path/setup.sh', config.get_config_elements()[0].get_path()) mock1 = PathSpec('/foo') mock2 = PathSpec('/opt/setup.sh', tags='setup-file') mock3 = PathSpec('/bar') config = self._get_mock_config([mock1, mock2, mock3]) self.assertEqual(3, len(config.get_config_elements())) self.assertEqual('/opt/setup.sh', config.get_config_elements()[1].get_local_name()) self.assertEqual('/opt/setup.sh', config.get_config_elements()[1].get_path()) def test_config_simple2(self): git1 = PathSpec('foo', 'git', 'git/uri') svn1 = PathSpec('foos', 'svn', 'svn/uri') hg1 = PathSpec('fooh', 'hg', 'hg/uri') bzr1 = PathSpec('foob', 'bzr', 'bzr/uri') config = self._get_mock_config([git1, svn1, hg1, bzr1]) self.assertEqual(4, len(config.get_config_elements())) self.assertEqual('foo', config.get_config_elements()[0].get_local_name()) self.assertEqual('/install/path/foo', config.get_config_elements()[0].get_path()) self.assertEqual('git', config.get_source()[0].get_scmtype()) self.assertEqual('/install/path/git/uri', config.get_source()[0].get_uri()) self.assertEqual('svn', config.get_source()[1].get_scmtype()) self.assertEqual('/install/path/svn/uri', config.get_source()[1].get_uri()) self.assertEqual('hg', config.get_source()[2].get_scmtype()) self.assertEqual('/install/path/hg/uri', config.get_source()[2].get_uri()) self.assertEqual('bzr', config.get_source()[3].get_scmtype()) self.assertEqual('/install/path/bzr/uri', config.get_source()[3].get_uri()) def test_config_simple3(self): git1 = PathSpec('foo', 'git', 'git/uri', 'git.version') svn1 = PathSpec('foos', 'svn', 'svn/uri', '12345') bzr1 = PathSpec('foob', 'bzr', 'bzr/uri', 'bzr.version') hg1 = PathSpec('fooh', 'hg', 'hg/uri', 'hg.version') config = self._get_mock_config([git1, svn1, hg1, bzr1]) self.assertEqual(4, len(config.get_config_elements())) def test_config_realfolders(self): try: root_path = tempfile.mkdtemp() share_path = os.path.join(root_path, "share") os.makedirs(share_path) ros_path = os.path.join(share_path, "ros") os.makedirs(ros_path) p1 = PathSpec('share') p2 = PathSpec('share/ros') config = self._get_mock_config([p1, p2]) self.assertEqual(2, len(config.get_config_elements())) try: p1 = PathSpec('share', 'git', 'git/uri', 'git.version') p2 = PathSpec('share/ros', 'hg', 'hg/uri', 'hg.version') config = self._get_mock_config([p1, p2]) self.fail("expected overlap Exception") except MultiProjectException: pass try: p1 = PathSpec('share', 'git', 'git/uri', 'git.version') p2 = PathSpec('share/ros', 'hg', 'hg/uri', 'hg.version') config = self._get_mock_config([p2, p1]) self.fail("expected overlap Exception") except MultiProjectException: pass try: p1 = PathSpec('share', 'git', 'git/uri', 'git.version') p2 = PathSpec('share/ros') config = self._get_mock_config([p2, p1]) self.fail("expected overlap Exception") except MultiProjectException: pass try: p1 = PathSpec('share', 'git', 'git/uri', 'git.version') p2 = PathSpec('share/ros') config = self._get_mock_config([p1, p2]) self.fail("expected overlap Exception") except MultiProjectException: pass finally: shutil.rmtree(root_path) def test_config_merging_kill_append(self): git1 = PathSpec('foo', 'git', 'git/uri') svn1 = PathSpec('foo', 'svn', 'svn/uri') hg1 = PathSpec('foo', 'hg', 'hg/uri') bzr1 = PathSpec('foo', 'bzr', 'bzr/uri') config = self._get_mock_config([git1, svn1, hg1, bzr1]) self.assertEqual(1, len(config.get_config_elements())) self.assertEqual('bzr', config.get_source()[0].get_scmtype()) self.assertEqual('/install/path/bzr/uri', config.get_source()[0].get_uri()) config = self._get_mock_config([git1, svn1, hg1, bzr1, git1]) self.assertEqual(1, len(config.get_config_elements())) self.assertEqual('git', config.get_source()[0].get_scmtype()) self.assertEqual('/install/path/git/uri', config.get_source()[0].get_uri()) bzr1 = PathSpec('bar', 'bzr', 'bzr/uri') config = self._get_mock_config([git1, svn1, hg1, bzr1]) self.assertEqual(2, len(config.get_config_elements())) self.assertEqual('hg', config.get_source()[0].get_scmtype()) self.assertEqual('/install/path/hg/uri', config.get_source()[0].get_uri()) self.assertEqual('bzr', config.get_source()[1].get_scmtype()) self.assertEqual('/install/path/bzr/uri', config.get_source()[1].get_uri()) config = self._get_mock_config([git1, svn1, hg1, bzr1, git1]) self.assertEqual(2, len(config.get_config_elements())) self.assertEqual('bzr', config.get_source()[0].get_scmtype()) self.assertEqual('/install/path/bzr/uri', config.get_source()[0].get_uri()) self.assertEqual('git', config.get_source()[1].get_scmtype()) self.assertEqual('/install/path/git/uri', config.get_source()[1].get_uri()) def test_config_merging_keep(self): git1 = PathSpec('foo', 'git', 'git/uri') svn1 = PathSpec('foo', 'svn', 'svn/uri') hg1 = PathSpec('foo', 'hg', 'hg/uri') bzr1 = PathSpec('foo', 'bzr', 'bzr/uri') config = self._get_mock_config([git1, svn1, hg1, bzr1], merge_strategy="MergeKeep") self.assertEqual(1, len(config.get_config_elements())) self.assertEqual('git', config.get_source()[0].get_scmtype()) self.assertEqual('/install/path/git/uri', config.get_source()[0].get_uri()) config = self._get_mock_config([git1, svn1, hg1, bzr1, git1], merge_strategy="MergeKeep") self.assertEqual(1, len(config.get_config_elements())) self.assertEqual('git', config.get_source()[0].get_scmtype()) self.assertEqual('/install/path/git/uri', config.get_source()[0].get_uri()) bzr1 = PathSpec('bar', 'bzr', 'bzr/uri') config = self._get_mock_config([git1, svn1, hg1, bzr1], merge_strategy="MergeKeep") self.assertEqual(2, len(config.get_config_elements())) self.assertEqual('git', config.get_source()[0].get_scmtype()) self.assertEqual('/install/path/git/uri', config.get_source()[0].get_uri()) self.assertEqual('bzr', config.get_source()[1].get_scmtype()) self.assertEqual('/install/path/bzr/uri', config.get_source()[1].get_uri()) config = self._get_mock_config([git1, svn1, hg1, bzr1, git1], merge_strategy="MergeKeep") self.assertEqual(2, len(config.get_config_elements())) self.assertEqual('git', config.get_source()[0].get_scmtype()) self.assertEqual('/install/path/git/uri', config.get_source()[0].get_uri()) self.assertEqual('bzr', config.get_source()[1].get_scmtype()) self.assertEqual('/install/path/bzr/uri', config.get_source()[1].get_uri()) def test_config_merging_replace(self): git1 = PathSpec('foo', 'git', 'git/uri') svn1 = PathSpec('foo', 'svn', 'svn/uri') hg1 = PathSpec('foo', 'hg', 'hg/uri') bzr1 = PathSpec('foo', 'bzr', 'bzr/uri') config = self._get_mock_config([git1, svn1, hg1, bzr1], merge_strategy="MergeReplace") self.assertEqual(1, len(config.get_config_elements())) self.assertEqual('bzr', config.get_source()[0].get_scmtype()) self.assertEqual('/install/path/bzr/uri', config.get_source()[0].get_uri()) config = self._get_mock_config([git1, svn1, hg1, bzr1, git1], merge_strategy="MergeReplace") self.assertEqual(1, len(config.get_config_elements())) self.assertEqual('git', config.get_source()[0].get_scmtype()) self.assertEqual('/install/path/git/uri', config.get_source()[0].get_uri()) bzr1 = PathSpec('bar', 'bzr', 'bzr/uri') config = self._get_mock_config([git1, svn1, hg1, bzr1], merge_strategy="MergeReplace") self.assertEqual(2, len(config.get_config_elements())) self.assertEqual('hg', config.get_source()[0].get_scmtype()) self.assertEqual('/install/path/hg/uri', config.get_source()[0].get_uri()) self.assertEqual('bzr', config.get_source()[1].get_scmtype()) self.assertEqual('/install/path/bzr/uri', config.get_source()[1].get_uri()) config = self._get_mock_config([git1, svn1, hg1, bzr1, git1], merge_strategy="MergeReplace") self.assertEqual(2, len(config.get_config_elements())) self.assertEqual('git', config.get_source()[0].get_scmtype()) self.assertEqual('/install/path/git/uri', config.get_source()[0].get_uri()) self.assertEqual('bzr', config.get_source()[1].get_scmtype()) self.assertEqual('/install/path/bzr/uri', config.get_source()[1].get_uri()) def test_remove(self): git1 = PathSpec('foo', 'git', 'git/uri', 'git.version') svn1 = PathSpec('foos', 'svn', 'svn/uri', '12345') hg1 = PathSpec('fooh', 'hg', 'hg/uri', 'hg.version') bzr1 = PathSpec('foob', 'bzr', 'bzr/uri', 'bzr.version') config = self._get_mock_config([git1, svn1, hg1, bzr1]) self.assertEqual(4, len(config.get_config_elements())) self.assertFalse(config.remove_element(None)) self.assertFalse(config.remove_element('bar')) self.assertEqual(4, len(config.get_config_elements())) self.assertTrue(config.remove_element('foo')) self.assertEqual(3, len(config.get_config_elements())) self.assertEqual('/install/path/foos', config.get_config_elements()[0].get_path()) self.assertEqual('/install/path/fooh', config.get_config_elements()[1].get_path()) self.assertEqual('/install/path/foob', config.get_config_elements()[2].get_path()) self.assertTrue(config.remove_element('fooh')) self.assertEqual(2, len(config.get_config_elements())) self.assertEqual('/install/path/foos', config.get_config_elements()[0].get_path()) self.assertEqual('/install/path/foob', config.get_config_elements()[1].get_path()) self.assertTrue(config.remove_element('foos')) self.assertEqual(1, len(config.get_config_elements())) self.assertTrue(config.remove_element('foob')) self.assertEqual(0, len(config.get_config_elements())) def test_absolute_localname(self): mock1 = PathSpec('/foo/bim') config = self._get_mock_config([mock1], install_path='/foo/bar/ba/ra/baz/bam') self.assertEqual(1, len(config.get_config_elements())) self.assertEqual('/foo/bim', config.get_config_elements()[0].get_local_name()) self.assertEqual('/foo/bim', config.get_config_elements()[0].get_path()) def test_unnormalized_localname(self): "Should source normalize local-name" mock1 = PathSpec('foo/bar/..') config = self._get_mock_config([mock1]) self.assertEqual(1, len(config.get_config_elements())) self.assertEqual('foo', config.get_config_elements()[0].get_local_name()) self.assertEqual('/install/path/foo', config.get_config_elements()[0].get_path()) def test_long_localname(self): "Should source choose shorter local-name" mock1 = PathSpec("/foo/bar/boo/far/bim") config = self._get_mock_config([mock1], '/foo/bar/boo/far') self.assertEqual(1, len(config.get_config_elements())) self.assertEqual('/foo/bar/boo/far/bim', config.get_config_elements()[0].get_local_name()) self.assertEqual('/foo/bar/boo/far/bim', config.get_config_elements()[0].get_path()) def test_double_entry(self): "Should source be rewritten without duplicates" mock1 = PathSpec('foo') mock2 = PathSpec('foo') config = self._get_mock_config([mock1, mock2]) self.assertEqual(1, len(config.get_config_elements())) def test_equivalent_entry(self): "Should source be rewritten without duplicates" mock1 = PathSpec('foo') mock2 = PathSpec('./foo') config = self._get_mock_config([mock1, mock2]) self.assertEqual(1, len(config.get_config_elements())) def test_double_localname(self): "Entries have same local name" mock1 = PathSpec('foo', 'git', 'git/uri') mock2 = PathSpec('foo', 'hg', 'hg/uri') config = self._get_mock_config([mock1, mock2]) self.assertEqual(1, len(config.get_config_elements())) def test_equivalent_localname(self): "Entries have equivalent local name" mock1 = PathSpec('foo', 'git', 'git/uri') mock2 = PathSpec('./foo/bar/..', 'hg', 'hg/uri') config = self._get_mock_config([mock1, mock2]) self.assertEqual(1, len(config.get_config_elements())) wstool-0.1.13/test/local/test_config_elt.py000066400000000000000000000234171270747036000207330ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import unittest import wstool.config from wstool.common import MultiProjectException from . import mock_client class ConfigElements_Test(unittest.TestCase): def test_simple_config_element_API(self): path = "some/path" localname = "some/local/name" other1 = wstool.config_elements.ConfigElement(path, localname) self.assertEqual(path, other1.get_path()) self.assertEqual(localname, other1.get_local_name()) self.assertFalse(other1.is_vcs_element()) other1 = wstool.config_elements.OtherConfigElement(path, localname) self.assertEqual(path, other1.get_path()) self.assertEqual(localname, other1.get_local_name()) self.assertEqual({'other': {'local-name': 'some/local/name'}}, other1.get_path_spec().get_legacy_yaml()) self.assertFalse(other1.is_vcs_element()) other1 = wstool.config_elements.SetupConfigElement(path, localname) self.assertEqual(path, other1.get_path()) self.assertEqual(localname, other1.get_local_name()) self.assertEqual({'setup-file': {'local-name': 'some/local/name'}}, other1.get_path_spec().get_legacy_yaml()) self.assertFalse(other1.is_vcs_element()) other1 = wstool.config_elements.OtherConfigElement(path, localname, properties=[{}]) self.assertEqual(path, other1.get_path()) self.assertEqual(localname, other1.get_local_name()) self.assertEqual({'other': {'local-name': 'some/local/name'}}, other1.get_path_spec().get_legacy_yaml()) self.assertFalse(other1.is_vcs_element()) other1 = wstool.config_elements.OtherConfigElement(path, localname, properties=['meta']) self.assertEqual(path, other1.get_path()) self.assertEqual(localname, other1.get_local_name()) self.assertEqual({'other': {'local-name': 'some/local/name', 'meta': None}}, other1.get_path_spec().get_legacy_yaml()) self.assertFalse(other1.is_vcs_element()) other1 = wstool.config_elements.OtherConfigElement(path, localname, properties=[{'meta': {'repo-name': 'skynetish-ros-pkg'}}]) self.assertEqual(path, other1.get_path()) self.assertEqual(localname, other1.get_local_name()) self.assertEqual({'other': {'local-name': 'some/local/name', 'meta': {'repo-name': 'skynetish-ros-pkg'}}}, other1.get_path_spec().get_legacy_yaml()) self.assertFalse(other1.is_vcs_element()) def test_mock_vcs_config_element_init(self): path = "some/path" localname = "some/local/name" try: wstool.config_elements.AVCSConfigElement("mock", None, None, None) self.fail("Exception expected") except MultiProjectException: pass try: wstool.config_elements.AVCSConfigElement("mock", "path", None, None) self.fail("Exception expected") except MultiProjectException: pass try: wstool.config_elements.AVCSConfigElement("mock", None, None, "some/uri") self.fail("Exception expected") except MultiProjectException: pass path = "some/path" localname = "some/local/name" uri = 'some/uri' version = 'some.version' vcsc = wstool.config_elements.AVCSConfigElement("mock", path, localname, uri, vcsc=mock_client.MockVcsClient()) self.assertEqual(path, vcsc.get_path()) self.assertEqual(localname, vcsc.get_local_name()) self.assertEqual(uri, vcsc.uri) self.assertTrue(vcsc.is_vcs_element()) self.assertEqual("mocktypemockdiffNone", vcsc.get_diff()) self.assertEqual("mocktype mockstatusNone,False", vcsc.get_status()) self.assertEqual({'mock': {'local-name': 'some/local/name', 'uri': 'some/uri'}}, vcsc.get_path_spec().get_legacy_yaml()) self.assertEqual({'mock': {'local-name': 'some/local/name', 'uri': 'some/uri', }}, vcsc.get_versioned_path_spec().get_legacy_yaml()) vcsc = wstool.config_elements.AVCSConfigElement("mock", path, localname, uri, None, vcsc=mock_client.MockVcsClient()) self.assertEqual(path, vcsc.get_path()) self.assertEqual(localname, vcsc.get_local_name()) self.assertEqual(uri, vcsc.uri) self.assertTrue(vcsc.is_vcs_element()) self.assertEqual("mocktypemockdiffNone", vcsc.get_diff()) self.assertEqual("mocktype mockstatusNone,False", vcsc.get_status()) self.assertEqual({'mock': {'local-name': 'some/local/name', 'uri': 'some/uri'}}, vcsc.get_path_spec().get_legacy_yaml()) self.assertEqual({'mock': {'local-name': 'some/local/name', 'uri': 'some/uri', }}, vcsc.get_versioned_path_spec().get_legacy_yaml()) vcsc = wstool.config_elements.AVCSConfigElement("mock", path, localname, uri, version, vcsc=mock_client.MockVcsClient()) self.assertEqual(path, vcsc.get_path()) self.assertEqual(localname, vcsc.get_local_name()) self.assertEqual(uri, vcsc.uri) self.assertTrue(vcsc.is_vcs_element()) self.assertEqual("mocktypemockdiffNone", vcsc.get_diff()) self.assertEqual("mocktype mockstatusNone,False", vcsc.get_status()) self.assertEqual({'mock': {'local-name': 'some/local/name', 'version': 'some.version', 'uri': 'some/uri'}}, vcsc.get_path_spec().get_legacy_yaml()) self.assertEqual({'mock': {'local-name': 'some/local/name', 'version': 'some.version', 'uri': 'some/uri'}}, vcsc.get_versioned_path_spec().get_legacy_yaml()) vcsc = wstool.config_elements.AVCSConfigElement( "mock", path, localname, uri, version, vcsc=mock_client.MockVcsClient(), properties=[{'meta': {'repo-name': 'skynetish-ros-pkg'}}]) self.assertEqual(path, vcsc.get_path()) self.assertEqual(localname, vcsc.get_local_name()) self.assertEqual(uri, vcsc.uri) self.assertTrue(vcsc.is_vcs_element()) self.assertEqual("mocktypemockdiffNone", vcsc.get_diff()) self.assertEqual("mocktype mockstatusNone,False", vcsc.get_status()) self.assertEqual({'mock': {'local-name': 'some/local/name', 'version': 'some.version', 'uri': 'some/uri', 'meta': {'repo-name': 'skynetish-ros-pkg'}}}, vcsc.get_path_spec().get_legacy_yaml()) self.assertEqual({'mock': {'local-name': 'some/local/name', 'version': 'some.version', 'uri': 'some/uri', 'meta': {'repo-name': 'skynetish-ros-pkg'}}}, vcsc.get_versioned_path_spec().get_legacy_yaml()) # this time using 'uri_shortcut' in mock_client.MockVcsClient, get special treatment un url_matches() uri2 = 'some/uri2' vcsc = wstool.config_elements.AVCSConfigElement( "mock", path, localname, uri2, version, vcsc=mock_client.MockVcsClient(url='url_shortcut'), properties=[{'meta': {'repo-name': 'skynetish-ros-pkg'}}]) self.assertEqual(path, vcsc.get_path()) self.assertEqual(localname, vcsc.get_local_name()) self.assertEqual(uri2, vcsc.uri) self.assertTrue(vcsc.is_vcs_element()) self.assertEqual("mocktypemockdiffNone", vcsc.get_diff()) self.assertEqual("mocktype mockstatusNone,False", vcsc.get_status()) self.assertEqual({'mock': {'local-name': 'some/local/name', 'version': 'some.version', 'uri': 'some/uri2', 'meta': {'repo-name': 'skynetish-ros-pkg'}}}, vcsc.get_path_spec().get_legacy_yaml()) self.assertEqual({'mock': {'local-name': 'some/local/name', 'version': 'some.version', 'uri': 'some/uri2', 'meta': {'repo-name': 'skynetish-ros-pkg'}}}, vcsc.get_versioned_path_spec().get_legacy_yaml()) def test_mock_install(self): path = "some/path" localname = "some/local/name" uri = 'some/uri' version = 'some.version' mockclient = mock_client.MockVcsClient(url=uri) vcsc = wstool.config_elements.AVCSConfigElement("mock", path, localname, uri, None, vcsc=mockclient) vcsc.install() self.assertTrue(mockclient.checkedout) self.assertFalse(mockclient.updated) # checkout failure mockclient = mock_client.MockVcsClient(url=uri, checkout_success=False) try: vcsc = wstool.config_elements.AVCSConfigElement("mock", path, localname, uri, None, vcsc=mockclient) vcsc.install() self.fail("should have raised Exception") except MultiProjectException: pass wstool-0.1.13/test/local/test_config_yaml.py000066400000000000000000000416121270747036000211060ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import os import unittest import tempfile import shutil import wstool.config_yaml import wstool.config from wstool.common import MultiProjectException from wstool.config_yaml import rewrite_included_source, \ get_path_spec_from_yaml, get_yaml_from_uri, get_path_specs_from_uri, \ PathSpec, aggregate_from_uris _test_root = os.path.dirname(os.path.dirname(__file__)) class YamlIO_Test(unittest.TestCase): def test_get_yaml_from_uri_from_file(self): filename = os.path.join(_test_root, "example.yaml") yamlstr = get_yaml_from_uri(filename) self.assertTrue("text" in yamlstr) self.assertTrue(yamlstr["text"] == "foobar") self.assertTrue("number" in yamlstr) self.assertTrue(yamlstr["number"] == 2) # invalid try: yaml = get_yaml_from_uri( os.path.join(_test_root, "example-broken.yaml")) except MultiProjectException: pass try: get_path_specs_from_uri(filename) self.fail("Expected exception") except MultiProjectException: pass def test_get_yaml_from_uri_from_missing_file(self): filename = "/asdfasdfasdfasfasdf_does_not_exist" try: get_yaml_from_uri(filename) self.fail("Expected exception") except MultiProjectException: pass try: get_path_specs_from_uri(filename) self.fail("Expected exception") except MultiProjectException: pass def test_get_yaml_from_uri_from_invalid_url(self): url = "http://invalidurl" try: get_yaml_from_uri(url) self.fail("Expected exception") except MultiProjectException: pass # valid but non-yaml url = "http://www.google.com" try: get_yaml_from_uri(url) self.fail("Expected exception") except MultiProjectException: pass class ConfigElementYamlFunctions_Test(unittest.TestCase): def test_rewrite_included_source(self): base_path = '/foo/bar' version = 'common_rosdeps-1.0.2' uri = 'https://kforge.ros.org/common/rosdepcore' # same simple struct = [PathSpec('local', 'hg', uri, version)] rewrite_included_source(struct, "/foo/bar") self.assertEqual(PathSpec(os.path.join(base_path, "local")), struct[0]) # absolute path struct = [PathSpec("/opt/poo", 'hg', uri, version)] rewrite_included_source(struct, "/foo/bar") self.assertEqual([PathSpec("/opt/poo")], struct) # absolute path, relative basepath struct = [PathSpec("/opt/poo", 'hg', uri, version)] rewrite_included_source(struct, "foo/bar") self.assertEqual([PathSpec("/opt/poo")], struct) # relative base path struct = [PathSpec("../opt/poo", 'hg', uri, version)] rewrite_included_source(struct, "foo/bar") self.assertEqual([PathSpec("foo/opt/poo")], struct) def test_rewrite_included_source_setupfile(self): base_path = '/foo/bar' version = 'common_rosdeps-1.0.2' uri = 'https://kforge.ros.org/common/rosdepcore' # same simple struct = [PathSpec('local', tags='setup-file')] rewrite_included_source(struct, "/foo/bar") self.assertEqual(PathSpec(os.path.join(base_path, "local"), tags='setup-file'), struct[0]) # absolute path struct = [PathSpec("/opt/poo", tags='setup-file')] rewrite_included_source(struct, "/foo/bar") self.assertEqual([PathSpec("/opt/poo", tags='setup-file')], struct) # absolute path, relative basepath struct = [PathSpec("/opt/poo", tags='setup-file')] rewrite_included_source(struct, "foo/bar") self.assertEqual([PathSpec("/opt/poo", tags='setup-file')], struct) # relative base path struct = [PathSpec("../opt/poo", tags='setup-file')] rewrite_included_source(struct, "foo/bar") self.assertEqual([PathSpec("foo/opt/poo", tags='setup-file')], struct) class UriAggregationTest(unittest.TestCase): def test_aggregate_from_uris(self): self.directory = tempfile.mkdtemp() config = wstool.config.Config( [PathSpec('ros', 'svn', 'some/uri')], self.directory) wstool.config_yaml.generate_config_yaml(config, 'foo', "# Hello\n") ryaml = aggregate_from_uris( [self.directory], config.get_config_filename()) self.assertEqual(ryaml[0].get_legacy_yaml(), {'other': {'local-name': self.directory}}) self.assertRaises(MultiProjectException, aggregate_from_uris, [self.directory], config.get_config_filename(), allow_other_element=False) def tearDown(self): if os.path.exists(self.directory): shutil.rmtree(self.directory) class ConfigFile_Test(unittest.TestCase): def test_generate(self): self.directory = tempfile.mkdtemp() config = wstool.config.Config([], self.directory) wstool.config_yaml.generate_config_yaml(config, 'foo', "# Hello\n") filepath = os.path.join(self.directory, 'foo') self.assertTrue(os.path.exists(filepath)) with open(filepath, 'r') as f: read_data = f.read() lines = read_data.splitlines() self.assertEqual(1, len(lines), lines) self.assertEqual("# Hello", lines[0]) def test_generate_with_other(self): self.directory = tempfile.mkdtemp() config = wstool.config.Config([PathSpec('ros')], self.directory) wstool.config_yaml.generate_config_yaml(config, 'foo', "# Hello\n") filepath = os.path.join(self.directory, 'foo') self.assertTrue(os.path.exists(filepath)) with open(filepath, 'r') as f: read_data = f.read() lines = read_data.splitlines() self.assertEqual("# Hello", lines[0]) self.assertEqual("- other: {local-name: ros}", lines[1]) def test_generate_with_stack(self): self.directory = tempfile.mkdtemp() config = wstool.config.Config([PathSpec('ros', 'svn', 'some/uri')], self.directory) wstool.config_yaml.generate_config_yaml(config, 'foo', "# Hello\n") filepath = os.path.join(self.directory, 'foo') self.assertTrue(os.path.exists(filepath)) with open(filepath, 'r') as f: read_data = f.read() lines = read_data.splitlines() self.assertEqual("# Hello", lines[0]) self.assertEqual("- svn: {local-name: ros, uri: %s/some/uri}" % self.directory, lines[1]) def test_generate_with_pretty_format(self): self.directory = tempfile.mkdtemp() config = wstool.config.Config([PathSpec('ros', 'svn', 'some/uri')], self.directory) wstool.config_yaml.generate_config_yaml(config, 'foo', "# Hello\n", pretty=True) filepath = os.path.join(self.directory, 'foo') self.assertTrue(os.path.exists(filepath)) with open(filepath, 'r') as f: read_data = f.read() self.assertEqual("""\ # Hello - svn: local-name: ros uri: %s/some/uri """ % self.directory, read_data) def test_generate_sorted_with_localname(self): self.directory = tempfile.mkdtemp() elements = [PathSpec('ros', 'svn', 'some/uri1'), PathSpec('git', 'git', 'some/uri2')] config = wstool.config.Config(elements, self.directory) wstool.config_yaml.generate_config_yaml(config, 'foo', "# Hello\n", sort_with_localname=True) filepath = os.path.join(self.directory, 'foo') self.assertTrue(os.path.exists(filepath)) with open(filepath, 'r') as f: read_data = f.read() self.assertEqual("""\ # Hello - git: {local-name: git, uri: %s/some/uri2} - svn: {local-name: ros, uri: %s/some/uri1} """ % (self.directory, self.directory), read_data) def tearDown(self): if os.path.exists(self.directory): shutil.rmtree(self.directory) class ConfigElementYamlWrapper_Test(unittest.TestCase): def test_original_syntax_scm(self): # - hg: {local-name: common_rosdeps, version: common_rosdeps-1.0.2, uri: https://kforge.ros.org/common/rosdepcore} local_name = 'common_rosdeps' version = 'common_rosdeps-1.0.2' uri = 'https://kforge.ros.org/common/rosdepcore' scmtype = 'hg' struct = {scmtype: {'local-name': local_name, 'version': version, 'uri': uri}} wrap = get_path_spec_from_yaml(struct) self.assertEqual(scmtype, wrap.get_scmtype()) self.assertEqual(scmtype, wrap.get_legacy_type()) self.assertEqual(version, wrap.get_version()) self.assertEqual(uri, wrap.get_uri()) self.assertEqual(struct, wrap.get_legacy_yaml()) # empty version local_name = 'common_rosdeps' version = None uri = 'https://kforge.ros.org/common/rosdepcore' scmtype = 'hg' struct = {scmtype: {'local-name': local_name, 'version': version, 'uri': uri}} wrap = get_path_spec_from_yaml(struct) self.assertEqual(scmtype, wrap.get_scmtype()) self.assertEqual(scmtype, wrap.get_legacy_type()) self.assertEqual(version, wrap.get_version()) self.assertEqual(uri, wrap.get_uri()) self.assertEqual({scmtype: {'local-name': local_name, 'uri': uri}}, wrap.get_legacy_yaml()) # no version local_name = 'common_rosdeps' version = None uri = 'https://kforge.ros.org/common/rosdepcore' scmtype = 'hg' struct = {scmtype: {'local-name': local_name, 'uri': uri}} wrap = get_path_spec_from_yaml(struct) self.assertEqual(scmtype, wrap.get_scmtype()) self.assertEqual(scmtype, wrap.get_legacy_type()) self.assertEqual(version, wrap.get_version()) self.assertEqual(uri, wrap.get_uri()) self.assertEqual({'hg': {'local-name': 'common_rosdeps', 'uri': 'https://kforge.ros.org/common/rosdepcore'}}, wrap.get_legacy_yaml()) # other local_name = 'common_rosdeps' version = None uri = None scmtype = 'other' struct = {scmtype: {'local-name': local_name, 'version': version, 'uri': uri}} wrap = get_path_spec_from_yaml(struct) self.assertEqual(None, wrap.get_scmtype()) self.assertEqual(scmtype, wrap.get_legacy_type()) self.assertEqual(version, wrap.get_version()) self.assertEqual(uri, wrap.get_uri()) self.assertEqual({scmtype: {'local-name': local_name}}, wrap.get_legacy_yaml()) # properties (undocumented feature required for builds) local_name = 'common_rosdeps' version = None uri = None scmtype = 'other' struct = {scmtype: {'local-name': local_name, 'version': version, 'uri': uri, 'meta': {'repo-name': 'skynetish-ros-pkg'}}} wrap = get_path_spec_from_yaml(struct) self.assertEqual(None, wrap.get_scmtype()) self.assertEqual(scmtype, wrap.get_legacy_type()) self.assertEqual(version, wrap.get_version()) self.assertEqual(uri, wrap.get_uri()) self.assertEqual([{'meta': {'repo-name': 'skynetish-ros-pkg'}}], wrap.get_tags()) self.assertEqual({scmtype: {'local-name': local_name, 'meta': {'repo-name': 'skynetish-ros-pkg'}}}, wrap.get_legacy_yaml()) # properties (undocumented feature required for builds) local_name = 'common_rosdeps' version = None uri = 'some/uri' scmtype = 'git' struct = {scmtype: {'local-name': local_name, 'version': version, 'uri': uri, 'meta': {'repo-name': 'skynetish-ros-pkg'}}} wrap = get_path_spec_from_yaml(struct) self.assertEqual('git', wrap.get_scmtype()) self.assertEqual(scmtype, wrap.get_legacy_type()) self.assertEqual(version, wrap.get_version()) self.assertEqual(uri, wrap.get_uri()) self.assertEqual([{'meta': {'repo-name': 'skynetish-ros-pkg'}}], wrap.get_tags()) self.assertEqual({scmtype: {'local-name': local_name, 'uri': 'some/uri', 'meta': {'repo-name': 'skynetish-ros-pkg'}}}, wrap.get_legacy_yaml()) def test_original_syntax_invalids(self): local_name = 'common_rosdeps' version = '1234' uri = 'https://kforge.ros.org/common/rosdepcore' scmtype = 'hg' try: struct = {} get_path_spec_from_yaml(struct) self.fail("expected exception") except MultiProjectException: pass try: struct = {"hello world": None} get_path_spec_from_yaml(struct) self.fail("expected exception") except MultiProjectException: pass try: struct = {"git": None} get_path_spec_from_yaml(struct) self.fail("expected exception") except MultiProjectException: pass try: struct = {"git": {}} get_path_spec_from_yaml(struct) self.fail("expected exception") except MultiProjectException: pass try: struct = {"git": {"uri": uri}} get_path_spec_from_yaml(struct) self.fail("expected exception") except MultiProjectException: pass try: struct = {"git": {"local-name": local_name}} get_path_spec_from_yaml(struct) self.fail("expected exception") except MultiProjectException: pass try: struct = {"foo": {"foo": None}} get_path_spec_from_yaml(struct) self.fail("expected exception") except MultiProjectException: pass try: struct = {"other": {"foo": None}} get_path_spec_from_yaml(struct) self.fail("expected exception") except MultiProjectException: pass try: struct = {"other": {"uri": uri}} get_path_spec_from_yaml(struct) self.fail("expected exception") except MultiProjectException: pass try: struct = {"other": {"version": version}} get_path_spec_from_yaml(struct) self.fail("expected exception") except MultiProjectException: pass def test_original_syntax_setupfile(self): local_name = '/opt/ros/fuerte/setup.sh' version = None uri = None scmtype = 'setup-file' struct = {scmtype: {'local-name': local_name, 'version': version, 'uri': uri}} wrap = get_path_spec_from_yaml(struct) self.assertEqual(None, wrap.get_scmtype()) self.assertEqual(scmtype, wrap.get_legacy_type()) self.assertEqual(version, wrap.get_version()) self.assertEqual(uri, wrap.get_uri()) version = "1234" uri = 'https://kforge.ros.org/common/rosdepcore' try: struct = {"setup-file": {"uri": uri}} get_path_spec_from_yaml(struct) self.fail("expected exception") except MultiProjectException: pass try: struct = {"setup-file": {"version": version}} get_path_spec_from_yaml(struct) self.fail("expected exception") except MultiProjectException: pass wstool-0.1.13/test/local/test_diff_functions_bzr.py000066400000000000000000000261461270747036000225010ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import unicode_literals import os import sys from test.io_wrapper import StringIO import subprocess import wstool import wstool.helpers import wstool.wstool_cli from wstool.wstool_cli import WstoolCLI from wstool.wstool_cli import wstool_main import test.scm_test_base from test.scm_test_base import AbstractSCMTest, _add_to_file, _nth_line_split def create_bzr_repo(remote_path): # create a "remote" repo subprocess.check_call(["bzr", "init"], cwd=remote_path) subprocess.check_call(["touch", "fixed.txt"], cwd=remote_path) subprocess.check_call(["touch", "modified.txt"], cwd=remote_path) subprocess.check_call(["touch", "modified-fs.txt"], cwd=remote_path) subprocess.check_call(["touch", "deleted.txt"], cwd=remote_path) subprocess.check_call(["touch", "deleted-fs.txt"], cwd=remote_path) subprocess.check_call(["bzr", "add", "fixed.txt"], cwd=remote_path) subprocess.check_call(["bzr", "add", "modified.txt"], cwd=remote_path) subprocess.check_call(["bzr", "add", "modified-fs.txt"], cwd=remote_path) subprocess.check_call(["bzr", "add", "deleted.txt"], cwd=remote_path) subprocess.check_call(["bzr", "add", "deleted-fs.txt"], cwd=remote_path) subprocess.check_call(["bzr", "commit", "-m", "modified"], cwd=remote_path) def modify_bzr_repo(clone_path): # make local modifications subprocess.check_call(["rm", "deleted-fs.txt"], cwd=clone_path) subprocess.check_call(["bzr", "rm", "deleted.txt"], cwd=clone_path) _add_to_file(os.path.join(clone_path, "modified-fs.txt"), "foo\n") _add_to_file(os.path.join(clone_path, "modified.txt"), "foo\n") _add_to_file(os.path.join(clone_path, "added-fs.txt"), "tada\n") _add_to_file(os.path.join(clone_path, "added.txt"), "flam\n") subprocess.check_call(["bzr", "add", "added.txt"], cwd=clone_path) class WstoolDiffBzrTest(AbstractSCMTest): @classmethod def setUpClass(self): AbstractSCMTest.setUpClass() remote_path = os.path.join(self.test_root_path, "remote") os.makedirs(remote_path) create_bzr_repo(remote_path) # wstool the remote repo and fake ros _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- bzr: {local-name: clone, uri: %s}" % remote_path) cmd = ["wstool", "update", "-t", "ws"] os.chdir(self.test_root_path) wstool_main(cmd) clone_path = os.path.join(self.local_path, "clone") modify_bzr_repo(clone_path) def check_diff_output(self, output): # uncomment following line for easiest way to get actual output with escapes # self.assertEqual(None, output); # bzr writes date-time of file into diff self.assertTrue(output.startswith("=== added file 'added.txt'\n--- clone/added.txt"), msg=0) self.assertTrue(0 < output.find("+++ clone/added.txt"), msg=1) self.assertTrue(0 < output.find("@@ -0,0 +1,1 @@\n+flam\n\n"), msg=2) self.assertTrue(0 < output.find("=== removed file 'deleted-fs.txt'\n=== removed file 'deleted.txt'\n=== modified file 'modified-fs.txt'\n--- clone/modified-fs.txt"), msg=3) self.assertTrue(0 < output.find("@@ -0,0 +1,1 @@\n+foo\n\n=== modified file 'modified.txt'\n--- clone/modified.txt"), msg=4) def test_wstool_diff_bzr_outside(self): """Test diff output for bzr when run outside workspace""" cmd = ["wstool", "diff", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = sys.__stdout__ output = output.getvalue() self.check_diff_output(output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_diff(os.path.join(self.test_root_path, 'ws'), [])) def test_wstool_diff_bzr_inside(self): """Test diff output for bzr when run inside workspace""" directory = self.test_root_path + "/ws" cmd = ["wstool", "diff"] os.chdir(directory) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() sys.stdout = sys.__stdout__ self.check_diff_output(output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_diff(directory, [])) def test_wstool_status_bzr_inside(self): """Test status output for bzr when run inside workspace""" directory = self.test_root_path + "/ws" cmd = ["wstool", "status"] os.chdir(directory) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() sys.stdout = sys.__stdout__ self.assertEqual('+N clone/added.txt\n D clone/deleted-fs.txt\n-D clone/deleted.txt\n M clone/modified-fs.txt\n M clone/modified.txt\n', output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_status(directory, [])) def test_wstool_status_bzr_outside(self): """Test status output for bzr when run outside workspace""" cmd = ["wstool", "status", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = sys.__stdout__ output = output.getvalue() self.assertEqual('+N clone/added.txt\n D clone/deleted-fs.txt\n-D clone/deleted.txt\n M clone/modified-fs.txt\n M clone/modified.txt\n', output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_status(os.path.join(self.test_root_path, 'ws'), [])) def test_wstool_status_bzr_untracked(self): """Test status output for bzr when run outside workspace""" cmd = ["wstool", "status", "-t", "ws", "--untracked"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = sys.__stdout__ output = output.getvalue() self.assertEqual('? clone/added-fs.txt\n+N clone/added.txt\n D clone/deleted-fs.txt\n-D clone/deleted.txt\n M clone/modified-fs.txt\n M clone/modified.txt\n', output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_status(os.path.join(self.test_root_path, 'ws'), ["--untracked"])) def test_wstool_info_bzr(self): cmd = ["wstool", "info", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'M', 'bzr'], tokens[0:3], output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_info(os.path.join(self.test_root_path, 'ws'), [])) class WstoolInfoBzrTest(AbstractSCMTest): def setUp(self): AbstractSCMTest.setUp(self) remote_path = os.path.join(self.test_root_path, "remote") os.makedirs(remote_path) # create a "remote" repo subprocess.check_call(["bzr", "init"], cwd=remote_path) subprocess.check_call(["touch", "test.txt"], cwd=remote_path) subprocess.check_call(["bzr", "add", "test.txt"], cwd=remote_path) subprocess.check_call(["bzr", "commit", "-m", "modified"], cwd=remote_path) self.version_init = "1" subprocess.check_call(["bzr", "tag", "footag"], cwd=remote_path) subprocess.check_call(["touch", "test2.txt"], cwd=remote_path) subprocess.check_call(["bzr", "add", "test2.txt"], cwd=remote_path) subprocess.check_call(["bzr", "commit", "-m", "modified"], cwd=remote_path) self.version_end = "2" # wstool the remote repo and fake ros _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- bzr: {local-name: clone, uri: ../remote}") cmd = ["wstool", "update"] os.chdir(self.local_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() sys.stdout = sys.__stdout__ def test_rosinstall_detailed_locapath_info(self): cmd = ["wstool", "info", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'bzr', self.version_end, os.path.join(self.test_root_path, 'remote')], tokens) clone_path = os.path.join(self.local_path, "clone") # make local modifications check subprocess.check_call(["rm", "test2.txt"], cwd=clone_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'M', 'bzr', self.version_end, os.path.join(self.test_root_path, 'remote')], tokens) subprocess.check_call(["rm", ".rosinstall"], cwd=self.local_path) _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- bzr: {local-name: clone, uri: ../remote, version: \"footag\"}") os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'MV', 'bzr', 'footag', self.version_end, "(%s)" % self.version_init, os.path.join(self.test_root_path, 'remote')], tokens) subprocess.check_call(["rm", "-rf", "clone"], cwd=self.local_path) os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'x', 'bzr', 'footag', os.path.join(self.test_root_path, 'remote')], tokens) wstool-0.1.13/test/local/test_diff_functions_git.py000066400000000000000000000376211270747036000224670ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import os import sys import subprocess from test.io_wrapper import StringIO import wstool import wstool.helpers import wstool.wstool_cli from wstool.wstool_cli import WstoolCLI from wstool.wstool_cli import wstool_main import test.scm_test_base from test.scm_test_base import AbstractSCMTest, _add_to_file, _nth_line_split def create_git_repo(remote_path): # create a "remote" repo subprocess.check_call(["git", "init"], cwd=remote_path) subprocess.check_call(["touch", "fixed.txt"], cwd=remote_path) subprocess.check_call(["touch", "modified.txt"], cwd=remote_path) subprocess.check_call(["touch", "modified-fs.txt"], cwd=remote_path) subprocess.check_call(["touch", "deleted.txt"], cwd=remote_path) subprocess.check_call(["touch", "deleted-fs.txt"], cwd=remote_path) subprocess.check_call(["git", "add", "*"], cwd=remote_path) subprocess.check_call(["git", "commit", "-m", "modified"], cwd=remote_path) def modify_git_repo(clone_path): # make local modifications subprocess.check_call(["rm", "deleted-fs.txt"], cwd=clone_path) subprocess.check_call(["git", "rm", "deleted.txt"], cwd=clone_path) _add_to_file(os.path.join(clone_path, "modified-fs.txt"), "foo\n") _add_to_file(os.path.join(clone_path, "modified.txt"), "foo\n") subprocess.check_call(["git", "add", "modified.txt"], cwd=clone_path) _add_to_file(os.path.join(clone_path, "added-fs.txt"), "tada\n") _add_to_file(os.path.join(clone_path, "added.txt"), "flam\n") subprocess.check_call(["git", "add", "added.txt"], cwd=clone_path) class WstoolDiffGitTest(AbstractSCMTest): @classmethod def setUpClass(self): AbstractSCMTest.setUpClass() remote_path = os.path.join(self.test_root_path, "remote") os.makedirs(remote_path) create_git_repo(remote_path) # wstool the remote repo and fake ros _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- git: {local-name: clone, uri: ../remote}") cmd = ["wstool", "update", "-t", "ws"] os.chdir(self.test_root_path) wstool_main(cmd) clone_path = os.path.join(self.local_path, "clone") modify_git_repo(clone_path) def check_diff_output(self, output): # sha ids are always same with git self.assertEqual('diff --git clone/added.txt clone/added.txt\nnew file mode 100644\nindex 0000000..8d63207\n--- /dev/null\n+++ clone/added.txt\n@@ -0,0 +1 @@\n+flam\ndiff --git clone/deleted-fs.txt clone/deleted-fs.txt\ndeleted file mode 100644\nindex e69de29..0000000\ndiff --git clone/deleted.txt clone/deleted.txt\ndeleted file mode 100644\nindex e69de29..0000000\ndiff --git clone/modified-fs.txt clone/modified-fs.txt\nindex e69de29..257cc56 100644\n--- clone/modified-fs.txt\n+++ clone/modified-fs.txt\n@@ -0,0 +1 @@\n+foo\ndiff --git clone/modified.txt clone/modified.txt\nindex e69de29..257cc56 100644\n--- clone/modified.txt\n+++ clone/modified.txt\n@@ -0,0 +1 @@\n+foo', output.rstrip()) def test_wstool_diff_git_outside(self): """Test diff output for git when run outside workspace""" cmd = ["wstool", "diff", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = sys.__stdout__ output = output.getvalue() self.check_diff_output(output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_diff(os.path.join(self.test_root_path, 'ws'), [])) def test_wstool_diff_git_inside(self): """Test diff output for git when run inside workspace""" directory = self.test_root_path + "/ws" cmd = ["wstool", "diff"] os.chdir(directory) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() sys.stdout = sys.__stdout__ self.check_diff_output(output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_diff(directory, [])) def test_wstool_status_git_inside(self): """Test status output for git when run inside workspace""" directory = self.test_root_path + "/ws" cmd = ["wstool", "status"] os.chdir(directory) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() sys.stdout = sys.__stdout__ self.assertEqual('A clone/added.txt\n D clone/deleted-fs.txt\nD clone/deleted.txt\n M clone/modified-fs.txt\nM clone/modified.txt\n', output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_diff(directory, [])) def test_Wstool_status_git_outside(self): """Test status output for git when run outside workspace""" cmd = ["wstool", "status", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = sys.__stdout__ output = output.getvalue() self.assertEqual('A clone/added.txt\n D clone/deleted-fs.txt\nD clone/deleted.txt\n M clone/modified-fs.txt\nM clone/modified.txt\n', output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_status(os.path.join(self.test_root_path, 'ws'), [])) def test_Wstool_status_git_untracked(self): """Test untracked status output for git when run outside workspace""" cmd = ["wstool", "status", "-t", "ws", "--untracked"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = sys.__stdout__ output = output.getvalue() self.assertEqual('A clone/added.txt\n D clone/deleted-fs.txt\nD clone/deleted.txt\n M clone/modified-fs.txt\nM clone/modified.txt\n?? clone/added-fs.txt\n', output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_status(os.path.join(self.test_root_path, 'ws'), ["--untracked"])) def test_wstool_info_git(self): cmd = ["wstool", "info", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'M', 'git'], tokens[0:3]) tokens2 = _nth_line_split(-1, output) self.assertEqual(1, len(tokens2)) self.assertEqual('../ros', tokens2[0]) cli = WstoolCLI() self.assertEqual(0, cli.cmd_info(os.path.join(self.test_root_path, 'ws'), [])) class WstoolInfoGitTest(AbstractSCMTest): def setUp(self): AbstractSCMTest.setUp(self) self.remote_path = os.path.join(self.test_root_path, "remote") os.makedirs(self.remote_path) # create a "remote" repo subprocess.check_call(["git", "init"], cwd=self.remote_path) subprocess.check_call(["touch", "test.txt"], cwd=self.remote_path) subprocess.check_call(["git", "add", "*"], cwd=self.remote_path) subprocess.check_call(["git", "commit", "-m", "modified"], cwd=self.remote_path) po = subprocess.Popen(["git", "log", "-n", "1", "--pretty=format:\"%H\""], cwd=self.remote_path, stdout=subprocess.PIPE) self.version_init = po.stdout.read().decode('UTF-8').rstrip('"').lstrip('"')[0:12] subprocess.check_call(["git", "tag", "footag"], cwd=self.remote_path) subprocess.check_call(["touch", "test2.txt"], cwd=self.remote_path) subprocess.check_call(["git", "add", "*"], cwd=self.remote_path) subprocess.check_call(["git", "commit", "-m", "modified"], cwd=self.remote_path) po = subprocess.Popen(["git", "log", "-n", "1", "--pretty=format:\"%H\""], cwd=self.remote_path, stdout=subprocess.PIPE) self.version_end = po.stdout.read().decode('UTF-8').rstrip('"').lstrip('"')[0:12] # wstool the remote repo and fake ros _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- git: {local-name: clone, uri: ../remote}") self.clone_path = os.path.join(self.local_path, "clone") cmd = ["wstool", "update"] os.chdir(self.local_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() sys.stdout = sys.__stdout__ def test_wstool_detailed_localpath_info(self): cmd = ["wstool", "info", "-t", "ws", "--managed-only"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'git', 'master', '(-)', self.version_end, self.remote_path], tokens) # test when remote version is different subprocess.check_call(["git", "reset", "--hard", "HEAD~1"], cwd=self.clone_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'C', 'git', 'master', '(-)', self.version_init, self.remote_path], tokens) # return branch back to original revision subprocess.check_call(["git", "reset", "--hard", self.version_end], cwd=self.clone_path) # make local modifications check subprocess.check_call(["rm", "test2.txt"], cwd=self.clone_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'M', 'git', 'master', '(-)', self.version_end, self.remote_path], tokens) subprocess.check_call(["rm", ".rosinstall"], cwd=self.local_path) _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- git: {local-name: clone, uri: ../remote, version: \"footag\"}") # test when version is different sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'MV', 'git', 'master', '(footag)', self.version_end, "(%s)" % self.version_init, self.remote_path], tokens) # test when tracking branch is different from current branch subprocess.check_call(["git", "checkout", "-b", "test_branch"], cwd=self.clone_path) subprocess.check_call(["git", "config", "--replace-all", "branch.test_branch.remote", "origin"], cwd=self.clone_path) subprocess.check_call(["git", "config", "--replace-all", "branch.test_branch.merge", "master"], cwd=self.clone_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'MV', 'git', 'test_branch', '<', 'master', '(footag)', self.version_end, "(%s)" % self.version_init, self.remote_path], tokens) # test when remote is different from origin by rename subprocess.check_call(["git", "remote", "rename", "origin", "remote2"], cwd=self.clone_path) subprocess.check_call(["git", "config", "--replace-all", "branch.test_branch.remote", "remote2"], cwd=self.clone_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'MV', 'git', 'test_branch', '<', 'remote2/master', '(footag)', self.version_end, "(%s)" % self.version_init, "(%s)" % self.remote_path], tokens) # return remote name to origin subprocess.check_call(["git", "remote", "rename", "remote2", "origin"], cwd=self.clone_path) # test when remote is different from origin, no fetch subprocess.check_call(["git", "remote", "add", "remote2", "../../remote"], cwd=self.clone_path) subprocess.check_call(["git", "config", "--replace-all", "branch.test_branch.remote", "remote2"], cwd=self.clone_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'MV', 'git', 'test_branch', '(footag)', self.version_end, "(%s)" % self.version_init, self.remote_path], tokens) # test when remote is different from origin, with fetch sys.stdout = output = StringIO() wstool_main(cmd + ['--fetch']) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'MV', 'git', 'test_branch', '<', 'remote2/master', '(footag)', self.version_end, "(%s)" % self.version_init, self.remote_path], tokens) # return branch back to master subprocess.check_call(["git", "checkout", "master"], cwd=self.clone_path) # using a denormalized local-name here subprocess.check_call(["rm", ".rosinstall"], cwd=self.local_path) _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- git: {local-name: clone/../clone, uri: ../remote, version: \"footag\"}") sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'MV', 'git', 'master', '(footag)', self.version_end, "(%s)" % self.version_init, self.remote_path], tokens) # using an absolute path to clone dir here subprocess.check_call(["rm", ".rosinstall"], cwd=self.local_path) _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- git: {local-name: '"+self.clone_path+"', uri: ../remote, version: \"footag\"}") sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual([self.clone_path, 'MV', 'git', 'master', '(footag)', self.version_end, "(%s)" % self.version_init, self.remote_path], tokens) # using an absolute path here where relative path is shorter to display (also checks x for missing) subprocess.check_call(["rm", ".rosinstall"], cwd=self.local_path) _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- git: {local-name: '"+os.path.join(self.local_path, "../foo")+"', uri: ../remote, version: \"footag\"}") sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) localname = os.path.join(os.path.dirname(self.local_path), 'foo') self.assertEqual([localname, 'x', 'git', '(footag)', self.remote_path], tokens) wstool-0.1.13/test/local/test_diff_functions_hg.py000066400000000000000000000264361270747036000223040ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import unicode_literals import os import sys from test.io_wrapper import StringIO import subprocess import wstool import wstool.helpers import wstool.wstool_cli from wstool.wstool_cli import WstoolCLI from wstool.wstool_cli import wstool_main import test.scm_test_base from test.scm_test_base import AbstractSCMTest, _add_to_file, _nth_line_split def create_hg_repo(remote_path): # create a "remote" repo subprocess.check_call(["hg", "init"], cwd=remote_path) subprocess.check_call(["touch", "fixed.txt"], cwd=remote_path) subprocess.check_call(["touch", "modified.txt"], cwd=remote_path) subprocess.check_call(["touch", "modified-fs.txt"], cwd=remote_path) subprocess.check_call(["touch", "deleted.txt"], cwd=remote_path) subprocess.check_call(["touch", "deleted-fs.txt"], cwd=remote_path) subprocess.check_call(["hg", "add", "fixed.txt"], cwd=remote_path) subprocess.check_call(["hg", "add", "modified.txt"], cwd=remote_path) subprocess.check_call(["hg", "add", "modified-fs.txt"], cwd=remote_path) subprocess.check_call(["hg", "add", "deleted.txt"], cwd=remote_path) subprocess.check_call(["hg", "add", "deleted-fs.txt"], cwd=remote_path) subprocess.check_call(["hg", "commit", "-m", "modified"], cwd=remote_path) def modify_hg_repo(clone_path): # make local modifications subprocess.check_call(["rm", "deleted-fs.txt"], cwd=clone_path) subprocess.check_call(["hg", "rm", "deleted.txt"], cwd=clone_path) _add_to_file(os.path.join(clone_path, "modified-fs.txt"), "foo\n") _add_to_file(os.path.join(clone_path, "modified.txt"), "foo\n") _add_to_file(os.path.join(clone_path, "added-fs.txt"), "tada\n") _add_to_file(os.path.join(clone_path, "added.txt"), "flam\n") subprocess.check_call(["hg", "add", "added.txt"], cwd=clone_path) class WstoolDiffHgTest(AbstractSCMTest): @classmethod def setUpClass(self): AbstractSCMTest.setUpClass() remote_path = os.path.join(self.test_root_path, "remote") os.makedirs(remote_path) create_hg_repo(remote_path) # wstool the remote repo and fake ros _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- hg: {local-name: clone, uri: ../remote}") cmd = ["wstool", "update", "-t", "ws"] os.chdir(self.test_root_path) wstool_main(cmd) clone_path = os.path.join(self.local_path, "clone") modify_hg_repo(clone_path) def check_diff_output(self, output): # sha ids are always same with hg self.assertEqual('diff --git clone/added.txt clone/added.txt\nnew file mode 100644\n--- /dev/null\n+++ clone/added.txt\n@@ -0,0 +1,1 @@\n+flam\ndiff --git clone/deleted.txt clone/deleted.txt\ndeleted file mode 100644\ndiff --git clone/modified-fs.txt clone/modified-fs.txt\n--- clone/modified-fs.txt\n+++ clone/modified-fs.txt\n@@ -0,0 +1,1 @@\n+foo\ndiff --git clone/modified.txt clone/modified.txt\n--- clone/modified.txt\n+++ clone/modified.txt\n@@ -0,0 +1,1 @@\n+foo\n', output) def test_wstool_diff_hg_outside(self): """Test diff output for hg when run outside workspace""" cmd = ["wstool", "diff", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = sys.__stdout__ output = output.getvalue() self.check_diff_output(output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_diff(os.path.join(self.test_root_path, 'ws'), [])) def test_wstool_diff_hg_inside(self): """Test diff output for hg when run inside workspace""" directory = self.test_root_path + "/ws" cmd = ["wstool", "diff"] os.chdir(directory) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() sys.stdout = sys.__stdout__ self.check_diff_output(output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_status(directory, [])) def test_wstool_status_hg_inside(self): """Test status output for hg when run inside workspace""" directory = self.test_root_path + "/ws" cmd = ["wstool", "status"] os.chdir(directory) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() sys.stdout = sys.__stdout__ self.assertEqual('M clone/modified-fs.txt\nM clone/modified.txt\nA clone/added.txt\nR clone/deleted.txt\n! clone/deleted-fs.txt\n', output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_diff(directory, [])) def test_wstool_status_hg_outside(self): """Test status output for hg when run outside workspace""" cmd = ["wstool", "status", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = sys.__stdout__ output = output.getvalue() self.assertEqual('M clone/modified-fs.txt\nM clone/modified.txt\nA clone/added.txt\nR clone/deleted.txt\n! clone/deleted-fs.txt\n', output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_status(os.path.join(self.test_root_path, 'ws'), [])) def test_wstool_status_hg_untracked(self): """Test untracked status output for hg when run outside workspace""" cmd = ["wstool", "status", "-t", "ws", "--untracked"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = sys.__stdout__ output = output.getvalue() self.assertEqual('M clone/modified-fs.txt\nM clone/modified.txt\nA clone/added.txt\nR clone/deleted.txt\n! clone/deleted-fs.txt\n? clone/added-fs.txt\n', output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_status(os.path.join(self.test_root_path, 'ws'), ["--untracked"])) def test_wstool_info_hg(self): cmd = ["wstool", "info", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'M', 'hg'], tokens[0:3], output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_info(os.path.join(self.test_root_path, 'ws'), [])) class WstoolInfoHgTest(AbstractSCMTest): def setUp(self): AbstractSCMTest.setUp(self) remote_path = os.path.join(self.test_root_path, "remote") os.makedirs(remote_path) # create a "remote" repo subprocess.check_call(["hg", "init"], cwd=remote_path) subprocess.check_call(["touch", "test.txt"], cwd=remote_path) subprocess.check_call(["hg", "add", "test.txt"], cwd=remote_path) subprocess.check_call(["hg", "commit", "-m", "modified"], cwd=remote_path) po = subprocess.Popen(["hg", "log", "--template", "'{node|short}'", "-l1"], cwd=remote_path, stdout=subprocess.PIPE) self.version_init = po.stdout.read().decode('UTF-8').rstrip("'").lstrip("'") subprocess.check_call(["hg", "tag", "footag"], cwd=remote_path) subprocess.check_call(["touch", "test2.txt"], cwd=remote_path) subprocess.check_call(["hg", "add", "test2.txt"], cwd=remote_path) subprocess.check_call(["hg", "commit", "-m", "modified"], cwd=remote_path) po = subprocess.Popen(["hg", "log", "--template", "'{node|short}'", "-l1"], cwd=remote_path, stdout=subprocess.PIPE) self.version_end = po.stdout.read().decode('UTF-8').rstrip("'").lstrip("'") # wstool the remote repo and fake ros _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- hg: {local-name: clone, uri: ../remote}") cmd = ["wstool", "update"] os.chdir(self.local_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() sys.stdout = sys.__stdout__ def test_rosinstall_detailed_locapath_info(self): cmd = ["wstool", "info", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'hg', 'default', '(-)', self.version_end, os.path.join(self.test_root_path, 'remote')], tokens) clone_path = os.path.join(self.local_path, "clone") # make local modifications check subprocess.check_call(["hg", "rm", "test2.txt"], cwd=clone_path) os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'M', 'hg', 'default', '(-)', self.version_end, os.path.join(self.test_root_path, 'remote')], tokens) subprocess.check_call(["rm", ".rosinstall"], cwd=self.local_path) _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- hg: {local-name: clone, uri: ../remote, version: \"footag\"}") os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'MV', 'hg', 'default', '(footag)', self.version_end, "(%s)" % self.version_init, os.path.join(self.test_root_path, 'remote')], tokens) subprocess.check_call(["rm", "-rf", "clone"], cwd=self.local_path) os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'x', 'hg', '(footag)', os.path.join(self.test_root_path, 'remote')], tokens) wstool-0.1.13/test/local/test_diff_functions_multi.py000066400000000000000000000351621270747036000230340ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import unicode_literals import os import sys from test.io_wrapper import StringIO import wstool import wstool.helpers import wstool.wstool_cli from wstool.wstool_cli import WstoolCLI from wstool.wstool_cli import wstool_main from test.scm_test_base import AbstractSCMTest, _add_to_file from test.local.test_diff_functions_svn import create_svn_repo, modify_svn_repo from test.local.test_diff_functions_git import create_git_repo, modify_git_repo from test.local.test_diff_functions_hg import create_hg_repo, modify_hg_repo from test.local.test_diff_functions_bzr import create_bzr_repo, modify_bzr_repo class WstoolDiffMultiTest(AbstractSCMTest): @classmethod def setUpClass(self): AbstractSCMTest.setUpClass() remote_path_svn = os.path.join(self.test_root_path, "remote_svn") remote_path_git = os.path.join(self.test_root_path, "remote_git") remote_path_bzr = os.path.join(self.test_root_path, "remote_bzr") remote_path_hg = os.path.join(self.test_root_path, "remote_hg") os.makedirs(remote_path_git) os.makedirs(remote_path_svn) os.makedirs(remote_path_hg) os.makedirs(remote_path_bzr) filler_path = os.path.join(self.test_root_path, "filler") svn_uri = "file://localhost"+remote_path_svn create_svn_repo(self.test_root_path, remote_path_svn, filler_path, svn_uri) create_git_repo(remote_path_git) create_hg_repo(remote_path_hg) create_bzr_repo(remote_path_bzr) # wstool the remote repo and fake ros (using git twice to check all overlaps) rosinstall_spec = """- other: {local-name: ../ros} - git: {local-name: clone_git, uri: ../remote_git} - svn: {local-name: clone_svn, uri: '%s'} - hg: {local-name: clone_hg, uri: ../remote_hg} - bzr: {local-name: clone_bzr, uri: ../remote_bzr} - git: {local-name: clone_git2, uri: ../remote_git}""" % svn_uri _add_to_file(os.path.join(self.local_path, ".rosinstall"), rosinstall_spec) cmd = ["rosws", "update", "-t", "ws"] os.chdir(self.test_root_path) wstool_main(cmd) clone_path_git = os.path.join(self.local_path, "clone_git") clone_path_git2 = os.path.join(self.local_path, "clone_git2") clone_path_svn = os.path.join(self.local_path, "clone_svn") clone_path_hg = os.path.join(self.local_path, "clone_hg") clone_path_bzr = os.path.join(self.local_path, "clone_bzr") modify_git_repo(clone_path_git2) modify_git_repo(clone_path_git) modify_svn_repo(clone_path_svn) modify_hg_repo(clone_path_hg) modify_bzr_repo(clone_path_bzr) def check_diff_output(self, output): # this tests that there are proper newlines between diff outputs # for svn, the order varies, so we check two known variants self.assertTrue("\nIndex: clone_svn/added.txt" in output, output) self.assertTrue("\nIndex: clone_svn/added.txt" in output, output) self.assertTrue("\nIndex: clone_svn/modified.txt" in output, output) self.assertTrue("\ndiff --git clone_hg/added.txt" in output, output) self.assertTrue("\n=== added file 'added.txt'\n--- clone_bzr/added.txt" in output, output) self.assertTrue("\ndiff --git clone_git2/added.txt" in output, output) def test_multi_diff_rosinstall_outside(self): '''Test wstool diff output from outside workspace. In particular asserts that there are newlines between diffs, and no overlaps''' cmd = ["wstool", "diff", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = sys.__stdout__ output = output.getvalue() self.check_diff_output(output) def test_multi_diff_wstool_outside(self): '''Test wstool diff output from outside workspace. In particular asserts that there are newlines between diffs, and no overlaps''' cmd = ["wstool", "diff", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = sys.__stdout__ output = output.getvalue() self.check_diff_output(output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_diff(os.path.join(self.test_root_path, 'ws'), [])) def test_multi_diff_rosinstall_inside(self): '''Test wstool diff output from inside workspace. In particular asserts that there are newlines between diffs, and no overlaps''' directory = self.test_root_path + "/ws" cmd = ["wstool", "diff"] os.chdir(directory) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() self.check_diff_output(output) def test_multi_diff_wstool_inside(self): '''Test wstool diff output from inside workspace. In particular asserts that there are newlines between diffs, and no overlaps''' directory = self.test_root_path + "/ws" cmd = ["wstool", "diff"] os.chdir(directory) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() sys.stdout = sys.__stdout__ self.check_diff_output(output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_diff(directory, [])) def test_multi_status_rosinstall_inside(self): """Test wstool status output when run inside workspace. In particular asserts that there are newlines between statuses, and no overlaps""" directory = self.test_root_path + "/ws" cmd = ["wstool", "status"] os.chdir(directory) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() self.assertStatusListEqual('A clone_git/added.txt\n D clone_git/deleted-fs.txt\nD clone_git/deleted.txt\n M clone_git/modified-fs.txt\nM clone_git/modified.txt\nA clone_svn/added.txt\nD clone_svn/deleted.txt\n! clone_svn/deleted-fs.txt\nM clone_svn/modified.txt\nM clone_hg/modified-fs.txt\nM clone_hg/modified.txt\nA clone_hg/added.txt\nR clone_hg/deleted.txt\n! clone_hg/deleted-fs.txt\n+N clone_bzr/added.txt\n D clone_bzr/deleted-fs.txt\n-D clone_bzr/deleted.txt\n M clone_bzr/modified-fs.txt\n M clone_bzr/modified.txt\nA clone_git2/added.txt\n D clone_git2/deleted-fs.txt\nD clone_git2/deleted.txt\n M clone_git2/modified-fs.txt\nM clone_git2/modified.txt\n', output) def test_multi_status_wstool_inside(self): """Test wstool status output when run inside workspace. In particular asserts that there are newlines between statuses, and no overlaps""" directory = self.test_root_path + "/ws" cmd = ["wstool", "status"] os.chdir(directory) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() sys.stdout = sys.__stdout__ self.assertStatusListEqual('A clone_git/added.txt\n D clone_git/deleted-fs.txt\nD clone_git/deleted.txt\n M clone_git/modified-fs.txt\nM clone_git/modified.txt\nA clone_svn/added.txt\nD clone_svn/deleted.txt\n! clone_svn/deleted-fs.txt\nM clone_svn/modified.txt\nM clone_hg/modified-fs.txt\nM clone_hg/modified.txt\nA clone_hg/added.txt\nR clone_hg/deleted.txt\n! clone_hg/deleted-fs.txt\n+N clone_bzr/added.txt\n D clone_bzr/deleted-fs.txt\n-D clone_bzr/deleted.txt\n M clone_bzr/modified-fs.txt\n M clone_bzr/modified.txt\nA clone_git2/added.txt\n D clone_git2/deleted-fs.txt\nD clone_git2/deleted.txt\n M clone_git2/modified-fs.txt\nM clone_git2/modified.txt\n', output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_diff(directory, [])) def test_multi_status_rosinstall_outside(self): """Test wstool status output when run outside workspace. In particular asserts that there are newlines between statuses, and no overlaps""" cmd = ["rosinstall", "status", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = sys.__stdout__ output = output.getvalue() self.assertStatusListEqual('A clone_git/added.txt\n D clone_git/deleted-fs.txt\nD clone_git/deleted.txt\n M clone_git/modified-fs.txt\nM clone_git/modified.txt\nA clone_svn/added.txt\nD clone_svn/deleted.txt\n! clone_svn/deleted-fs.txt\nM clone_svn/modified.txt\nM clone_hg/modified-fs.txt\nM clone_hg/modified.txt\nA clone_hg/added.txt\nR clone_hg/deleted.txt\n! clone_hg/deleted-fs.txt\n+N clone_bzr/added.txt\n D clone_bzr/deleted-fs.txt\n-D clone_bzr/deleted.txt\n M clone_bzr/modified-fs.txt\n M clone_bzr/modified.txt\nA clone_git2/added.txt\n D clone_git2/deleted-fs.txt\nD clone_git2/deleted.txt\n M clone_git2/modified-fs.txt\nM clone_git2/modified.txt\n', output) def test_multi_status_wstool_outside(self): """Test wstool status output when run outside workspace. In particular asserts that there are newlines between statuses, and no overlaps""" cmd = ["wstool", "status", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = sys.__stdout__ output = output.getvalue() self.assertStatusListEqual('A clone_git/added.txt\n D clone_git/deleted-fs.txt\nD clone_git/deleted.txt\n M clone_git/modified-fs.txt\nM clone_git/modified.txt\nA clone_svn/added.txt\nD clone_svn/deleted.txt\n! clone_svn/deleted-fs.txt\nM clone_svn/modified.txt\nM clone_hg/modified-fs.txt\nM clone_hg/modified.txt\nA clone_hg/added.txt\nR clone_hg/deleted.txt\n! clone_hg/deleted-fs.txt\n+N clone_bzr/added.txt\n D clone_bzr/deleted-fs.txt\n-D clone_bzr/deleted.txt\n M clone_bzr/modified-fs.txt\n M clone_bzr/modified.txt\nA clone_git2/added.txt\n D clone_git2/deleted-fs.txt\nD clone_git2/deleted.txt\n M clone_git2/modified-fs.txt\nM clone_git2/modified.txt\n', output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_status(os.path.join(self.test_root_path, 'ws'), [])) def test_multi_status_untracked(self): '''tests status output for --untracked. In particular asserts that there are newlines between statuses, and no overlaps''' cmd = ["wstool", "status", "-t", "ws", "--untracked"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = sys.__stdout__ output = output.getvalue() self.assertStatusListEqual('A clone_git/added.txt\n D clone_git/deleted-fs.txt\nD clone_git/deleted.txt\n M clone_git/modified-fs.txt\nM clone_git/modified.txt\n?? clone_git/added-fs.txt\n? clone_svn/added-fs.txt\nA clone_svn/added.txt\nD clone_svn/deleted.txt\n! clone_svn/deleted-fs.txt\nM clone_svn/modified.txt\nM clone_hg/modified-fs.txt\nM clone_hg/modified.txt\nA clone_hg/added.txt\nR clone_hg/deleted.txt\n! clone_hg/deleted-fs.txt\n? clone_hg/added-fs.txt\n? clone_bzr/added-fs.txt\n+N clone_bzr/added.txt\n D clone_bzr/deleted-fs.txt\n-D clone_bzr/deleted.txt\n M clone_bzr/modified-fs.txt\n M clone_bzr/modified.txt\nA clone_git2/added.txt\n D clone_git2/deleted-fs.txt\nD clone_git2/deleted.txt\n M clone_git2/modified-fs.txt\nM clone_git2/modified.txt\n?? clone_git2/added-fs.txt\n', output) cmd = ["wstool", "status", "-t", "ws", "--untracked"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = sys.__stdout__ output = output.getvalue() self.assertStatusListEqual('A clone_git/added.txt\n D clone_git/deleted-fs.txt\nD clone_git/deleted.txt\n M clone_git/modified-fs.txt\nM clone_git/modified.txt\n?? clone_git/added-fs.txt\n? clone_svn/added-fs.txt\nA clone_svn/added.txt\nD clone_svn/deleted.txt\n! clone_svn/deleted-fs.txt\nM clone_svn/modified.txt\nM clone_hg/modified-fs.txt\nM clone_hg/modified.txt\nA clone_hg/added.txt\nR clone_hg/deleted.txt\n! clone_hg/deleted-fs.txt\n? clone_hg/added-fs.txt\n? clone_bzr/added-fs.txt\n+N clone_bzr/added.txt\n D clone_bzr/deleted-fs.txt\n-D clone_bzr/deleted.txt\n M clone_bzr/modified-fs.txt\n M clone_bzr/modified.txt\nA clone_git2/added.txt\n D clone_git2/deleted-fs.txt\nD clone_git2/deleted.txt\n M clone_git2/modified-fs.txt\nM clone_git2/modified.txt\n?? clone_git2/added-fs.txt\n', output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_status(os.path.join(self.test_root_path, 'ws'), ["--untracked"])) wstool-0.1.13/test/local/test_diff_functions_svn.py000066400000000000000000000271471270747036000225140ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import unicode_literals import os import sys from test.io_wrapper import StringIO import subprocess import re import wstool import wstool.helpers import wstool.wstool_cli from wstool.wstool_cli import WstoolCLI from wstool.wstool_cli import wstool_main import test.scm_test_base from test.scm_test_base import AbstractSCMTest, _add_to_file, _nth_line_split def create_svn_repo(test_root_path, remote_path, filler_path, svn_uri): # create a "remote" repo subprocess.check_call(["svnadmin", "create", remote_path], cwd=test_root_path) subprocess.check_call(["svn", "checkout", svn_uri, filler_path], cwd=test_root_path) subprocess.check_call(["touch", "fixed.txt"], cwd=filler_path) subprocess.check_call(["touch", "modified.txt"], cwd=filler_path) subprocess.check_call(["touch", "modified-fs.txt"], cwd=filler_path) subprocess.check_call(["touch", "deleted.txt"], cwd=filler_path) subprocess.check_call(["touch", "deleted-fs.txt"], cwd=filler_path) subprocess.check_call(["svn", "add", "fixed.txt"], cwd=filler_path) subprocess.check_call(["svn", "add", "modified.txt"], cwd=filler_path) subprocess.check_call(["svn", "add", "modified-fs.txt"], cwd=filler_path) subprocess.check_call(["svn", "add", "deleted.txt"], cwd=filler_path) subprocess.check_call(["svn", "add", "deleted-fs.txt"], cwd=filler_path) subprocess.check_call(["svn", "commit", "-m", "modified"], cwd=filler_path) def modify_svn_repo(clone_path): # make local modifications subprocess.check_call(["rm", "deleted-fs.txt"], cwd=clone_path) subprocess.check_call(["svn", "rm", "deleted.txt"], cwd=clone_path) #_add_to_file(os.path.join(clone_path, "modified-fs.txt"), "foo\n") _add_to_file(os.path.join(clone_path, "modified.txt"), "foo\n") _add_to_file(os.path.join(clone_path, "added-fs.txt"), "tada\n") _add_to_file(os.path.join(clone_path, "added.txt"), "flam\n") subprocess.check_call(["svn", "add", "--no-auto-props", "added.txt"], cwd=clone_path) class WstoolDiffSvnTest(AbstractSCMTest): @classmethod def setUpClass(self): AbstractSCMTest.setUpClass() remote_path = os.path.join(self.test_root_path, "remote") filler_path = os.path.join(self.test_root_path, "filler") svn_uri = "file://localhost" + remote_path create_svn_repo(self.test_root_path, remote_path, filler_path, svn_uri) # wstool the remote repo and fake ros _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- svn: {local-name: clone, uri: '" + svn_uri + "'}") cmd = ["wstool", "update", "-t", "ws"] os.chdir(self.test_root_path) wstool_main(cmd) clone_path = os.path.join(self.local_path, "clone") modify_svn_repo(clone_path) def check_diff_output(self, output): # svn 1.9 added the "nonexistent" output, replace it with the # revision 0 that the test results expect. output_fixed = re.sub("\(nonexistent\)", "(revision 0)", output) # svn output order varies between versions expected = ["""\ Index: clone/added.txt =================================================================== --- clone/added.txt\t(revision 0) +++ clone/added.txt\t""", """@@ -0,0 +1 @@ +flam""", """\ Index: clone/modified.txt =================================================================== --- clone/modified.txt\t(revision 1) +++ clone/modified.txt\t(working copy) @@ -0,0 +1 @@ +foo"""] for snippet in expected: for line in snippet.splitlines(): # assertIn is not supported in Python2.6 self.assertTrue(line in output_fixed, output) def test_wstool_diff_svn_outside(self): """Test diff output for svn when run outside workspace""" cmd = ["wstool", "diff", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = sys.__stdout__ output = output.getvalue() self.check_diff_output(output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_diff(os.path.join(self.test_root_path, 'ws'), [])) def test_wstool_diff_svn_inside(self): """Test diff output for svn when run inside workspace""" directory = self.test_root_path + "/ws" cmd = ["wstool", "diff"] os.chdir(directory) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() sys.stdout = sys.__stdout__ self.check_diff_output(output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_status(directory, [])) def test_wstool_status_svn_inside(self): """Test status output for svn when run inside workspace""" directory = self.test_root_path + "/ws" cmd = ["wstool", "status"] os.chdir(directory) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() sys.stdout = sys.__stdout__ self.assertStatusListEqual('A clone/added.txt\nD clone/deleted.txt\n! clone/deleted-fs.txt\nM clone/modified.txt\n', output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_diff(directory, [])) def test_wstool_status_svn_outside(self): """Test status output for svn when run outside workspace""" cmd = ["wstool", "status", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = sys.__stdout__ output = output.getvalue() self.assertStatusListEqual('A clone/added.txt\nD clone/deleted.txt\n! clone/deleted-fs.txt\nM clone/modified.txt\n', output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_status(os.path.join(self.test_root_path, 'ws'), [])) def test_wstool_status_svn_untracked(self): """Test status output for svn when run outside workspace""" cmd = ["wstool", "status", "-t", "ws", "--untracked"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) sys.stdout = sys.__stdout__ output = output.getvalue() self.assertStatusListEqual('? clone/added-fs.txt\nA clone/added.txt\nD clone/deleted.txt\n! clone/deleted-fs.txt\nM clone/modified.txt\n', output) cli = WstoolCLI() self.assertEqual(0, cli.cmd_status(os.path.join(self.test_root_path, 'ws'), ["--untracked"])) def test_wstool_info_svn(self): cmd = ["wstool", "info", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'M', 'svn'], tokens[0:3]) cli = WstoolCLI() self.assertEqual(0, cli.cmd_info(os.path.join(self.test_root_path, 'ws'), [])) class WstoolInfoSvnTest(AbstractSCMTest): def setUp(self): AbstractSCMTest.setUp(self) remote_path = os.path.join(self.test_root_path, "remote") filler_path = os.path.join(self.test_root_path, "filler") self.svn_uri = "file://localhost" + remote_path # create a "remote" repo subprocess.check_call(["svnadmin", "create", remote_path], cwd=self.test_root_path) subprocess.check_call(["svn", "checkout", self.svn_uri, filler_path], cwd=self.test_root_path) subprocess.check_call(["touch", "test.txt"], cwd=filler_path) subprocess.check_call(["svn", "add", "test.txt"], cwd=filler_path) subprocess.check_call(["svn", "commit", "-m", "modified"], cwd=filler_path) subprocess.check_call(["touch", "test2.txt"], cwd=filler_path) subprocess.check_call(["svn", "add", "test2.txt"], cwd=filler_path) subprocess.check_call(["svn", "commit", "-m", "modified"], cwd=filler_path) self.version_init = "-r1" self.version_end = "-r2" # wstool the remote repo and fake ros _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- svn: {local-name: clone, uri: '" + self.svn_uri + "'}") cmd = ["wstool", "update"] os.chdir(self.local_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() sys.stdout = sys.__stdout__ def test_rosinstall_detailed_locapath_info(self): cmd = ["wstool", "info", "-t", "ws"] os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'svn', self.version_end, self.svn_uri], tokens) clone_path = os.path.join(self.local_path, "clone") # make local modifications check subprocess.check_call(["touch", "test3.txt"], cwd=clone_path) subprocess.check_call(["svn", "add", "test3.txt"], cwd=clone_path) os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'M', 'svn', self.version_end, self.svn_uri], tokens) subprocess.check_call(["rm", ".rosinstall"], cwd=self.local_path) _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- svn: {local-name: clone, uri: '" + self.svn_uri + "', version: \"1\"}") os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'MV', 'svn', '1', '(-)', self.version_end, "(%s)" % self.version_init, self.svn_uri], tokens) subprocess.check_call(["rm", "-rf", "clone"], cwd=self.local_path) os.chdir(self.test_root_path) sys.stdout = output = StringIO() wstool_main(cmd) output = output.getvalue() tokens = _nth_line_split(-2, output) self.assertEqual(['clone', 'x', 'svn', '(-)', self.svn_uri], tokens) wstool-0.1.13/test/local/test_interation.py000066400000000000000000000076641270747036000210040ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import os import wstool import wstool.multiproject_cmd import wstool.ui from test.scm_test_base import AbstractFakeRosBasedTest, _create_yaml_file, _create_config_elt_dict class FakeUi(wstool.ui.Ui): def __init__(self, path='', mode='skip', prompt_result='y'): self.path = path self.mode = mode def get_backup_path(self): return path def prompt_del_abort_retry(self, prompt, allow_skip=False): return mode def get_input(self, prompt): return prompt_result class RosinstallInteractive(AbstractFakeRosBasedTest): """tests with possible User Interaction, using mock to simulate user input""" def setUp(self): self.old_ui = wstool.ui.Ui.get_ui() wstool.ui.Ui.set_ui(FakeUi()) def tearDown(self): wstool.ui.Ui.set_ui(self.old_ui) def test_twice_with_relpath(self): """runs wstool with generated self.simple_rosinstall to create local wstool env and creates a directory for a second local wstool env""" AbstractFakeRosBasedTest.setUp(self) self.rel_uri_rosinstall = os.path.join(self.test_root_path, "rel_uri.rosinstall") _create_yaml_file([_create_config_elt_dict("git", "ros", self.ros_path), _create_config_elt_dict("git", "gitrepo", os.path.relpath(self.git_path, self.directory))], self.rel_uri_rosinstall) config = wstool.multiproject_cmd.get_config(self.directory, [self.rel_uri_rosinstall, self.ros_path]) wstool.multiproject_cmd.cmd_info(config) wstool.multiproject_cmd.cmd_find_unmanaged_repos wstool.multiproject_cmd.cmd_install_or_update(config) config = wstool.multiproject_cmd.get_config(self.directory, [self.rel_uri_rosinstall, self.ros_path]) wstool.multiproject_cmd.cmd_install_or_update(config) self.rel_uri_rosinstall2 = os.path.join(self.test_root_path, "rel_uri.wstool2") # switch URIs to confuse config _create_yaml_file([_create_config_elt_dict("git", "ros", os.path.relpath(self.git_path, self.directory)), _create_config_elt_dict("git", "gitrepo", self.ros_path)], self.rel_uri_rosinstall2) config = wstool.multiproject_cmd.get_config(self.directory, [self.rel_uri_rosinstall, self.ros_path]) wstool.multiproject_cmd.cmd_install_or_update(config) wstool-0.1.13/test/local/test_multiproject_functions.py000066400000000000000000000237721270747036000234370ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import os import unittest from wstool.common import DistributedWork, WorkerThread, normabspath,\ is_web_uri, select_elements, select_element, normalize_uri, realpath_relation,\ conditional_abspath, string_diff, MultiProjectException class FooThing: def __init__(self, el, result=None): self.element = el self.done = False self.result = result def do_work(self): self.done = True return self.result def get_path_spec(self): return self.element def get_local_name(self): return 'bar' class MockElement: def __init__(self, localname, path): self.localname = localname self.path = path def get_local_name(self): return self.localname def get_path(self): return self.path class FunctionsTest(unittest.TestCase): def test_normabspath(self): base = "/foo/bar" self.assertEqual("/foo/bar", normabspath('.', base)) self.assertEqual("/foo/bar", normabspath('foo/..', base)) self.assertEqual("/foo/bar", normabspath(base, base)) self.assertEqual("/foo", normabspath("/foo", base)) self.assertEqual("/foo/bar/bim", normabspath('bim', base)) self.assertEqual("/foo", normabspath('..', base)) def test_is_web_uri(self): self.assertTrue(is_web_uri('http://foo.com')) self.assertTrue(is_web_uri('http://foo.com/bar')) self.assertTrue(is_web_uri('http://foo.com:42')) self.assertTrue(is_web_uri('http://foo.com:42/bar')) self.assertTrue(is_web_uri('ssh://foo.com')) self.assertTrue(is_web_uri('lp:foo.com')) self.assertTrue(is_web_uri('git://foo.com')) self.assertTrue(is_web_uri('git+ssh://foo.com:foo')) self.assertTrue(is_web_uri('user@foo:foo/bar')) self.assertFalse(is_web_uri('foo/bar')) self.assertFalse(is_web_uri('bar')) self.assertFalse(is_web_uri('')) self.assertFalse(is_web_uri(None)) def test_normalize_uri(self): self.assertEqual('/foo', normalize_uri('/foo', None)) self.assertEqual(None, normalize_uri(None, None)) self.assertEqual('/bar/foo', normalize_uri('foo', '/bar')) self.assertEqual('http://foo.com', normalize_uri('http://foo.com', None)) def test_string_diff(self): self.assertEqual('', string_diff(None, None)) self.assertEqual('foo', string_diff('foo', 'foo')) self.assertEqual('foo3', string_diff('foo', 'foo3')) self.assertEqual( '...7890foo3', string_diff('12345678901234567890foo', '12345678901234567890foo3')) self.assertEqual( '...7890foo3', string_diff('12345678901234567890foo4', '12345678901234567890foo3')) self.assertEqual( '...7890foo3', string_diff('12345678901234567890foo45', '12345678901234567890foo3')) self.assertEqual( '...4567890foo123456789123456789', string_diff('12345678901234567890', '12345678901234567890foo123456789123456789')) self.assertEqual("['foo']", string_diff(['foo'], ['foo'])) self.assertEqual("['bar']", string_diff(['foo'], ['bar'])) def test_conditional_abspath(self): path = "foo" self.assertEqual(os.path.normpath(os.path.join(os.getcwd(), path)), conditional_abspath(path)) path = "http://someuri.com" self.assertEqual("http://someuri.com", conditional_abspath(path)) def test_abspath_overlap(self): base = "/foo/bar" # simple self.assertEqual('SAME_AS', realpath_relation("/foo", "/foo")) self.assertEqual('SAME_AS', realpath_relation("/", "/")) # denormalized self.assertEqual('SAME_AS', realpath_relation("/foo/.", "/foo/bar/../")) # subdir self.assertEqual('PARENT_OF', realpath_relation("/foo", "/foo/bar/baz/bam")) self.assertEqual('CHILD_OF', realpath_relation("/foo/bar/baz/bam", "/foo")) ## Negatives self.assertEqual(None, realpath_relation("/foo", "/bar")) self.assertEqual(None, realpath_relation("/foo", "/foo2")) self.assertEqual(None, realpath_relation("/foo/bar", "/foo/ba")) self.assertEqual(None, realpath_relation("/foo/ba", "/foo/bar/baz")) self.assertEqual(None, realpath_relation("/foo/bar/baz", "/foo/ba")) def test_select_element(self): self.assertEqual(None, select_element(None, None)) self.assertEqual(None, select_element([], None)) mock1 = MockElement('foo', '/test/path1') mock2 = MockElement('bar', '/test/path2') mock3 = MockElement('baz', '/test/path3') self.assertEqual(None, select_element([], 'pin')) self.assertEqual(None, select_element([mock1], 'pin')) self.assertEqual(None, select_element([mock1, mock3], 'pin')) self.assertEqual('bar', select_element([mock1, mock2, mock3], 'bar').get_local_name()) self.assertEqual('bar', select_element([mock1, mock2, mock3], '/test/path2').get_local_name()) self.assertEqual('bar', select_element([mock1, mock2, mock3], '/test/../foo/../test/path2/').get_local_name()) def test_worker_thread(self): try: w = WorkerThread(None, None, None) self.fail("expected Exception") except MultiProjectException: pass try: w = WorkerThread(FooThing(el=None), 2, 3) self.fail("expected Exception") except MultiProjectException: pass thing = FooThing(FooThing(None)) result = [None] w = WorkerThread(thing, result, 0) self.assertEqual(thing.done, False) w.run() self.assertEqual(thing.done, True, result) self.assertEqual(True, 'error' in result[0]) thing = FooThing(FooThing(None), result={'done': True}) result = [None] w = WorkerThread(thing, result, 0) self.assertEqual(thing.done, False) w.run() self.assertEqual(thing.done, True, result) self.assertEqual(False, 'error' in result[0], result) def test_distributed_work_init(self): work = DistributedWork(capacity=200) self.assertEqual(10, work.num_threads) work = DistributedWork(capacity=3, num_threads=5) self.assertEqual(3, work.num_threads) work = DistributedWork(capacity=5, num_threads=3) self.assertEqual(3, work.num_threads) work = DistributedWork(capacity=3, num_threads=-1) self.assertEqual(3, work.num_threads) def test_distributed_work(self): work = DistributedWork(3) thing1 = FooThing(FooThing(FooThing(None)), result={'done': True}) thing2 = FooThing(FooThing(FooThing(None)), result={'done': True}) thing3 = FooThing(FooThing(FooThing(None)), result={'done': True}) self.assertEqual(3, len(work.outputs)) work.add_thread(thing1) self.assertEqual(1, len(work.threads)) work.add_thread(thing2) self.assertEqual(2, len(work.threads)) work.add_thread(thing3) self.assertEqual(3, len(work.threads)) self.assertEqual(thing1.done, False) self.assertEqual(thing2.done, False) self.assertEqual(thing3.done, False) output = work.run() self.assertEqual(False, 'error' in output[0], output) self.assertEqual(False, 'error' in output[1], output) self.assertEqual(False, 'error' in output[2], output) def test_select_elements(self): self.assertEqual([], select_elements(None, None)) mock1 = MockElement('foo', '/test/path1') mock2 = MockElement('bar', '/test/path2') mock3 = MockElement('baz', '/test/path3') class FakeConfig(): def get_config_elements(self): return [mock1, mock2, mock3] def get_base_path(self): return '/foo/bar' self.assertEqual([mock1, mock2, mock3], select_elements(FakeConfig(), None)) self.assertEqual([mock2], select_elements(FakeConfig(), ['bar'])) self.assertEqual([mock1, mock2, mock3], select_elements(FakeConfig(), ['/foo/bar'])) self.assertRaises(MultiProjectException, select_elements, FakeConfig(), ['bum']) self.assertRaises(MultiProjectException, select_elements, FakeConfig(), ['foo', 'bum', 'bar']) self.assertRaises(MultiProjectException, select_elements, FakeConfig(), ['bu*']) wstool-0.1.13/test/local/test_rosinstall_options.py000066400000000000000000000140001270747036000225530ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import os import copy import tempfile import wstool from wstool.wstool_cli import wstool_main from wstool.common import MultiProjectException from test.scm_test_base import AbstractFakeRosBasedTest, _create_yaml_file, _create_config_elt_dict, _create_git_repo class RosinstallOptionsTest(AbstractFakeRosBasedTest): """Test command line option for failure behavior""" @classmethod def setUpClass(self): AbstractFakeRosBasedTest.setUpClass() # create another repo in git self.git_path2 = os.path.join(self.test_root_path, "gitrepo2") _create_git_repo(self.git_path2) self.simple_changed_uri_rosinstall = os.path.join(self.test_root_path, "simple_changed_uri.rosinstall") # same local name for gitrepo, different uri _create_yaml_file([_create_config_elt_dict("git", "ros", self.ros_path), _create_config_elt_dict("git", "gitrepo", self.git_path2)], self.simple_changed_uri_rosinstall) # create a broken config self.broken_rosinstall = os.path.join(self.test_root_path, "broken.rosinstall") _create_yaml_file([_create_config_elt_dict("other", self.ros_path), _create_config_elt_dict("hg", "hgrepo", self.hg_path + "invalid")], self.broken_rosinstall) def test_Rosinstall_help(self): cmd = copy.copy(self.wstool_fn) cmd.append("-h") self.assertEqual(0, wstool_main(cmd)) def test_rosinstall_delete_changes(self): cmd = copy.copy(self.wstool_fn) cmd.extend(["init", self.directory, self.simple_rosinstall]) self.assertEqual(0, wstool_main(cmd)) cmd = copy.copy(self.wstool_fn) cmd.extend(["merge", "-t", self.directory, self.simple_changed_uri_rosinstall, "--merge-replace", "-y"]) self.assertEqual(0, wstool_main(cmd)) cmd = copy.copy(self.wstool_fn) cmd.extend(["update", "-t", self.directory, "--delete-changed-uri"]) self.assertEqual(0, wstool_main(cmd)) def test_rosinstall_abort_changes(self): cmd = copy.copy(self.wstool_fn) cmd.extend(["init", self.directory, self.simple_rosinstall]) self.assertEqual(0, wstool_main(cmd)) cmd = copy.copy(self.wstool_fn) cmd.extend(["merge", "-t", self.directory, self.simple_changed_uri_rosinstall, "--merge-replace", "-y"]) self.assertEqual(0, wstool_main(cmd)) cmd = copy.copy(self.wstool_fn) cmd.extend(["update", "-t", self.directory, "--abort-changed-uri"]) try: wstool_main(cmd) self.fail("expected exception") except MultiProjectException: pass def test_rosinstall_backup_changes(self): cmd = copy.copy(self.wstool_fn) cmd.extend(["init", self.directory, self.simple_rosinstall]) self.assertEqual(0, wstool_main(cmd)) directory1 = tempfile.mkdtemp() self.directories["backup1"] = directory1 cmd = copy.copy(self.wstool_fn) cmd.extend(["merge", "-t", self.directory, self.simple_changed_uri_rosinstall, "--merge-replace", "-y"]) self.assertEqual(0, wstool_main(cmd)) cmd = copy.copy(self.wstool_fn) cmd.extend(["update", "-t", self.directory, "--backup-changed-uri=%s" % directory1]) self.assertEqual(0, wstool_main(cmd)) self.assertEqual(len(os.listdir(directory1)), 1) def test_rosinstall_change_vcs_type(self): cmd = copy.copy(self.wstool_fn) cmd.extend(["init", self.directory, self.simple_rosinstall]) self.assertEqual(0, wstool_main(cmd)) cmd = copy.copy(self.wstool_fn) cmd.extend(["merge", "-t", self.directory, self.simple_changed_vcs_rosinstall, "--merge-replace", "-y"]) self.assertEqual(0, wstool_main(cmd)) cmd = copy.copy(self.wstool_fn) cmd.extend(["update", "-t", self.directory, "--delete-changed-uri"]) self.assertEqual(0, wstool_main(cmd)) def test_rosinstall_invalid_fail(self): cmd = copy.copy(self.wstool_fn) cmd.extend([self.directory, "init", self.broken_rosinstall]) try: wstool_main(cmd) self.fail("expected exception") except MultiProjectException: pass def test_rosinstall_invalid_continue(self): cmd = copy.copy(self.wstool_fn) cmd.extend(["-t", self.directory, "merge", self.broken_rosinstall, "--continue-on-error"]) self.assertTrue(wstool_main(cmd)) wstool-0.1.13/test/local/test_rosinstall_standalone_functions.py000066400000000000000000000041611270747036000253070ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import os import unittest import subprocess import wstool.helpers from wstool.config import Config from wstool.config_yaml import PathSpec from mock import Mock class FunctionsTest(unittest.TestCase): def test_get_ros_package_path(self): config = Config([PathSpec("foo"), PathSpec("bar")], ".", None) self.assertEqual(list(map(os.path.abspath, ['bar', 'foo'])), wstool.helpers.get_ros_package_path(config)) wstool-0.1.13/test/local/test_tarfile.py000066400000000000000000000027041270747036000202440ustar00rootroot00000000000000import os import copy import yaml import wstool import wstool.helpers from wstool.wstool_cli import wstool_main from test.scm_test_base import AbstractFakeRosBasedTest, _create_yaml_file, _create_config_elt_dict, _create_tar_file class RosinstallTarTest(AbstractFakeRosBasedTest): """Tests for tarfile support""" @classmethod def setUpClass(self): AbstractFakeRosBasedTest.setUpClass() # create another repo in git self.tar_path = os.path.join(self.test_root_path, "tarfile.tar.bz2") _create_tar_file(self.tar_path) self.simple_tar_rosinstall = os.path.join(self.test_root_path, "simple_changed_uri.rosinstall") # same local name for gitrepo, different uri _create_yaml_file([_create_config_elt_dict("tar", "temptar", uri=self.tar_path, version='temptar')], self.simple_tar_rosinstall) def test_install(self): cmd = copy.copy(self.wstool_fn) cmd.extend(["init", self.directory, self.simple_tar_rosinstall]) self.assertEquals(0, wstool_main(cmd)) self.assertTrue(os.path.isdir(os.path.join(self.directory, "temptar"))) self.assertTrue(os.path.isfile(os.path.join(self.directory, ".rosinstall"))) stream = open(os.path.join(self.directory, '.rosinstall'), 'r') yamlsrc = yaml.load(stream) stream.close() self.assertEqual(1, len(yamlsrc)) self.assertEqual('tar', list(yamlsrc[0].keys())[0]) wstool-0.1.13/test/scm_test_base.py000066400000000000000000000257251270747036000173100ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2009, Willow Garage, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of Willow Garage, Inc. nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import unicode_literals import os import copy import unittest import subprocess import tempfile import shutil def _add_to_file(path, content): """Util function to append to file to get a modification""" with open(path, 'ab') as fhand: fhand.write(content.encode('UTF-8')) def _create_fake_ros_dir(root_path): """setup fake ros root within root_path/ros""" ros_path = os.path.join(root_path, "ros") os.makedirs(ros_path) bin_path = os.path.join(ros_path, "bin") os.makedirs(bin_path) subprocess.check_call(["git", "init"], cwd=ros_path) _add_to_file(os.path.join(ros_path, "stack.xml"), '') _add_to_file(os.path.join(ros_path, "setup.sh"), 'export FOO_BAR=`pwd`') _add_to_file(os.path.join(bin_path, "rosmake"), '#!/usr/bin/env sh') _add_to_file(os.path.join(bin_path, "rospack"), '#!/usr/bin/env sh') # even faking rosmake subprocess.check_call(["chmod", "u+x", os.path.join(bin_path, "rosmake")]) subprocess.check_call(["chmod", "u+x", os.path.join(bin_path, "rospack")]) subprocess.check_call(["git", "add", "*"], cwd=ros_path) subprocess.check_call(["git", "commit", "-m", "initial"], cwd=ros_path) def _create_yaml_file(config_elements, path): content = '' for elt in list(config_elements): content += "- %s:\n" % elt["type"] if elt["uri"] is not None: content += " uri: '%s'\n" % elt["uri"] content += " local-name: '%s'\n" % elt["local-name"] if elt["version"] is not None: content += " version: '%s'\n" % elt["version"] _add_to_file(path, content) def _create_config_elt_dict(scmtype, localname, uri=None, version=None): element = {} element["type"] = scmtype element["uri"] = uri element["local-name"] = localname element["version"] = version return element def _create_git_repo(git_path): os.makedirs(git_path) subprocess.check_call(["git", "init"], cwd=git_path) subprocess.check_call(["touch", "gitfixed.txt"], cwd=git_path) subprocess.check_call(["git", "add", "*"], cwd=git_path) subprocess.check_call(["git", "commit", "-m", "initial"], cwd=git_path) def _create_tar_file(tar_file): parent_path = os.path.dirname(tar_file) tar_path = os.path.join(parent_path, 'temptar') os.makedirs(tar_path) subprocess.check_call(["touch", "tarfixed.txt"], cwd=tar_path) subprocess.check_call(["tar", "-czf", os.path.basename(tar_file), 'temptar'], cwd=parent_path) def _create_hg_repo(hg_path): os.makedirs(hg_path) subprocess.check_call(["hg", "init"], cwd=hg_path) subprocess.check_call(["touch", "hgfixed.txt"], cwd=hg_path) subprocess.check_call(["hg", "add", "hgfixed.txt"], cwd=hg_path) subprocess.check_call(["hg", "commit", "-m", "initial"], cwd=hg_path) def _nth_line_split(n, output): """returns the last line as list of non-blank tokens""" lines = output.splitlines() if len(lines) > 0: return lines[n].split() else: return [] # ROSINSTALL_CMD = os.path.join(os.getcwd(), 'scripts/rosinstall') # ROSWS_CMD = os.path.join(os.getcwd(), 'scripts/rosws') class AbstractRosinstallCLITest(unittest.TestCase): """Base class for cli tests""" @classmethod def setUpClass(self): self.new_environ = copy.copy(os.environ) self.new_environ["PYTHONPATH"] = os.path.join(os.getcwd(), "src") if "ROS_WORKSPACE" in self.new_environ: self.new_environ.pop("ROS_WORKSPACE") class AbstractRosinstallBaseDirTest(AbstractRosinstallCLITest): """test class where each test method get its own fresh tempdir named self.directory""" def setUp(self): self.directories = {} self.directory = tempfile.mkdtemp() self.directories["base"] = self.directory self.wstool_fn = ["wstool"] def tearDown(self): for d in self.directories: shutil.rmtree(self.directories[d]) self.directories = {} class AbstractFakeRosBasedTest(AbstractRosinstallBaseDirTest): """ creates some larger infrastructure for testing locally: a root folder containing all other folders in self.test_root_path a fake ros folder in self.ros_path a git repo in self.git_path a hg repo in self.hg_path a file named self.simple_rosinstall with ros and gitrepo a file named self.simple_changed_vcs_rosinstall with ros and hgrepo """ @classmethod def setUpClass(self): AbstractRosinstallBaseDirTest.setUpClass() # create a dir mimicking ros self.test_root_path = os.path.realpath(tempfile.mkdtemp()) _create_fake_ros_dir(self.test_root_path) # create a repo in git self.ros_path = os.path.join(self.test_root_path, "ros") self.git_path = os.path.join(self.test_root_path, "gitrepo") _create_git_repo(self.git_path) # create a repo in hg self.hg_path = os.path.join(self.test_root_path, "hgrepo") _create_hg_repo(self.hg_path) # create custom wstool files to use as input self.simple_rosinstall = os.path.join(self.test_root_path, "simple.rosinstall") _create_yaml_file([_create_config_elt_dict("git", "ros", self.ros_path), _create_config_elt_dict("git", "gitrepo", self.git_path)], self.simple_rosinstall) self.simple_changed_vcs_rosinstall = os.path.join(self.test_root_path, "simple_changed_vcs.rosinstall") _create_yaml_file([_create_config_elt_dict("git", "ros", self.ros_path), _create_config_elt_dict("hg", "hgrepo", self.hg_path)], self.simple_changed_vcs_rosinstall) @classmethod def tearDownClass(self): shutil.rmtree(self.test_root_path) class AbstractSCMTest(AbstractRosinstallCLITest): """Base class for diff tests, setting up a tempdir self.test_root_path for a whole class""" @classmethod def setUpClass(self): """creates a directory 'ros' mimicking to be a ROS root to rosinstall""" AbstractRosinstallCLITest.setUpClass() self.test_root_path = os.path.realpath(tempfile.mkdtemp()) self.directories = {} self.directories["root"] = self.test_root_path _create_fake_ros_dir(self.test_root_path) self.local_path = os.path.join(self.test_root_path, "ws") os.makedirs(self.local_path) self.curdir = os.getcwd() @classmethod def tearDownClass(self): os.chdir(self.curdir) for d in self.directories: shutil.rmtree(self.directories[d]) def assertStatusListEqual(self, listexpect, listactual): """helper fun to check scm status output while discarding file ordering differences""" lines_expect = listexpect.splitlines() lines_actual = listactual.splitlines() for line in lines_expect: self.assertTrue(line in lines_actual, 'Missing entry %s in output %s' % (line, listactual)) for line in lines_actual: self.assertTrue(line in lines_expect, 'Superflous entry %s in output %s' % (line, listactual)) class UtilTest(unittest.TestCase): """test to check the methods run by unit test setups""" def test_add_to_file(self): self.test_root_path = tempfile.mkdtemp() filepath = os.path.join(self.test_root_path, 'foofile') self.assertFalse(os.path.exists(filepath)) _add_to_file(filepath, 'foo') self.assertTrue(os.path.exists(filepath)) with open(filepath, 'r') as f: read_data = f.read() self.assertEqual(read_data, 'foo') _add_to_file(filepath, 'bar') with open(filepath, 'r') as f: read_data = f.read() self.assertEqual(read_data, 'foobar') shutil.rmtree(self.test_root_path) def test_create_fake_ros(self): self.test_root_path = tempfile.mkdtemp() rospath = os.path.join(self.test_root_path, 'ros') self.assertFalse(os.path.exists(rospath)) _create_fake_ros_dir(self.test_root_path) self.assertTrue(os.path.exists(rospath)) self.assertTrue(os.path.exists(os.path.join(rospath, "setup.sh"))) self.assertTrue(os.path.exists(os.path.join(rospath, "stack.xml"))) self.assertTrue(os.path.exists(os.path.join(rospath, ".git"))) shutil.rmtree(self.test_root_path) def test_create_config_elt_dict(self): scmtype = 'foo' uri = 'bar' localname = 'pip' version = 'pop' element = _create_config_elt_dict(scmtype, localname, uri, version) self.assertEqual(element["type"], scmtype) self.assertEqual(element["uri"], uri) self.assertEqual(element["local-name"], localname) self.assertEqual(element["version"], version) def test_create_yaml_file(self): self.test_root_path = tempfile.mkdtemp() filepath = os.path.join(self.test_root_path, 'foofile') config_elements = [ _create_config_elt_dict("other", "foo"), _create_config_elt_dict("git", "foo", "foouri"), _create_config_elt_dict("svn", "bar", "baruri", "barversion")] _create_yaml_file(config_elements, filepath) with open(filepath, 'r') as f: read_data = f.read() self.assertEqual(read_data, """- other: local-name: 'foo' - git: uri: 'foouri' local-name: 'foo' - svn: uri: 'baruri' local-name: 'bar' version: 'barversion' """) shutil.rmtree(self.test_root_path)