Permalink
Browse files

Merge branch 'release/1.0.1'

  • Loading branch information...
2 parents 6b6f494 + 7431240 commit b1f2a9fddaa407cfbee6f249a52129e370800bd1 @jezdez jezdez committed Apr 30, 2011
View
@@ -47,7 +47,7 @@
# built documents.
#
# The short X.Y version.
-release = "1.0"
+release = "1.0.1"
version = '.'.join(release.split('.')[:2])
# The language for content autogenerated by Sphinx. Refer to documentation
View
@@ -7,6 +7,14 @@ All kinds of contributions are welcome - code, tests, documentation,
bug reports, ideas, etc.
+Release Schedule
+================
+
+Minor releases of pip (e.g. 1.1, 1.2, 1.3...) occur every four months
+(beginning with the release of pip 1.0 on April 4, 2011). Two weeks before a
+scheduled release, a new branch ``release/X.Y`` is created for release testing
+and preparation. This branch is only open to bugfixes.
+
.. _contributing-with-code:
Contributing with Code
@@ -23,27 +31,44 @@ Log in to Github, go to the `pip repository page
to copy the repository and then clone your fork, like::
$ git clone https://github.com/YOU_USER_NAME/pip
-
+
Now you can change whatever you want, commit, push to your fork and when your
contribution is done, follow the **pull request** link and send us a request
explaining what you did and why.
+Branches
+--------
+
+Pip uses the `git-flow`_ branching model. The default branch on GitHub is
+``develop``, and all development work (new features and bugfixes) should happen
+in that branch. The ``master`` branch is stable, and reflects the last released
+state.
+
+.. _git-flow: http://nvie.com/posts/a-successful-git-branching-model/
All tests should pass
---------------------
Almost all changes to pip should be accompanied by automated tests -
especially ones adding new behavior.
+
`Nose`_ is used to find and run all tests. Take a look at :doc:`running-tests`
to see what you need and how you should run the tests.
Before sending us a pull request, please, be sure all tests pass.
+Supported Python versions
+-------------------------
+
+Pip supports Python versions 2.4, 2.5, 2.6, 2.7, 3.1, and 3.2, from a single
+codebase (without use of 2to3 translation). Untested contributions frequently
+break Python 2.4 or 3.x compatibility. Please run the tests on at least 2.4 and
+3.2 and report your results when sending a pull request.
-Using a Continuous Integration server
--------------------------------------
+Continuous Integration server
+-----------------------------
-We have a continuous integration server running all pip related tests at
+We have a continuous integration server running all pip related tests at
http://ci.cloudsilverlining.org/view/pip. But if you want to have your own,
you can learn how to set up a Hudson CI server like that in the
:doc:`ci-server-step-by-step` page.
View
@@ -1,8 +1,24 @@
News / Changelog
================
-1.0
----
+Next release (1.1) schedule
+---------------------------
+
+Beta release mid-July 2011, final release early August.
+
+1.0.1 (2011-04-30)
+------------------
+
+* Start to use git-flow.
+* Fixed issue #274 - `find_command` should not raise AttributeError
+* Fixed issue #273 - respect Content-Disposition header. Thanks Bradley Ayers.
+* Fixed issue #233 - pathext handling on Windows.
+* Fixed issue #252 - svn+svn protocol.
+* Fixed issue #44 - multiple CLI searches.
+* Fixed issue #266 - current working directory when running setup.py clean.
+
+1.0 (2011-04-04)
+----------------
* Added Python 3 support! Huge thanks to Vinay Sajip, Vitaly Babiy, Kelsey
Hightower, and Alex Gronholm, among others.
@@ -16,7 +32,7 @@ News / Changelog
python-setuptools package (workaround until fixed in Debian and Ubuntu).
* Added `get-pip.py <https://github.com/pypa/pip/raw/master/contrib/get-pip.py>`_
- installer. Simple download and execute it, using the Python interpreter of
+ installer. Simply download and execute it, using the Python interpreter of
your choice::
$ curl -O https://github.com/pypa/pip/raw/master/contrib/get-pip.py
@@ -24,6 +24,15 @@ The ``#egg=MyProject`` part is important, because while you can
install simply given the svn location, the project name is useful in
other places.
+You can also specify the egg name for a non-editable url. This is useful to
+point to HEAD locations on the local filesystem:
+
+ file:///path/to/your/lib/project#egg=MyProject
+
+or relative paths:
+
+ file:../../lib/project#egg=MyProject
+
If you need to give pip (and by association easy_install) hints
about where to find a package, you can use the ``-f``
(``--find-links``) option, like::
@@ -48,9 +57,10 @@ Right now pip knows of the following major version control systems:
Subversion
~~~~~~~~~~
-Pip supports the URL schemes ``svn``, ``svn+http``, ``svn+https``, ``svn+ssh``.
+Pip supports the URL schemes ``svn``, ``svn+svn``, ``svn+http``, ``svn+https``, ``svn+ssh``.
You can also give specific revisions to an SVN URL, like::
+ -e svn+svn://svn.myproject.org/svn/MyProject#egg=MyProject
-e svn+http://svn.myproject.org/svn/MyProject/trunk@2019#egg=MyProject
which will check out revision 2019. ``@{20080101}`` would also check
View
@@ -134,6 +134,10 @@ def main(self, complete_args, args, initial_options):
logger.fatal(str(e))
logger.info('Exception information:\n%s' % format_exc())
exit = 1
+ except KeyboardInterrupt:
+ logger.fatal('Operation cancelled by user')
+ logger.info('Exception information:\n%s' % format_exc())
+ exit = 1
except:
logger.fatal('Exception:\n%s' % format_exc())
exit = 2
View
@@ -27,7 +27,7 @@ def run(self, options, args):
if not args:
logger.warn('ERROR: Missing required argument (search query).')
return
- query = ' '.join(args)
+ query = args
index_url = options.index
pypi_hits = self.search(query, index_url)
View
@@ -1,9 +1,10 @@
-import re
+import cgi
import getpass
-import sys
-import os
import mimetypes
+import os
+import re
import shutil
+import sys
import tempfile
from pip.backwardcompat import (md5, copytree, xmlrpclib, urllib, urllib2,
urlparse, string_types, HTTPError)
@@ -330,7 +331,7 @@ def _check_md5(download_hash, link):
def _get_md5_from_file(target_file, link):
download_hash = md5()
fp = open(target_file, 'rb')
- while 1:
+ while True:
chunk = fp.read(4096)
if not chunk:
break
@@ -362,7 +363,7 @@ def _download_url(resp, link, temp_location):
logger.notify('Downloading %s' % show_url)
logger.debug('Downloading from URL %s' % link)
- while 1:
+ while True:
chunk = resp.read(4096)
if not chunk:
break
@@ -416,7 +417,7 @@ def unpack_http_url(link, location, download_cache, only_download):
create_download_cache_folder(download_cache)
if (target_file
and os.path.exists(target_file)
- and os.path.exists(target_file+'.content-type')):
+ and os.path.exists(target_file + '.content-type')):
fp = open(target_file+'.content-type')
content_type = fp.read().strip()
fp.close()
@@ -427,7 +428,14 @@ def unpack_http_url(link, location, download_cache, only_download):
else:
resp = _get_response_from_url(target_url, link)
content_type = resp.info()['content-type']
- filename = link.filename
+ filename = link.filename # fallback
+ # Have a look at the Content-Disposition header for a better guess
+ content_disposition = resp.info().get('content-disposition')
+ if content_disposition:
+ type, params = cgi.parse_header(content_disposition)
+ # We use ``or`` here because we don't want to use an "empty" value
+ # from the filename param.
+ filename = params.get('filename') or filename
ext = splitext(filename)[1]
if not ext:
ext = mimetypes.guess_extension(content_type)
@@ -466,6 +474,7 @@ def _get_response_from_url(target_url, link):
raise
return resp
+
class Urllib2HeadRequest(urllib2.Request):
def get_method(self):
return "HEAD"
View
@@ -578,13 +578,9 @@ def __hash__(self):
@property
def filename(self):
- url = self.url
- url = url.split('#', 1)[0]
- url = url.split('?', 1)[0]
- url = url.rstrip('/')
+ url = self.url_fragment
name = posixpath.basename(url)
- assert name, (
- 'URL %r produced no filename' % url)
+ assert name, ('URL %r produced no filename' % url)
return name
@property
@@ -598,6 +594,14 @@ def path(self):
def splitext(self):
return splitext(posixpath.basename(self.path.rstrip('/')))
+ @property
+ def url_fragment(self):
+ url = self.url
+ url = url.split('#', 1)[0]
+ url = url.split('?', 1)[0]
+ url = url.rstrip('/')
+ return url
+
_egg_fragment_re = re.compile(r'#egg=([^&]*)')
@property
View
@@ -73,28 +73,34 @@ def from_line(cls, name, comes_from=None):
"""
url = None
name = name.strip()
- req = name
+ req = None
path = os.path.normpath(os.path.abspath(name))
+ link = None
if is_url(name):
- url = name
- ## FIXME: I think getting the requirement here is a bad idea:
- #req = get_requirement_from_url(url)
- req = None
+ link = Link(name)
elif os.path.isdir(path) and (os.path.sep in name or name.startswith('.')):
if not is_installable_dir(path):
- raise InstallationError("Directory %r is not installable. File 'setup.py' not found."
- % name)
- url = path_to_url(name)
- #req = get_requirement_from_url(url)
- req = None
+ raise InstallationError("Directory %r is not installable. File 'setup.py' not found.", name)
+ link = Link(path_to_url(name))
elif is_archive_file(path):
if not os.path.isfile(path):
- logger.warn('Requirement %r looks like a filename, but the file does not exist'
- % name)
- url = path_to_url(name)
- #req = get_requirement_from_url(url)
- req = None
+ logger.warn('Requirement %r looks like a filename, but the file does not exist', name)
+ link = Link(path_to_url(name))
+
+ # If the line has an egg= definition, but isn't editable, pull the requirement out.
+ # Otherwise, assume the name is the req for the non URL/path/archive case.
+ if link and req is None:
+ url = link.url_fragment
+ req = link.egg_fragment
+
+ # Handle relative file URLs
+ if link.scheme == 'file' and re.search(r'\.\./', url):
+ url = path_to_url(os.path.normpath(os.path.abspath(link.path)))
+
+ else:
+ req = name
+
return cls(req, comes_from, url=url)
def __str__(self):
@@ -752,12 +758,12 @@ def values(self):
def __contains__(self, item):
return item in self._keys
-
+
def __setitem__(self, key, value):
if key not in self._keys:
self._keys.append(key)
self._dict[key] = value
-
+
def __getitem__(self, key):
return self._dict[key]
@@ -1054,10 +1060,11 @@ def _pip_has_created_build_dir(self):
def copy_to_build_dir(self, req_to_install):
target_dir = req_to_install.editable and self.src_dir or self.build_dir
- logger.info("Copying %s to %s" %(req_to_install.name, target_dir))
+ logger.info("Copying %s to %s" % (req_to_install.name, target_dir))
dest = os.path.join(target_dir, req_to_install.name)
copytree(req_to_install.source_dir, dest)
- call_subprocess(["python", "%s/setup.py"%dest, "clean"])
+ call_subprocess(["python", "%s/setup.py" % dest, "clean"], cwd=dest,
+ command_desc='python setup.py clean')
def unpack_url(self, link, location, only_download=False):
if only_download:
@@ -1077,7 +1084,7 @@ def install(self, install_options, global_options=()):
if self.upgrade or not r.satisfied_by]
if to_install:
- logger.notify('Installing collected packages: %s' % (', '.join([req.name for req in to_install])))
+ logger.notify('Installing collected packages: %s' % ', '.join([req.name for req in to_install]))
logger.indent += 2
try:
for requirement in to_install:
View
@@ -7,7 +7,7 @@
import pkg_resources
import zipfile
import tarfile
-from pip.exceptions import InstallationError
+from pip.exceptions import InstallationError, BadCommand
from pip.backwardcompat import WindowsError, string_types, raw_input
from pip.locations import site_packages, running_under_virtualenv
from pip.log import logger
@@ -71,12 +71,12 @@ def backup_dir(dir, ext='.bak'):
def find_command(cmd, paths=None, pathext=None):
"""Searches the PATH for the given command and returns its path"""
if paths is None:
- paths = os.environ.get('PATH', []).split(os.pathsep)
+ paths = os.environ.get('PATH', '').split(os.pathsep)
if isinstance(paths, string_types):
paths = [paths]
# check if there are funny path extensions for executables, e.g. Windows
if pathext is None:
- pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
+ pathext = get_pathext()
pathext = [ext for ext in pathext.lower().split(os.pathsep)]
# don't use extensions if the command ends with one of them
if os.path.splitext(cmd)[1].lower() in pathext:
@@ -92,9 +92,16 @@ def find_command(cmd, paths=None, pathext=None):
return cmd_path_ext
if os.path.isfile(cmd_path):
return cmd_path
- return None
+ raise BadCommand('Cannot find command %r' % cmd)
+def get_pathext(default_pathext=None):
+ """Returns the path extensions from environment or a default"""
+ if default_pathext is None:
+ default_pathext = os.pathsep.join([ '.COM', '.EXE', '.BAT', '.CMD' ])
+ pathext = os.environ.get('PATHEXT', default_pathext)
+ return pathext
+
def ask(message, options):
"""Ask the message interactively, with the given possible responses"""
while 1:
Oops, something went wrong.

0 comments on commit b1f2a9f

Please sign in to comment.