Skip to content

Commit bcc7ae6

Browse files
committed
introduce GeoDiffLongPath handler
1 parent 782603c commit bcc7ae6

4 files changed

Lines changed: 95 additions & 61 deletions

File tree

mergin/client.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1384,9 +1384,7 @@ def reset_local_changes(self, directory: str, files_to_reset: typing.List[str] =
13841384
for file in push_changes["updated"]:
13851385
if all_files or file["path"] in files_to_reset:
13861386
if mp.is_versioned_file(file["path"]):
1387-
mp.geodiff.make_copy_sqlite(
1388-
long_path(mp.fpath_meta(file["path"])), long_path(mp.fpath(file["path"]))
1389-
)
1387+
mp.geodiff.make_copy_sqlite(mp.fpath_meta(file["path"]), mp.fpath(file["path"]))
13901388
else:
13911389
files_download.append(file["path"])
13921390

mergin/client_pull.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ def apply(self, directory, mp):
423423
# Make a copy of the file to meta dir only if there is no user-specified path for the file.
424424
# destination_file is None for full project download and takes a meaningful value for a single file download.
425425
if mp.is_versioned_file(self.file_path) and self.destination_file is None:
426-
mp.geodiff.make_copy_sqlite(long_path(mp.fpath(self.file_path)), long_path(mp.fpath_meta(self.file_path)))
426+
mp.geodiff.make_copy_sqlite(mp.fpath(self.file_path), mp.fpath_meta(self.file_path))
427427

428428

429429
class PullJob:

mergin/merginproject.py

Lines changed: 92 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,54 @@
4040
import pygeodiff
4141

4242

43+
class GeoDiffLongPath:
44+
"""
45+
Wraps a pygeodiff.GeoDiff instance so that every filesystem path passed to it is prefixed
46+
with the Windows extended-length ("\\?\") marker.
47+
48+
Only the geodiff methods that take path arguments are listed explicitly.
49+
"""
50+
51+
def __init__(self, geodiff):
52+
self._geodiff = geodiff
53+
54+
def create_changeset(self, base, modified, changeset):
55+
return self._geodiff.create_changeset(long_path(base), long_path(modified), long_path(changeset))
56+
57+
def apply_changeset(self, base, changeset):
58+
return self._geodiff.apply_changeset(long_path(base), long_path(changeset))
59+
60+
def rebase(self, base, modified_their, modified, conflict):
61+
return self._geodiff.rebase(
62+
long_path(base), long_path(modified_their), long_path(modified), long_path(conflict)
63+
)
64+
65+
def make_copy_sqlite(self, src, dst):
66+
return self._geodiff.make_copy_sqlite(long_path(src), long_path(dst))
67+
68+
def has_changes(self, changeset):
69+
return self._geodiff.has_changes(long_path(changeset))
70+
71+
def changes_count(self, changeset):
72+
return self._geodiff.changes_count(long_path(changeset))
73+
74+
def read_changeset(self, changeset):
75+
return self._geodiff.read_changeset(long_path(changeset))
76+
77+
def list_changes_summary(self, changeset, json):
78+
return self._geodiff.list_changes_summary(long_path(changeset), long_path(json))
79+
80+
def concat_changes(self, list_changesets, output_changeset):
81+
return self._geodiff.concat_changes([long_path(p) for p in list_changesets], long_path(output_changeset))
82+
83+
def schema(self, driver, driver_info, src, json):
84+
return self._geodiff.schema(driver, driver_info, long_path(src), long_path(json))
85+
86+
def __getattr__(self, name):
87+
# everything without path arguments (set_logger_callback, level/table setters, version(), ...)
88+
return getattr(self._geodiff, name)
89+
90+
4391
class MerginProject:
4492
"""Base class for Mergin Maps local projects.
4593
@@ -70,7 +118,7 @@ def __init__(self, directory):
70118

71119
# make sure we can load correct pygeodiff
72120
try:
73-
self.geodiff = pygeodiff.GeoDiff()
121+
self.geodiff = GeoDiffLongPath(pygeodiff.GeoDiff())
74122
except pygeodiff.geodifflib.GeoDiffLibVersionError:
75123
# this is a fatal error, we can't live without geodiff
76124
self.log.error("Unable to load geodiff! (lib version error)")
@@ -627,8 +675,8 @@ def get_local_delta(self, diff_directory: str) -> List[ProjectDeltaChange]:
627675
diff_location = self.fpath(diff_file, diff_directory)
628676
diff_location_lp = long_path(diff_location)
629677
try:
630-
self.geodiff.create_changeset(long_path(origin_file), long_path(current_file), diff_location_lp)
631-
if not self.geodiff.has_changes(diff_location_lp):
678+
self.geodiff.create_changeset(origin_file, current_file, diff_location)
679+
if not self.geodiff.has_changes(diff_location):
632680
os.remove(diff_location_lp)
633681
continue
634682

@@ -681,8 +729,8 @@ def get_push_changes(self):
681729
diff_file = self.fpath_meta(diff_name)
682730
diff_file_lp = long_path(diff_file)
683731
try:
684-
self.geodiff.create_changeset(long_path(origin_file), long_path(current_file), diff_file_lp)
685-
if self.geodiff.has_changes(diff_file_lp):
732+
self.geodiff.create_changeset(origin_file, current_file, diff_file)
733+
if self.geodiff.has_changes(diff_file):
686734
diff_size = os.path.getsize(diff_file_lp)
687735
file["checksum"] = file["origin_checksum"] # need to match basefile on server
688736
file["chunks"] = [str(uuid.uuid4()) for i in range(math.ceil(diff_size / UPLOAD_CHUNK_SIZE))]
@@ -716,7 +764,7 @@ def copy_versioned_file_for_upload(self, f: FileChange, tmp_dir: str) -> str:
716764
tmp_file = os.path.join(tmp_dir, path)
717765
os.makedirs(os.path.dirname(tmp_file), exist_ok=True)
718766
tmp_file_lp = long_path(tmp_file)
719-
self.geodiff.make_copy_sqlite(long_path(self.fpath(path)), tmp_file_lp)
767+
self.geodiff.make_copy_sqlite(self.fpath(path), tmp_file)
720768
f.size = os.path.getsize(tmp_file_lp)
721769
f.checksum = generate_checksum(tmp_file_lp)
722770
f.chunks = [str(uuid.uuid4()) for i in range(math.ceil(f.size / UPLOAD_CHUNK_SIZE))]
@@ -731,7 +779,7 @@ def get_list_of_push_changes(self, push_changes):
731779
changeset = self.fpath_meta(changeset_path)
732780
result_file = self.fpath("change_list" + str(idx), self.meta_dir)
733781
try:
734-
self.geodiff.list_changes_summary(long_path(changeset), result_file)
782+
self.geodiff.list_changes_summary(changeset, result_file)
735783
with open(result_file, "r") as f:
736784
change = f.read()
737785
changes[file["path"]] = json.loads(change)
@@ -772,8 +820,8 @@ def apply_pull_actions(self, actions: List[PullAction], download_dir: str, serve
772820
if action_type == PullActionType.COPY:
773821
# simply copy the file from server
774822
if is_versioned_file(path):
775-
self.geodiff.make_copy_sqlite(server_file_lp, live_file_lp)
776-
self.geodiff.make_copy_sqlite(server_file_lp, basefile_lp)
823+
self.geodiff.make_copy_sqlite(server_file, live_file)
824+
self.geodiff.make_copy_sqlite(server_file, basefile)
777825
else:
778826
shutil.copy(server_file_lp, live_file_lp)
779827
elif action_type == PullActionType.APPLY_DIFF_NO_REBASE:
@@ -793,13 +841,13 @@ def apply_pull_actions(self, actions: List[PullAction], download_dir: str, serve
793841
conflicts.append(conflict)
794842
if self.is_versioned_file(path):
795843
try:
796-
self.geodiff.make_copy_sqlite(server_file_lp, live_file_lp)
797-
self.geodiff.make_copy_sqlite(server_file_lp, basefile_lp)
844+
self.geodiff.make_copy_sqlite(server_file, live_file)
845+
self.geodiff.make_copy_sqlite(server_file, basefile)
798846
except pygeodiff.GeoDiffLibError:
799847
self.log.info("failed to create SQLite copy for file: " + path)
800848
# create unfinished pull copy instead
801-
f_server_unfinished = long_path(self.fpath_unfinished_pull(path))
802-
self.geodiff.make_copy_sqlite(server_file_lp, f_server_unfinished)
849+
f_server_unfinished = self.fpath_unfinished_pull(path)
850+
self.geodiff.make_copy_sqlite(server_file, f_server_unfinished)
803851
else:
804852
shutil.copy(server_file_lp, live_file_lp)
805853

@@ -835,57 +883,51 @@ def update_with_rebase(self, path, src, dest, basefile, temp_dir, user_name):
835883
"""
836884
self.log.info("updating file with rebase: " + path)
837885

838-
src_lp = long_path(src)
839-
dest_lp = long_path(dest)
840-
basefile_lp = long_path(basefile)
841-
842-
server_diff = long_path(
843-
self.fpath(f"{path}-server_diff", temp_dir)
844-
) # diff between server file and local basefile
845-
local_diff = long_path(self.fpath(f"{path}-local_diff", temp_dir))
886+
server_diff = self.fpath(f"{path}-server_diff", temp_dir) # diff between server file and local basefile
887+
local_diff = self.fpath(f"{path}-local_diff", temp_dir)
846888

847889
# temporary backup of file pulled from server for recovery
848-
f_server_backup = long_path(self.fpath(f"{path}-server_backup", temp_dir))
849-
self.geodiff.make_copy_sqlite(src_lp, f_server_backup)
890+
f_server_backup = self.fpath(f"{path}-server_backup", temp_dir)
891+
self.geodiff.make_copy_sqlite(src, f_server_backup)
850892

851893
# create temp backup (ideally with geodiff) of locally modified file if needed later
852-
f_conflict_file = long_path(self.fpath(f"{path}-local_backup", temp_dir))
894+
f_conflict_file = self.fpath(f"{path}-local_backup", temp_dir)
853895

854896
try:
855-
self.geodiff.create_changeset(basefile_lp, dest_lp, local_diff)
856-
self.geodiff.make_copy_sqlite(basefile_lp, f_conflict_file)
897+
self.geodiff.create_changeset(basefile, dest, local_diff)
898+
self.geodiff.make_copy_sqlite(basefile, f_conflict_file)
857899
self.geodiff.apply_changeset(f_conflict_file, local_diff)
858900
except (pygeodiff.GeoDiffLibError, pygeodiff.GeoDiffLibConflictError):
859901
self.log.info("backup of local file with geodiff failed - need to do hard copy")
860-
self.geodiff.make_copy_sqlite(dest_lp, f_conflict_file)
902+
self.geodiff.make_copy_sqlite(dest, f_conflict_file)
861903

862904
# in case there will be any conflicting operations found during rebase,
863905
# they will be stored in a JSON file - if there are no conflicts, the file
864906
# won't even be created
865-
rebase_conflicts = long_path(
866-
unique_path_name(edit_conflict_file_name(self.fpath(path), user_name, int_version(self.version())))
907+
rebase_conflicts = unique_path_name(
908+
edit_conflict_file_name(self.fpath(path), user_name, int_version(self.version()))
867909
)
868910

869911
# try to do rebase magic
870912
try:
871-
self.geodiff.create_changeset(basefile_lp, src_lp, server_diff)
872-
self.geodiff.rebase(basefile_lp, src_lp, dest_lp, rebase_conflicts)
913+
self.geodiff.create_changeset(basefile, src, server_diff)
914+
self.geodiff.rebase(basefile, src, dest, rebase_conflicts)
873915
# make sure basefile is in the same state as remote server file (for calc of push changes)
874-
self.geodiff.apply_changeset(basefile_lp, server_diff)
916+
self.geodiff.apply_changeset(basefile, server_diff)
875917
self.log.info("rebase successful!")
876918
except (pygeodiff.GeoDiffLibError, pygeodiff.GeoDiffLibConflictError) as err:
877919
self.log.warning("rebase failed! going to create conflict file")
878920
try:
879921
# it would not be possible to commit local changes, they need to end up in new conflict file
880-
self.geodiff.make_copy_sqlite(f_conflict_file, dest_lp)
922+
self.geodiff.make_copy_sqlite(f_conflict_file, dest)
881923
conflict = self.create_conflicted_copy(path, user_name)
882924
# original file synced with server
883-
self.geodiff.make_copy_sqlite(f_server_backup, basefile_lp)
884-
self.geodiff.make_copy_sqlite(f_server_backup, dest_lp)
925+
self.geodiff.make_copy_sqlite(f_server_backup, basefile)
926+
self.geodiff.make_copy_sqlite(f_server_backup, dest)
885927
return conflict
886928
except pygeodiff.GeoDiffLibError as err:
887929
self.log.warning("creation of conflicted copy failed! going to create an unfinished pull")
888-
f_server_unfinished = long_path(self.fpath_unfinished_pull(path))
930+
f_server_unfinished = self.fpath_unfinished_pull(path)
889931
self.geodiff.make_copy_sqlite(f_server_backup, f_server_unfinished)
890932

891933
return ""
@@ -911,27 +953,22 @@ def update_without_rebase(self, path, src, dest, basefile, temp_dir):
911953
:type temp_dir: str
912954
"""
913955
self.log.info("updating file without rebase: " + path)
914-
src_lp = long_path(src)
915-
dest_lp = long_path(dest)
916-
basefile_lp = long_path(basefile)
917956
try:
918-
server_diff = long_path(
919-
self.fpath(f"{path}-server_diff", temp_dir)
920-
) # diff between server file and local basefile
957+
server_diff = self.fpath(f"{path}-server_diff", temp_dir) # diff between server file and local basefile
921958
# TODO: it could happen that basefile does not exist.
922959
# It was either never created (e.g. when pushing without geodiff)
923960
# or it was deleted by mistake(?) by the user. We should detect that
924961
# when starting pull and download it as well
925-
self.geodiff.create_changeset(basefile_lp, src_lp, server_diff)
926-
self.geodiff.apply_changeset(dest_lp, server_diff)
927-
self.geodiff.apply_changeset(basefile_lp, server_diff)
962+
self.geodiff.create_changeset(basefile, src, server_diff)
963+
self.geodiff.apply_changeset(dest, server_diff)
964+
self.geodiff.apply_changeset(basefile, server_diff)
928965
self.log.info("update successful")
929966
except (pygeodiff.GeoDiffLibError, pygeodiff.GeoDiffLibConflictError):
930967
self.log.warning("update failed! going to copy file")
931968
# something bad happened and we have failed to patch our local files - this should not happen if there
932969
# wasn't a schema change or something similar that geodiff can't handle.
933-
self.geodiff.make_copy_sqlite(src_lp, dest_lp)
934-
self.geodiff.make_copy_sqlite(src_lp, basefile_lp)
970+
self.geodiff.make_copy_sqlite(src, dest)
971+
self.geodiff.make_copy_sqlite(src, basefile)
935972

936973
def apply_push_changes(self, changes):
937974
"""
@@ -951,13 +988,13 @@ def apply_push_changes(self, changes):
951988
if k == "removed":
952989
os.remove(basefile_lp)
953990
elif k == "added":
954-
self.geodiff.make_copy_sqlite(long_path(self.fpath(path)), basefile_lp)
991+
self.geodiff.make_copy_sqlite(self.fpath(path), basefile)
955992
elif k == "updated":
956993
# in case for geopackage cannot be created diff (e.g. forced update with committed changes from wal file)
957994
diff = item.get("diff")
958995
if not diff:
959996
self.log.info("updating basefile (copy) for: " + path)
960-
self.geodiff.make_copy_sqlite(long_path(self.fpath(path)), basefile_lp)
997+
self.geodiff.make_copy_sqlite(self.fpath(path), basefile)
961998
else:
962999
self.log.info("updating basefile (diff) for: " + path)
9631000
# better to apply diff to previous basefile to avoid issues with geodiff tmp files
@@ -989,7 +1026,7 @@ def create_conflicted_copy(self, file: str, user_name: str):
9891026
)
9901027

9911028
if self.is_versioned_file(file):
992-
self.geodiff.make_copy_sqlite(src_lp, long_path(backup_path))
1029+
self.geodiff.make_copy_sqlite(src, backup_path)
9931030
else:
9941031
shutil.copy(src_lp, long_path(backup_path))
9951032
return backup_path
@@ -1010,10 +1047,9 @@ def apply_diffs(self, basefile, diffs):
10101047
if not self.is_versioned_file(basefile):
10111048
return error
10121049

1013-
basefile_lp = long_path(basefile)
10141050
for index, diff in enumerate(diffs):
10151051
try:
1016-
self.geodiff.apply_changeset(basefile_lp, long_path(diff))
1052+
self.geodiff.apply_changeset(basefile, diff)
10171053
except (pygeodiff.GeoDiffLibError, pygeodiff.GeoDiffLibConflictError) as e:
10181054
self.log.warning("failed to apply changeset " + diff + " to " + basefile)
10191055
error = str(e)
@@ -1067,8 +1103,8 @@ def resolve_unfinished_pull(self, user_name):
10671103
for file_name in files:
10681104
src = os.path.join(root, file_name) # already long-path-prefixed, root came from os.walk above
10691105
file_path = os.path.relpath(src, long_path(self.unfinished_pull_dir))
1070-
dest = long_path(self.fpath(file_path))
1071-
basefile = long_path(self.fpath_meta(file_path))
1106+
dest = self.fpath(file_path)
1107+
basefile = self.fpath_meta(file_path)
10721108

10731109
self.log.info("trying to resolve unfinished pull for: " + file_path)
10741110

@@ -1131,9 +1167,9 @@ def get_geodiff_changes_count(self, diff_rel_path: str):
11311167
Never raises – diagnostics/logging must not fail.
11321168
"""
11331169

1134-
diff_abs = long_path(self.fpath_meta(diff_rel_path))
1170+
diff_abs = self.fpath_meta(diff_rel_path)
11351171
try:
1136-
return pygeodiff.GeoDiff().changes_count(diff_abs)
1172+
return GeoDiffLongPath(pygeodiff.GeoDiff()).changes_count(diff_abs)
11371173
except (
11381174
pygeodiff.GeoDiffLibError,
11391175
pygeodiff.GeoDiffLibConflictError,

mergin/report.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ def create_report(mc, directory, since, to, out_file):
263263
warnings.append(f"Missing diff: {f['path']} was {f['history'][version]['change']} in {version}")
264264
continue
265265

266-
v_diff_file = long_path(mp.fpath_cache(f["history"][version]["diff"]["path"], version=version))
266+
v_diff_file = mp.fpath_cache(f["history"][version]["diff"]["path"], version=version)
267267
version_data = versions_map[version]
268268
cr = mp.geodiff.read_changeset(v_diff_file)
269269
report = changeset_report(cr, schema, mp)

0 commit comments

Comments
 (0)