pax_global_header00006660000000000000000000000064123736175340014525gustar00rootroot0000000000000052 comment=cac06cf131c3d0c30e71c69447337553efa3fe16 setuptools-git-1.1/000077500000000000000000000000001237361753400143705ustar00rootroot00000000000000setuptools-git-1.1/.gitignore000066400000000000000000000000621237361753400163560ustar00rootroot00000000000000*.pyc *.pyo /dist /build /ez_setup.py /*.egg-info setuptools-git-1.1/AUTHORS.txt000066400000000000000000000007241237361753400162610ustar00rootroot00000000000000Stefan H. Holek Support for Python 3.3 Support for OS X and Windows Chris McDonough Support for non-ASCII filenames Wichert Akkerman Handle in-git symlinks, small code cleanups Current maintainer Vinay Sajip : Python 3 support Humberto Diogenes : Modernization and portability Yannick Gingras : Original implementation setuptools-git-1.1/LICENSE.txt000066400000000000000000000027331237361753400162200ustar00rootroot00000000000000Copyright (c) The Regents of the University of California. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. 3. Neither the name of the University 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 REGENTS 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 REGENTS 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. setuptools-git-1.1/README.rst000066400000000000000000000100731237361753400160600ustar00rootroot00000000000000About ----- This is a plugin for setuptools that enables git integration. Once installed, Setuptools can be told to include in a package distribution all the files tracked by git. This is an alternative to explicit inclusion specifications with ``MANIFEST.in``. A package distribution here refers to a package that you create using setup.py, ex:: python setup.py sdist python setup.py bdist_rpm python setup.py bdist_egg This package was formerly known as gitlsfiles. The name change is the result of an effort by the setuptools plugin developers to provide a uniform naming convention. Installation ------------ With easy_install:: easy_install setuptools_git Alternative manual installation:: tar -zxvf setuptools_git-X.Y.Z.tar.gz cd setuptools_git-X.Y.Z python setup.py install Where X.Y.Z is a version number. Usage ----- To activate this plugin, you must first package your python module with ``setup.py`` and use setuptools. The former is well documented in the `distutils manual `_. To use setuptools instead of distutils, just edit ``setup.py`` and change:: from distutils.core import setup to:: from setuptools import setup, find_packages When Setuptools builds a source package, it always includes all files tracked by your revision control system, if it knows how to learn what those files are. When Setuptools builds a binary package, you can ask it to include all files tracked by your revision control system, by adding these argument to your invocation of `setup()`:: setup(..., packages=find_packages(), include_package_data=True, ...) which will detect that a directory is a package if it contains a ``__init__.py`` file. Alternatively, you can do without ``__init__.py`` files and tell Setuptools explicitly which packages to process:: setup(..., packages=["a_package", "another_one"], include_package_data=True, ...) This plugin lets setuptools know what files are tracked by your git revision control tool. Setuptools ships with support for cvs and subversion. Other plugins like this one are available for bzr, darcs, monotone, mercurial, and many others. It might happen that you track files with your revision control system that you don't want to include in your packages. In that case, you can prevent setuptools from packaging those files with a directive in your `MANIFEST.in`, ex:: exclude .gitignore recursive-exclude images *.xcf *.blend In this example, we prevent setuptools from packaging `.gitignore` and the Gimp and Blender source files found under the `images` directory. Files to exclude from the package can also be listed in the `setup()` directive. To do the same as the MANIFEST.in above, do:: setup(..., exclude_package_data = {'': ['.gitignore'], 'images': ['*.xcf', '*.blend']}, ...) Here is another example:: setup(..., exclude_package_data = {'': ['.gitignore', 'artwork/*'], 'model': ['config.py']}, ...) Gotchas ------- Be aware that for this module to work properly, git and the git meta-data must be available. That means that if someone tries to make a package distribution out of a non-git distribution of yours, say a tarball, setuptools will lack the information necessary to know which files to include. A similar problem will happen if someone clones your git repository but does not install this plugin. Resolving those problems is out of the scope of this plugin; you should add relevant warnings to your documentation if those situations are a concern to you. You can make sure that anyone who clones your git repository and uses your setup.py file has this plugin by adding a `setup_requires` argument:: setup(..., setup_requires = [ "setuptools_git >= 0.3", ], ...) References ---------- * `How to distribute Python modules with Distutils `_ * `Setuptools complete manual `_ Thanks to Zooko O'Whielacronx for many improvements to the documentation. setuptools-git-1.1/TODO.txt000066400000000000000000000013501237361753400156750ustar00rootroot00000000000000TODO ---- * Git thinks that filenames are bytestreams, not character sequences. http://article.gmane.org/gmane.comp.version-control.git/122860 * Git can not work across different encodings, and admits as much. Then again Subversion can't either. * Our only hope is to ask Git for bytestreams and apply some heuristics as to the encoding and composition used. * ATM, setuptools-git returns files by their OS path, not the POSIX path that is returned by Git. It also resolves symbolic links within the same repository. * We might get away with passing along the bytestreams in Python 2, but in Python 3 we must return Unicode. * For passing through non-UTF-8 filenames under Python 3, use the 'surrogateescape' error handler. setuptools-git-1.1/setup.py000066400000000000000000000025011237361753400161000ustar00rootroot00000000000000from setuptools import setup, find_packages version = '1.1' setup( name="setuptools-git", version=version, maintainer='Wichert Akkerman', maintainer_email='wichert@wiggy.net', author="Yannick Gingras", author_email="ygingras@ygingras.net", url="https://github.com/wichert/setuptools-git", keywords='distutils setuptools git', description="Setuptools revision control system plugin for Git", long_description=open('README.rst').read(), license='BSD', packages=find_packages(), test_suite='setuptools_git', zip_safe=True, classifiers=[ "Topic :: Software Development :: Version Control", "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.4", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.1", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", ], entry_points=""" [setuptools.file_finders] git=setuptools_git:listfiles """ ) setuptools-git-1.1/setuptools_git/000077500000000000000000000000001237361753400174545ustar00rootroot00000000000000setuptools-git-1.1/setuptools_git/__init__.py000066400000000000000000000060751237361753400215750ustar00rootroot00000000000000""" A hook into setuptools for Git. """ import sys import os import posixpath from os.path import realpath, join from subprocess import PIPE from setuptools_git.utils import check_output from setuptools_git.utils import b from setuptools_git.utils import posix from setuptools_git.utils import fsdecode from setuptools_git.utils import hfs_quote from setuptools_git.utils import compose from setuptools_git.utils import decompose from setuptools_git.utils import CalledProcessError def ntfsdecode(path): # We receive the raw bytes from Git and must decode by hand if sys.version_info >= (3,): try: path = path.decode('utf-8') except UnicodeDecodeError: path = path.decode(sys.getfilesystemencoding()) else: try: path = path.decode('utf-8').encode(sys.getfilesystemencoding()) except UnicodeError: pass # Already in filesystem encoding (hopefully) return path def gitlsfiles(dirname=''): # NB: Passing the '-z' option to 'git ls-files' below returns the # output as a blob of null-terminated filenames without canonical- # ization or use of double-quoting. # # So we'll get back e.g.: # # 'pyramid/tests/fixtures/static/h\xc3\xa9h\xc3\xa9.html' # # instead of: # # '"pyramid/tests/fixtures/static/h\\303\\251h\\303\\251.html"' # # for each file. res = set() try: topdir = check_output( ['git', 'rev-parse', '--show-toplevel'], cwd=dirname or None, stderr=PIPE).strip() if sys.platform == 'win32': cwd = ntfsdecode(topdir) else: cwd = topdir filenames = check_output( ['git', 'ls-files', '-z'], cwd=cwd, stderr=PIPE) except (CalledProcessError, OSError): # Setuptools mandates we fail silently return res for filename in filenames.split(b('\x00')): if filename: filename = posixpath.join(topdir, filename) if sys.platform == 'darwin': filename = hfs_quote(filename) if sys.platform == 'win32': filename = ntfsdecode(filename) else: filename = fsdecode(filename) if sys.platform == 'darwin': filename = decompose(filename) res.add(filename) return res def listfiles(dirname=''): git_files = gitlsfiles(dirname) cwd = realpath(dirname or os.curdir) prefix_length = len(cwd) + 1 if sys.version_info >= (2, 6): walker = os.walk(cwd, followlinks=True) else: walker = os.walk(cwd) for root, dirs, files in walker: for file in files: filename = join(root, file) if posix(realpath(filename)) in git_files: yield filename[prefix_length:] if __name__ == '__main__': if len(sys.argv) > 1: dirname = sys.argv[1] else: dirname = '' for filename in listfiles(dirname): try: print(compose(filename)) except UnicodeEncodeError: print(repr(filename)[1:-1]) setuptools-git-1.1/setuptools_git/tests.py000066400000000000000000000247331237361753400212010ustar00rootroot00000000000000# -*- coding: utf-8 -*- import sys import os import tempfile import unittest from os.path import realpath, join from setuptools_git.utils import rmtree from setuptools_git.utils import posix from setuptools_git.utils import fsdecode from setuptools_git.utils import hfs_quote from setuptools_git.utils import decompose class GitTestCase(unittest.TestCase): def setUp(self): self.old_cwd = os.getcwd() self.directory = self.new_repo() def tearDown(self): os.chdir(self.old_cwd) rmtree(self.directory) def new_repo(self): from setuptools_git.utils import check_call directory = realpath(tempfile.mkdtemp()) os.chdir(directory) check_call(['git', 'init', '--quiet', os.curdir]) return directory def create_file(self, *path): fd = open(join(*path), 'wt') fd.write('dummy\n') fd.close() def create_dir(self, *path): os.makedirs(join(*path)) def create_git_file(self, *path): from setuptools_git.utils import check_call filename = join(*path) fd = open(filename, 'wt') fd.write('dummy\n') fd.close() check_call(['git', 'add', filename]) check_call(['git', 'commit', '--quiet', '-m', 'add new file']) class gitlsfiles_tests(GitTestCase): def gitlsfiles(self, *a, **kw): from setuptools_git import gitlsfiles return gitlsfiles(*a, **kw) def test_at_repo_root(self): self.create_git_file('root.txt') self.assertEqual( set(self.gitlsfiles(self.directory)), set([posix(realpath('root.txt'))])) def test_at_repo_root_with_subdir(self): self.create_git_file('root.txt') os.mkdir(join(self.directory, 'subdir')) self.create_git_file('subdir', 'entry.txt') self.assertEqual( set(self.gitlsfiles(self.directory)), set([posix(realpath('root.txt')), posix(realpath('subdir/entry.txt'))])) def test_at_repo_subdir(self): self.create_git_file('root.txt') os.mkdir(join(self.directory, 'subdir')) self.create_git_file('subdir', 'entry.txt') self.assertEqual( set(self.gitlsfiles(join(self.directory, 'subdir'))), set([posix(realpath('root.txt')), posix(realpath('subdir/entry.txt'))])) def test_nonascii_filename(self): filename = 'héhé.html' # HFS Plus uses decomposed UTF-8 if sys.platform == 'darwin': filename = decompose(filename) self.create_git_file(filename) self.assertEqual( [fn for fn in os.listdir(self.directory) if fn[0] != '.'], [filename]) self.assertEqual( set(self.gitlsfiles(self.directory)), set([posix(realpath(filename))])) def test_utf8_filename(self): if sys.version_info >= (3,): filename = 'héhé.html'.encode('utf-8') else: filename = 'héhé.html' # HFS Plus uses decomposed UTF-8 if sys.platform == 'darwin': filename = decompose(filename) # Windows does not like byte filenames under Python 3 if sys.platform == 'win32' and sys.version_info >= (3,): filename = filename.decode('utf-8') self.create_git_file(filename) self.assertEqual( [fn for fn in os.listdir(self.directory) if fn[0] != '.'], [fsdecode(filename)]) self.assertEqual( set(self.gitlsfiles(self.directory)), set([posix(realpath(fsdecode(filename)))])) def test_latin1_filename(self): if sys.version_info >= (3,): filename = 'héhé.html'.encode('latin-1') else: filename = 'h\xe9h\xe9.html' # HFS Plus quotes unknown bytes if sys.platform == 'darwin': filename = hfs_quote(filename) # Windows does not like byte filenames under Python 3 if sys.platform == 'win32' and sys.version_info >= (3,): filename = filename.decode('latin-1') self.create_git_file(filename) self.assertEqual( [fn for fn in os.listdir(self.directory) if fn[0] != '.'], [fsdecode(filename)]) self.assertEqual( set(self.gitlsfiles(self.directory)), set([posix(realpath(fsdecode(filename)))])) def test_empty_repo(self): self.assertEqual( [fn for fn in os.listdir(self.directory) if fn[0] != '.'], []) self.assertEqual( set(self.gitlsfiles(self.directory)), set([])) def test_empty_dirname(self): self.create_git_file('root.txt') self.assertEqual( set(self.gitlsfiles()), set([posix(realpath('root.txt'))])) def test_directory_only_contains_another_directory(self): self.create_dir('foo/bar') self.create_git_file('foo/bar/root.txt') self.assertEqual( set(self.gitlsfiles()), set([posix(realpath(join('foo', 'bar', 'root.txt')))]) ) def test_empty_dirname_in_subdir(self): self.create_git_file('root.txt') os.mkdir(join(self.directory, 'subdir')) self.create_git_file('subdir', 'entry.txt') os.chdir(join(self.directory, 'subdir')) self.assertEqual( set(self.gitlsfiles()), set([posix(realpath('../root.txt')), posix(realpath('../subdir/entry.txt'))])) def test_git_error(self): import setuptools_git from setuptools_git.utils import CalledProcessError def do_raise(*args, **kw): raise CalledProcessError(1, 'git') self.create_git_file('root.txt') saved = setuptools_git.check_output setuptools_git.check_output = do_raise try: self.assertEqual(self.gitlsfiles(), set()) finally: setuptools_git.check_output = saved class listfiles_tests(GitTestCase): def listfiles(self, *a, **kw): from setuptools_git import listfiles return listfiles(*a, **kw) def test_at_repo_root(self): self.create_git_file('root.txt') self.assertEqual( set(self.listfiles(self.directory)), set(['root.txt'])) def test_at_repo_root_with_subdir(self): self.create_git_file('root.txt') os.mkdir(join(self.directory, 'subdir')) self.create_git_file('subdir', 'entry.txt') self.assertEqual( set(self.listfiles(self.directory)), set(['root.txt', join('subdir', 'entry.txt')])) def test_at_repo_subdir(self): self.create_git_file('root.txt') os.mkdir(join(self.directory, 'subdir')) self.create_git_file('subdir', 'entry.txt') self.assertEqual( set(self.listfiles(join(self.directory, 'subdir'))), set(['entry.txt'])) def test_nonascii_filename(self): filename = 'héhé.html' # HFS Plus uses decomposed UTF-8 if sys.platform == 'darwin': filename = decompose(filename) self.create_git_file(filename) self.assertEqual( [fn for fn in os.listdir(self.directory) if fn[0] != '.'], [filename]) self.assertEqual( set(self.listfiles(self.directory)), set([filename])) def test_utf8_filename(self): if sys.version_info >= (3,): filename = 'héhé.html'.encode('utf-8') else: filename = 'héhé.html' # HFS Plus uses decomposed UTF-8 if sys.platform == 'darwin': filename = decompose(filename) # Windows does not like byte filenames under Python 3 if sys.platform == 'win32' and sys.version_info >= (3,): filename = filename.decode('utf-8') self.create_git_file(filename) self.assertEqual( [fn for fn in os.listdir(self.directory) if fn[0] != '.'], [fsdecode(filename)]) self.assertEqual( set(self.listfiles(self.directory)), set([fsdecode(filename)])) def test_latin1_filename(self): if sys.version_info >= (3,): filename = 'héhé.html'.encode('latin-1') else: filename = 'h\xe9h\xe9.html' # HFS Plus quotes unknown bytes if sys.platform == 'darwin': filename = hfs_quote(filename) # Windows does not like byte filenames under Python 3 if sys.platform == 'win32' and sys.version_info >= (3,): filename = filename.decode('latin-1') self.create_git_file(filename) self.assertEqual( [fn for fn in os.listdir(self.directory) if fn[0] != '.'], [fsdecode(filename)]) self.assertEqual( set(self.listfiles(self.directory)), set([fsdecode(filename)])) def test_empty_repo(self): self.assertEqual( [fn for fn in os.listdir(self.directory) if fn[0] != '.'], []) self.assertEqual( set(self.listfiles(self.directory)), set([])) def test_empty_dirname(self): self.create_git_file('root.txt') self.assertEqual( set(self.listfiles()), set(['root.txt'])) def test_directory_only_contains_another_directory(self): self.create_dir('foo/bar') self.create_git_file('foo/bar/root.txt') self.assertEqual( set(self.listfiles()), set([join('foo', 'bar', 'root.txt')]) ) def test_empty_dirname_in_subdir(self): self.create_git_file('root.txt') os.mkdir(join(self.directory, 'subdir')) self.create_git_file('subdir', 'entry.txt') os.chdir(join(self.directory, 'subdir')) self.assertEqual( set(self.listfiles()), set(['entry.txt'])) def test_git_error(self): import setuptools_git from setuptools_git.utils import CalledProcessError def do_raise(*args, **kw): raise CalledProcessError(1, 'git') self.create_git_file('root.txt') saved = setuptools_git.check_output setuptools_git.check_output = do_raise try: for filename in self.listfiles(): self.fail('unexpected results') finally: setuptools_git.check_output = saved setuptools-git-1.1/setuptools_git/utils.py000066400000000000000000000100501237361753400211620ustar00rootroot00000000000000import sys import os import stat import shutil import unicodedata import posixpath if sys.version_info >= (3,): from urllib.parse import quote as url_quote unicode = str else: from urllib import quote as url_quote __all__ = ['check_call', 'check_output', 'rmtree', 'b', 'posix', 'fsdecode', 'hfs_quote', 'compose', 'decompose'] try: from subprocess import CalledProcessError except ImportError: # BBB for Python < 2.5 class CalledProcessError(Exception): """ This exception is raised when a process run by check_call() or check_output() returns a non-zero exit status. The exit status will be stored in the returncode attribute; check_output() will also store the output in the output attribute. """ def __init__(self, returncode, cmd, output=None): self.returncode = returncode self.cmd = cmd self.output = output def __str__(self): return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) try: from subprocess import check_call except ImportError: # BBB for Python < 2.5 def check_call(*popenargs, **kwargs): from subprocess import call retcode = call(*popenargs, **kwargs) cmd = kwargs.get("args") if cmd is None: cmd = popenargs[0] if retcode: raise CalledProcessError(retcode, cmd) return retcode try: from subprocess import check_output except ImportError: # BBB for Python < 2.7 def check_output(*popenargs, **kwargs): from subprocess import PIPE from subprocess import Popen if 'stdout' in kwargs: raise ValueError( 'stdout argument not allowed, it will be overridden.') process = Popen(stdout=PIPE, *popenargs, **kwargs) output, unused_err = process.communicate() retcode = process.poll() if retcode: cmd = kwargs.get("args") if cmd is None: cmd = popenargs[0] raise CalledProcessError(retcode, cmd) return output # Windows cannot delete read-only Git objects def rmtree(path): if sys.platform == 'win32': def onerror(func, path, excinfo): os.chmod(path, stat.S_IWRITE) func(path) shutil.rmtree(path, False, onerror) else: shutil.rmtree(path, False) # Fake byte literals for Python < 2.6 def b(s, encoding='utf-8'): if sys.version_info >= (3,): return s.encode(encoding) return s # Convert path to POSIX path on Windows def posix(path): if sys.platform == 'win32': return path.replace(os.sep, posixpath.sep) return path # Decode path from fs encoding under Python 3 def fsdecode(path): if sys.version_info >= (3,): if not isinstance(path, str): if sys.platform == 'win32': errors = 'strict' else: errors = 'surrogateescape' return path.decode(sys.getfilesystemencoding(), errors) return path # HFS Plus quotes unknown bytes like so: %F6 def hfs_quote(path): if isinstance(path, unicode): raise TypeError('bytes are required') try: path.decode('utf-8') except UnicodeDecodeError: path = url_quote(path) # Not UTF-8 if sys.version_info >= (3,): path = path.encode('ascii') return path # HFS Plus uses decomposed UTF-8 def compose(path): if isinstance(path, unicode): return unicodedata.normalize('NFC', path) try: path = path.decode('utf-8') path = unicodedata.normalize('NFC', path) path = path.encode('utf-8') except UnicodeError: pass # Not UTF-8 return path # HFS Plus uses decomposed UTF-8 def decompose(path): if isinstance(path, unicode): return unicodedata.normalize('NFD', path) try: path = path.decode('utf-8') path = unicodedata.normalize('NFD', path) path = path.encode('utf-8') except UnicodeError: pass # Not UTF-8 return path