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
if self.local:
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:
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)
if (target_last_modified.replace(microsecond=0) >= source_last_modified.replace(microsecond=0) and
full_path and not (self.symlink ^ os.path.islink(full_path))):
file_is_unmodified = (
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:
self.unmodified_files.append(prefixed_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
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()
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)
def _get_file(self, filepath):

View File

@ -1,6 +1,6 @@
import errno
import os
from datetime import datetime
from datetime import datetime, timedelta
from django.conf import settings
from django.contrib.staticfiles.storage import CachedStaticFilesStorage
@ -59,6 +59,14 @@ class PathNotImplementedStorage(storage.Storage):
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):
def url(self, path):
return path + '?a=b&c=d'

View File

@ -399,6 +399,23 @@ class TestCollectionNonLocalStorage(TestNoFilesCreated, CollectionTestCase):
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.")
class TestCollectionLinks(TestDefaults, CollectionTestCase):
"""