mirror of https://github.com/django/django.git
Fixed #27658 -- Prevented collectstatic from overwriting newer files in remote storages.
Thanks revimi for the initial patch.
This commit is contained in:
parent
f60d4e704d
commit
c85831e4b7
|
@ -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)
|
||||||
|
|
|
@ -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`).
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue