mirror of https://github.com/pypa/pip
use shutil.move for rollback-save (fixes #27)
If TMPDIR is on a different filesystem, this has the potential to lose file ownership info. In general that shouldn't be an issue; seems to be the best we can do.
This commit is contained in:
parent
1b905e0456
commit
f0416ebddd
20
pip/req.py
20
pip/req.py
|
@ -19,7 +19,7 @@ from pip.log import logger
|
||||||
from pip.util import display_path, rmtree, format_size
|
from pip.util import display_path, rmtree, format_size
|
||||||
from pip.util import splitext, ask, backup_dir
|
from pip.util import splitext, ask, backup_dir
|
||||||
from pip.util import url_to_filename, filename_to_url
|
from pip.util import url_to_filename, filename_to_url
|
||||||
from pip.util import is_url, is_filename, is_framework_layout
|
from pip.util import is_url, is_filename, renames, is_framework_layout
|
||||||
from pip.util import make_path_relative, is_svn_page, file_contents
|
from pip.util import make_path_relative, is_svn_page, file_contents
|
||||||
from pip.util import has_leading_dir, split_leading_dir
|
from pip.util import has_leading_dir, split_leading_dir
|
||||||
from pip.util import get_file_content
|
from pip.util import get_file_content
|
||||||
|
@ -455,6 +455,13 @@ execfile(__file__)
|
||||||
logger.error("Can't rollback %s, nothing uninstalled."
|
logger.error("Can't rollback %s, nothing uninstalled."
|
||||||
% (self.project_name,))
|
% (self.project_name,))
|
||||||
|
|
||||||
|
def commit_uninstall(self):
|
||||||
|
if self.uninstalled:
|
||||||
|
self.uninstalled.commit()
|
||||||
|
else:
|
||||||
|
logger.error("Can't commit %s, nothing uninstalled."
|
||||||
|
% (self.project_name,))
|
||||||
|
|
||||||
def archive(self, build_dir):
|
def archive(self, build_dir):
|
||||||
assert self.source_dir
|
assert self.source_dir
|
||||||
create_archive = True
|
create_archive = True
|
||||||
|
@ -748,6 +755,7 @@ class RequirementSet(object):
|
||||||
def uninstall(self, auto_confirm=False):
|
def uninstall(self, auto_confirm=False):
|
||||||
for req in self.requirements.values():
|
for req in self.requirements.values():
|
||||||
req.uninstall(auto_confirm=auto_confirm)
|
req.uninstall(auto_confirm=auto_confirm)
|
||||||
|
req.commit_uninstall()
|
||||||
|
|
||||||
def install_files(self, finder, force_root_egg_info=False):
|
def install_files(self, finder, force_root_egg_info=False):
|
||||||
unnamed = list(self.unnamed_requirements)
|
unnamed = list(self.unnamed_requirements)
|
||||||
|
@ -1135,6 +1143,9 @@ class RequirementSet(object):
|
||||||
if requirement.conflicts_with and not requirement.install_succeeded:
|
if requirement.conflicts_with and not requirement.install_succeeded:
|
||||||
requirement.rollback_uninstall()
|
requirement.rollback_uninstall()
|
||||||
raise
|
raise
|
||||||
|
else:
|
||||||
|
if requirement.conflicts_with and requirement.install_succeeded:
|
||||||
|
requirement.commit_uninstall()
|
||||||
requirement.remove_temporary_source()
|
requirement.remove_temporary_source()
|
||||||
finally:
|
finally:
|
||||||
logger.indent -= 2
|
logger.indent -= 2
|
||||||
|
@ -1418,13 +1429,14 @@ class UninstallPathSet(object):
|
||||||
for path in self.compact(self._refuse):
|
for path in self.compact(self._refuse):
|
||||||
logger.notify(path)
|
logger.notify(path)
|
||||||
if response == 'y':
|
if response == 'y':
|
||||||
self.save_dir = tempfile.mkdtemp('-uninstall', 'pip-')
|
self.save_dir = tempfile.mkdtemp(suffix='-uninstall',
|
||||||
|
prefix='pip-')
|
||||||
for path in paths:
|
for path in paths:
|
||||||
new_path = os.path.join(self.save_dir,
|
new_path = os.path.join(self.save_dir,
|
||||||
path.lstrip(os.path.sep))
|
path.lstrip(os.path.sep))
|
||||||
logger.info('Removing file or directory %s' % path)
|
logger.info('Removing file or directory %s' % path)
|
||||||
self._moved_paths.append(path)
|
self._moved_paths.append(path)
|
||||||
os.renames(path, new_path)
|
renames(path, new_path)
|
||||||
for pth in self.pth.values():
|
for pth in self.pth.values():
|
||||||
pth.remove()
|
pth.remove()
|
||||||
logger.notify('Successfully uninstalled %s' % self.dist.project_name)
|
logger.notify('Successfully uninstalled %s' % self.dist.project_name)
|
||||||
|
@ -1441,7 +1453,7 @@ class UninstallPathSet(object):
|
||||||
for path in self._moved_paths:
|
for path in self._moved_paths:
|
||||||
tmp_path = os.path.join(self.save_dir, path.lstrip(os.path.sep))
|
tmp_path = os.path.join(self.save_dir, path.lstrip(os.path.sep))
|
||||||
logger.info('Replacing %s' % path)
|
logger.info('Replacing %s' % path)
|
||||||
os.renames(tmp_path, path)
|
renames(tmp_path, path)
|
||||||
for pth in self.pth:
|
for pth in self.pth:
|
||||||
pth.rollback()
|
pth.rollback()
|
||||||
|
|
||||||
|
|
18
pip/util.py
18
pip/util.py
|
@ -17,7 +17,7 @@ __all__ = ['rmtree', 'display_path', 'backup_dir',
|
||||||
'strip_prefix', 'is_svn_page', 'file_contents',
|
'strip_prefix', 'is_svn_page', 'file_contents',
|
||||||
'split_leading_dir', 'has_leading_dir',
|
'split_leading_dir', 'has_leading_dir',
|
||||||
'make_path_relative', 'is_framework_layout',
|
'make_path_relative', 'is_framework_layout',
|
||||||
'get_file_content']
|
'get_file_content', 'renames']
|
||||||
|
|
||||||
def rmtree(dir):
|
def rmtree(dir):
|
||||||
shutil.rmtree(dir, ignore_errors=True,
|
shutil.rmtree(dir, ignore_errors=True,
|
||||||
|
@ -294,3 +294,19 @@ def get_file_content(url, comes_from=None):
|
||||||
content = f.read()
|
content = f.read()
|
||||||
f.close()
|
f.close()
|
||||||
return url, content
|
return url, content
|
||||||
|
|
||||||
|
def renames(old, new):
|
||||||
|
"""Like os.renames(), but handles renaming across devices."""
|
||||||
|
# Implementation borrowed from os.renames().
|
||||||
|
head, tail = os.path.split(new)
|
||||||
|
if head and tail and not os.path.exists(head):
|
||||||
|
os.makedirs(head)
|
||||||
|
|
||||||
|
shutil.move(old, new)
|
||||||
|
|
||||||
|
head, tail = os.path.split(old)
|
||||||
|
if head and tail:
|
||||||
|
try:
|
||||||
|
os.removedirs(head)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
Loading…
Reference in New Issue