Fixed #27658 -- Prevented collectstatic from overwriting newer files in remote storages.

Thanks revimi for the initial patch.
This commit is contained in:
Tim Graham 2017-01-03 19:03:08 -05:00
parent f60d4e704d
commit c85831e4b7
5 changed files with 46 additions and 5 deletions

View File

@ -274,12 +274,24 @@ class Command(BaseCommand):
# The full path of the target file # The full path of the target file
if self.local: if self.local:
full_path = self.storage.path(prefixed_path) full_path = self.storage.path(prefixed_path)
# If it's --link mode and the path isn't a link (i.e.
# the previous collectstatic wasn't with --link) or if
# it's non-link mode and the path is a link (i.e. the
# previous collectstatic was with --link), the old
# links/files must be deleted so it's not safe to skip
# unmodified files.
can_skip_unmodified_files = not (self.symlink ^ os.path.islink(full_path))
else: else:
full_path = None full_path = None
# Skip the file if the source file is younger # In remote storages, skipping is only based on the
# modified times since symlinks aren't relevant.
can_skip_unmodified_files = True
# Avoid sub-second precision (see #14665, #19540) # Avoid sub-second precision (see #14665, #19540)
if (target_last_modified.replace(microsecond=0) >= source_last_modified.replace(microsecond=0) and file_is_unmodified = (
full_path and not (self.symlink ^ os.path.islink(full_path))): target_last_modified.replace(microsecond=0) >=
source_last_modified.replace(microsecond=0)
)
if file_is_unmodified and can_skip_unmodified_files:
if prefixed_path not in self.unmodified_files: if prefixed_path not in self.unmodified_files:
self.unmodified_files.append(prefixed_path) self.unmodified_files.append(prefixed_path)
self.log("Skipping '%s' (not modified)" % path) self.log("Skipping '%s' (not modified)" % path)

View File

@ -17,3 +17,6 @@ Bugfixes
* Fixed a regression in the ``timesince`` and ``timeuntil`` filters that caused * Fixed a regression in the ``timesince`` and ``timeuntil`` filters that caused
incorrect results for dates in a leap year (:ticket:`27637`). incorrect results for dates in a leap year (:ticket:`27637`).
* Fixed a regression where ``collectstatic`` overwrote newer files in remote
storages (:ticket:`27658`).

View File

@ -82,7 +82,8 @@ class CollectionTestCase(BaseStaticFilesMixin, SimpleTestCase):
super(CollectionTestCase, self).tearDown() super(CollectionTestCase, self).tearDown()
def run_collectstatic(self, **kwargs): def run_collectstatic(self, **kwargs):
call_command('collectstatic', interactive=False, verbosity=0, verbosity = kwargs.pop('verbosity', 0)
call_command('collectstatic', interactive=False, verbosity=verbosity,
ignore_patterns=['*.ignoreme'], **kwargs) ignore_patterns=['*.ignoreme'], **kwargs)
def _get_file(self, filepath): def _get_file(self, filepath):

View File

@ -1,6 +1,6 @@
import errno import errno
import os import os
from datetime import datetime from datetime import datetime, timedelta
from django.conf import settings from django.conf import settings
from django.contrib.staticfiles.storage import CachedStaticFilesStorage from django.contrib.staticfiles.storage import CachedStaticFilesStorage
@ -59,6 +59,14 @@ class PathNotImplementedStorage(storage.Storage):
raise NotImplementedError raise NotImplementedError
class NeverCopyRemoteStorage(PathNotImplementedStorage):
"""
Return a future modified time for all files so that nothing is collected.
"""
def get_modified_time(self, name):
return datetime.now() + timedelta(days=30)
class QueryStringStorage(storage.Storage): class QueryStringStorage(storage.Storage):
def url(self, path): def url(self, path):
return path + '?a=b&c=d' return path + '?a=b&c=d'

View File

@ -399,6 +399,23 @@ class TestCollectionNonLocalStorage(TestNoFilesCreated, CollectionTestCase):
storage.path('name') storage.path('name')
class TestCollectionNeverCopyStorage(CollectionTestCase):
@override_settings(STATICFILES_STORAGE='staticfiles_tests.storage.NeverCopyRemoteStorage')
def test_skips_newer_files_in_remote_storage(self):
"""
collectstatic skips newer files in a remote storage.
run_collectstatic() in setUp() copies the static files, then files are
always skipped after NeverCopyRemoteStorage is activated since
NeverCopyRemoteStorage.get_modified_time() returns a datetime in the
future to simulate an unmodified file.
"""
stdout = six.StringIO()
self.run_collectstatic(stdout=stdout, verbosity=2)
output = force_text(stdout.getvalue())
self.assertIn("Skipping 'test.txt' (not modified)", output)
@unittest.skipUnless(symlinks_supported(), "Must be able to symlink to run this test.") @unittest.skipUnless(symlinks_supported(), "Must be able to symlink to run this test.")
class TestCollectionLinks(TestDefaults, CollectionTestCase): class TestCollectionLinks(TestDefaults, CollectionTestCase):
""" """