From 8e742c7be18e325a50772c9d89d2934c30ebb3d5 Mon Sep 17 00:00:00 2001 From: harminius Date: Tue, 11 Aug 2026 14:22:06 +0200 Subject: [PATCH 1/2] add safeguard for too long path --- mergin/common.py | 3 +++ mergin/merginproject.py | 15 ++++++++++++++- mergin/utils.py | 17 ++++++++++++++++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/mergin/common.py b/mergin/common.py index 3d3f7e8..c3d11ca 100644 --- a/mergin/common.py +++ b/mergin/common.py @@ -33,6 +33,9 @@ # Maximum changes uploading to server MAX_UPLOAD_CHANGES = 100 +# maximum length of a path supported by Windows without long paths enabled (MAX_PATH) +WINDOWS_MAX_PATH = 260 + # default URL for submitting logs MERGIN_DEFAULT_LOGS_URL = "https://g4pfq226j0.execute-api.eu-west-1.amazonaws.com/mergin_client_log_submit" diff --git a/mergin/merginproject.py b/mergin/merginproject.py index 12d798f..2237084 100644 --- a/mergin/merginproject.py +++ b/mergin/merginproject.py @@ -19,6 +19,7 @@ from .utils import ( generate_checksum, is_versioned_file, + is_path_too_long, int_version, do_sqlite_checkpoint, unique_path_name, @@ -623,8 +624,14 @@ def get_local_delta(self, diff_directory: str) -> List[ProjectDeltaChange]: delta_item.size = checkpoint_size delta_item.checksum = checkpoint_checksum + diff_location = self.fpath(diff_file, diff_directory) + if is_path_too_long(diff_location): + raise ClientError( + f"Cannot create changeset for '{path}': diff file path is too long " + f"({len(diff_location)} characters) for this OS: {diff_location}\n" + "Move the project to a directory with a shorter path and try again." + ) try: - diff_location = self.fpath(diff_file, diff_directory) self.geodiff.create_changeset(origin_file, current_file, diff_location) if not self.geodiff.has_changes(diff_location): os.remove(diff_location) @@ -677,6 +684,12 @@ def get_push_changes(self): diff_id = str(uuid.uuid4()) diff_name = path + "-diff-" + diff_id diff_file = self.fpath_meta(diff_name) + if is_path_too_long(diff_file): + raise ClientError( + f"Cannot create changeset for '{path}': diff file path is too long " + f"({len(diff_file)} characters) for this OS: {diff_file}\n" + "Move the project to a directory with a shorter path and try again." + ) try: self.geodiff.create_changeset(origin_file, current_file, diff_file) if self.geodiff.has_changes(diff_file): diff --git a/mergin/utils.py b/mergin/utils.py index 91796f3..6de05b3 100644 --- a/mergin/utils.py +++ b/mergin/utils.py @@ -9,7 +9,7 @@ import tempfile from enum import Enum from typing import Optional, Type, Union, ByteString -from .common import ClientError +from .common import ClientError, WINDOWS_MAX_PATH def generate_checksum(file, chunk_size=4096): @@ -266,6 +266,21 @@ def is_versioned_file(path: str) -> bool: return f_extension.lower() in diff_extensions +def is_path_too_long(path: str) -> bool: + """ + Check whether an absolute path is too long to be reliably created/opened on this OS. + + Windows limits paths to WINDOWS_MAX_PATH (260) characters unless long paths have been + explicitly enabled (which we cannot rely on being the case), so we treat that as the limit. + + :param path: absolute path to check + :type path: str + :returns: whether the path is likely to be rejected by the OS + :rtype: bool + """ + return os.name == "nt" and len(path) >= WINDOWS_MAX_PATH + + def is_qgis_file(path: str) -> bool: """ Check if file is a QGIS project file. From 77ae14ba1bbc455c66e57c8ecf9d05d09a49ad4e Mon Sep 17 00:00:00 2001 From: harminius Date: Tue, 11 Aug 2026 14:30:09 +0200 Subject: [PATCH 2/2] docstring --- mergin/utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/mergin/utils.py b/mergin/utils.py index 6de05b3..02bee94 100644 --- a/mergin/utils.py +++ b/mergin/utils.py @@ -270,9 +270,6 @@ def is_path_too_long(path: str) -> bool: """ Check whether an absolute path is too long to be reliably created/opened on this OS. - Windows limits paths to WINDOWS_MAX_PATH (260) characters unless long paths have been - explicitly enabled (which we cannot rely on being the case), so we treat that as the limit. - :param path: absolute path to check :type path: str :returns: whether the path is likely to be rejected by the OS