| commit 397: | c74803491ef2 |
| parent 396: | 45d24e7032ee |
| branch: | trunk |
refactor uninstall rollback-save to use full paths
- View carljm's profile
-
carljm's public repos »
- virtualenv
- django-adminfiles
- django-admintabs
- sample-distutils2-project
- django-tagging
- pip-testing
- django-markitup
- django-model-utils
- virtualenv-small-sitepy
- pip-windows
- pip-uninstall
- pep376
- pip
- django-lockdown
- python-peps
- django-form-utils
- gridimage
- python-peps-376
- python-distutils
- django-project-template
- django-errorstack
- django-localeurl
- django-admin-uploads
- ScriptTest
- dotfiles
- Send message
3 months ago
Changed (Δ554 bytes):
raw changeset »
pip/req.py (43 lines added, 37 lines removed)
pip/util.py (3 lines added, 16 lines removed)
| … | … | @@ -19,7 +19,7 @@ from pip.log import logger |
19 |
19 |
from pip.util import display_path, rmtree, format_size |
20 |
20 |
from pip.util import splitext, ask, backup_dir |
21 |
21 |
from pip.util import url_to_filename, filename_to_url |
22 |
from pip.util import is_url, is_filename, |
|
22 |
from pip.util import is_url, is_filename, is_framework_layout |
|
23 |
23 |
from pip.util import make_path_relative, is_svn_page, file_contents |
24 |
24 |
from pip.util import has_leading_dir, split_leading_dir |
25 |
25 |
from pip.util import get_file_content |
| … | … | @@ -1344,9 +1344,22 @@ class UninstallPathSet(object): |
1344 |
1344 |
self.save_dir = None |
1345 |
1345 |
self._moved_paths = [] |
1346 |
1346 |
|
1347 |
def _permitted(self, path): |
|
1348 |
""" |
|
1349 |
Return True if the given path is one we are permitted to remove, |
|
1350 |
False otherwise. |
|
1351 |
||
1352 |
""" |
|
1353 |
ok_prefixes = [self.prefix] |
|
1354 |
# Yep, we are special casing the framework layout of MacPython here |
|
1355 |
if is_framework_layout(sys.prefix): |
|
1356 |
for location in ('/Library', '/usr/local'): |
|
1357 |
if path.startswith(location): |
|
1358 |
ok_prefixes.append(location) |
|
1359 |
return any([path.startswith(prefix) for prefix in ok_prefixes]) |
|
1360 |
||
1347 |
1361 |
def _can_uninstall(self): |
1348 |
prefix, stripped = strip_prefix(self.location, self.prefix) |
|
1349 |
if not stripped: |
|
1362 |
if not self._permitted(self.location): |
|
1350 |
1363 |
logger.notify("Not uninstalling %s at %s, outside environment %s" |
1351 |
1364 |
% (self.dist.project_name, self.dist.location, |
1352 |
1365 |
self.prefix)) |
| … | … | @@ -1354,24 +1367,23 @@ class UninstallPathSet(object): |
1354 |
1367 |
return True |
1355 |
1368 |
|
1356 |
1369 |
def add(self, path): |
1357 |
path = os.path. |
|
1370 |
path = os.path.normcase(os.path.abspath(path)) |
|
1358 |
1371 |
if not os.path.exists(path): |
1359 |
1372 |
return |
1360 |
prefix, stripped = strip_prefix(os.path.normcase(path), self.prefix) |
|
1361 |
if stripped: |
|
1362 |
|
|
1373 |
if self._permitted(path): |
|
1374 |
self.paths.add(path) |
|
1363 |
1375 |
else: |
1364 |
self._refuse.add( |
|
1376 |
self._refuse.add(path) |
|
1365 |
1377 |
|
1366 |
1378 |
def add_pth(self, pth_file, entry): |
1367 |
prefix, stripped = strip_prefix(os.path.normcase(pth_file), self.prefix) |
|
1368 |
if stripped: |
|
1379 |
pth_file = os.path.normcase(pth_file) |
|
1380 |
if self._permitted(pth_file): |
|
1369 |
1381 |
entry = os.path.normcase(entry) |
1370 |
if stripped not in self.pth: |
|
1371 |
self.pth[stripped] = UninstallPthEntries(os.path.join(prefix, stripped)) |
|
1372 |
|
|
1382 |
if pth_file not in self.pth: |
|
1383 |
self.pth[pth_file] = UninstallPthEntries(pth_file) |
|
1384 |
self.pth[pth_file].add(entry) |
|
1373 |
1385 |
else: |
1374 |
self._refuse.add( |
|
1386 |
self._refuse.add(pth_file) |
|
1375 |
1387 |
|
1376 |
1388 |
def compact(self, paths): |
1377 |
1389 |
"""Compact a path set to contain the minimal number of paths |
| … | … | @@ -1379,15 +1391,11 @@ class UninstallPathSet(object): |
1379 |
1391 |
/a/path/to/a/file.txt are both in the set, leave only the |
1380 |
1392 |
shorter path.""" |
1381 |
1393 |
short_paths = set() |
1382 |
def sort_set(x, y): |
|
1383 |
prefix_x, path_x = x |
|
1384 |
prefix_y, path_y = y |
|
1385 |
return cmp(len(path_x), len(path_y)) |
|
1386 |
for p |
|
1394 |
for path in sorted(paths, lambda x, y: cmp(len(x), len(y))): |
|
1387 |
1395 |
if not any([(path.startswith(shortpath) and |
1388 |
1396 |
path[len(shortpath.rstrip(os.path.sep))] == os.path.sep) |
1389 |
for shortprefix, shortpath in short_paths]): |
|
1390 |
short_paths.add((prefix, path)) |
|
1397 |
for shortpath in short_paths]): |
|
1398 |
short_paths.add(path) |
|
1391 |
1399 |
return short_paths |
1392 |
1400 |
|
1393 |
1401 |
def remove(self, auto_confirm=False): |
| … | … | @@ -1402,22 +1410,21 @@ class UninstallPathSet(object): |
1402 |
1410 |
if auto_confirm: |
1403 |
1411 |
response = 'y' |
1404 |
1412 |
else: |
1405 |
for prefix, path in paths: |
|
1406 |
logger.notify(os.path.join(prefix, path)) |
|
1413 |
for path in paths: |
|
1414 |
logger.notify(path) |
|
1407 |
1415 |
response = ask('Proceed (y/n)? ', ('y', 'n')) |
1408 |
1416 |
if self._refuse: |
1409 |
1417 |
logger.notify('Not removing or modifying (outside of prefix):') |
1410 |
for prefix, path in self.compact(self._refuse): |
|
1411 |
logger.notify(os.path.join(prefix, path)) |
|
1418 |
for path in self.compact(self._refuse): |
|
1419 |
logger.notify(path) |
|
1412 |
1420 |
if response == 'y': |
1413 |
1421 |
self.save_dir = tempfile.mkdtemp('-uninstall', 'pip-') |
1414 |
for prefix, path in paths: |
|
1415 |
full_path = os.path.join(prefix, path) |
|
1416 |
new_path = os.path.join(self.save_dir, path) |
|
1417 |
new_dir = os.path.dirname(new_path) |
|
1418 |
logger.info('Removing file or directory %s' % full_path) |
|
1419 |
self._moved_paths.append((prefix, path)) |
|
1420 |
|
|
1422 |
for path in paths: |
|
1423 |
new_path = os.path.join(self.save_dir, |
|
1424 |
path.lstrip(os.path.sep)) |
|
1425 |
logger.info('Removing file or directory %s' % path) |
|
1426 |
self._moved_paths.append(path) |
|
1427 |
os.renames(path, new_path) |
|
1421 |
1428 |
for pth in self.pth.values(): |
1422 |
1429 |
pth.remove() |
1423 |
1430 |
logger.notify('Successfully uninstalled %s' % self.dist.project_name) |
| … | … | @@ -1431,11 +1438,10 @@ class UninstallPathSet(object): |
1431 |
1438 |
logger.error("Can't roll back %s; was not uninstalled" % self.dist.project_name) |
1432 |
1439 |
return False |
1433 |
1440 |
logger.notify('Rolling back uninstall of %s' % self.dist.project_name) |
1434 |
for prefix, path in self._moved_paths: |
|
1435 |
tmp_path = os.path.join(self.save_dir, path) |
|
1436 |
real_path = os.path.join(prefix, path) |
|
1437 |
logger.info('Replacing %s' % real_path) |
|
1438 |
|
|
1441 |
for path in self._moved_paths: |
|
1442 |
tmp_path = os.path.join(self.save_dir, path.lstrip(os.path.sep)) |
|
1443 |
logger.info('Replacing %s' % path) |
|
1444 |
os.renames(tmp_path, path) |
|
1439 |
1445 |
for pth in self.pth: |
1440 |
1446 |
pth.rollback() |
1441 |
1447 |
| … | … | @@ -190,20 +190,6 @@ def is_filename(name): |
190 |
190 |
return False |
191 |
191 |
return True |
192 |
192 |
|
193 |
def strip_prefix(path, prefix): |
|
194 |
""" If ``path`` begins with ``prefix``, return ``path`` with |
|
195 |
``prefix`` stripped off. Otherwise return None.""" |
|
196 |
prefixes = [prefix] |
|
197 |
# Yep, we are special casing the framework layout of MacPython here |
|
198 |
if is_framework_layout(sys.prefix): |
|
199 |
for location in ('/Library', '/usr/local'): |
|
200 |
if path.startswith(location): |
|
201 |
prefixes.append(location) |
|
202 |
for prefix in prefixes: |
|
203 |
if path.startswith(prefix): |
|
204 |
return prefix, path.replace(prefix + os.path.sep, '') |
|
205 |
return None, None |
|
206 |
||
207 |
193 |
def is_svn_page(html): |
208 |
194 |
"""Returns true if the page appears to be the index page of an svn repository""" |
209 |
195 |
return (re.search(r'<title>[^<]*Revision \d+:', html) |
| … | … | @@ -269,11 +255,12 @@ def make_path_relative(path, rel_to): |
269 |
255 |
return '.' + os.path.sep |
270 |
256 |
return os.path.sep.join(full_parts) |
271 |
257 |
|
272 |
def is_framework_layout( |
|
258 |
def is_framework_layout(site_packages_dir): |
|
273 |
259 |
"""Return True if the current platform is the default Python of Mac OS X |
274 |
260 |
which installs scripts in /usr/local/bin""" |
275 |
261 |
return (sys.platform[:6] == 'darwin' and |
276 |
( |
|
262 |
(site_packages_dir[:9] == '/Library/' or |
|
263 |
site_packages_dir[:16] == '/System/Library/')) |
|
277 |
264 |
|
278 |
265 |
_scheme_re = re.compile(r'^(http|https|file):', re.I) |
279 |
266 |
_url_slash_drive_re = re.compile(r'/*([a-z])\|', re.I) |
