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+
4391class 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 ,
0 commit comments