Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 19 additions & 15 deletions src/borg/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,26 +314,30 @@ def _build_files_cache(self):
# only put regular files' infos into the files cache:
if stat.S_ISREG(item.mode):
path_hash = self.key.id_hash(safe_encode(item.path))
# an item does not necessarily have all of these timestamps: --noctime omits ctime,
# on Windows ctime is never archived (it is the file creation time there, see #8730)
# and very old archives only have mtime.
ctime_ns = item.get("ctime")
mtime_ns = item.get("mtime")
# keep track of the key(s) for the most recent timestamp(s):
ctime_ns = item.ctime
if self._newest_cmtime is None or ctime_ns > self._newest_cmtime:
self._newest_cmtime = ctime_ns
self._newest_path_hashes = {path_hash}
elif ctime_ns == self._newest_cmtime:
self._newest_path_hashes.add(path_hash)
mtime_ns = item.mtime
if self._newest_cmtime is None or mtime_ns > self._newest_cmtime:
self._newest_cmtime = mtime_ns
self._newest_path_hashes = {path_hash}
elif mtime_ns == self._newest_cmtime:
self._newest_path_hashes.add(path_hash)
# add the file to the in-memory files cache
for timestamp_ns in (ctime_ns, mtime_ns):
if timestamp_ns is None:
continue
if self._newest_cmtime is None or timestamp_ns > self._newest_cmtime:
self._newest_cmtime = timestamp_ns
self._newest_path_hashes = {path_hash}
elif timestamp_ns == self._newest_cmtime:
self._newest_path_hashes.add(path_hash)
# Add the file to the in-memory files cache. A timestamp the archive does not have
# is cached as 0, so it can not compare equal to the timestamp seen in the file
# system: if that timestamp is part of the files cache mode, the file is considered
# changed and gets chunked again, which is the safe outcome.
entry = FileCacheEntry(
age=0,
inode=item.get("inode", 0),
size=item.size,
ctime=int_to_timestamp(ctime_ns),
mtime=int_to_timestamp(mtime_ns),
ctime=int_to_timestamp(0 if ctime_ns is None else ctime_ns),
mtime=int_to_timestamp(0 if mtime_ns is None else mtime_ns),
chunks=item.chunks,
digests=item.get("digests"),
)
Expand Down
22 changes: 22 additions & 0 deletions src/borg/testsuite/archiver/create_cmd_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import socket
import stat
import subprocess
from pathlib import Path

import pytest
from blake3 import blake3
Expand Down Expand Up @@ -2035,3 +2036,24 @@ def no_close_time_write(self, now, force=False, clear=False):
with changedir("output"):
cmd(archiver, "extract", "test")
assert_dirs_equal("input", "output/input")


def _remove_files_cache(archiver, archive_name):
"""Remove the local files cache of an archive series, forcing a rebuild from the repository."""
from ...cache import files_cache_name
from ...helpers import get_cache_dir

repo_id = json.loads(cmd(archiver, "repo-info", "--json"))["repository"]["id"]
cache_file = Path(get_cache_dir(repo_id, create=False)) / files_cache_name(archive_name)
cache_file.unlink()


def test_files_cache_rebuild_without_ctime(archivers, request):
"""Rebuilding from an archive that has no ctime must work - --noctime, and always on Windows."""
archiver = request.getfixturevalue(archivers)
create_regular_file(archiver.input_path, "file1", size=1024 * 80)
cmd(archiver, "repo-create", RK_ENCRYPTION)
cmd(archiver, "create", "--noctime", "home", "input")
_remove_files_cache(archiver, "home")
output = cmd(archiver, "create", "--noctime", "--debug", "home", "input")
assert "Building files cache from" in output
Loading