11use std:: borrow:: Cow ;
22use std:: io:: Write ;
33use std:: path:: Path ;
4+ use std:: fs;
45
56use anyhow:: { anyhow, bail, Context as _, Result } ;
67use clap:: { Arg , ArgAction , ArgMatches , Command } ;
@@ -12,6 +13,9 @@ use symbolic::common::ByteView;
1213use zip:: write:: SimpleFileOptions ;
1314use zip:: { DateTime , ZipWriter } ;
1415
16+ #[ cfg( not( windows) ) ]
17+ use std:: os:: unix:: fs:: PermissionsExt ;
18+
1519use crate :: api:: { Api , AuthenticatedApi , ChunkUploadCapability } ;
1620use crate :: config:: Config ;
1721use crate :: utils:: args:: ArgExt ;
@@ -261,16 +265,26 @@ fn normalize_directory(path: &Path) -> Result<TempFile> {
261265 . into_iter ( )
262266 . sorted_by ( |( _, a) , ( _, b) | a. cmp ( b) ) ;
263267
264- // Need to set the last modified time to a fixed value to ensure consistent checksums
265- // This is important as an optimization to avoid re-uploading the same chunks if they're already on the server
266- // but the last modified time being different will cause checksums to be different.
267- let options = SimpleFileOptions :: default ( )
268- . compression_method ( zip:: CompressionMethod :: Stored )
269- . last_modified_time ( DateTime :: default ( ) ) ;
270-
271268 for ( entry_path, relative_path) in entries {
272269 debug ! ( "Adding file to zip: {}" , relative_path. display( ) ) ;
273270
271+ // Get file metadata to preserve permissions
272+ let metadata = fs:: metadata ( & entry_path) ?;
273+
274+ // Need to set the last modified time to a fixed value to ensure consistent checksums
275+ // This is important as an optimization to avoid re-uploading the same chunks if they're already on the server
276+ // but the last modified time being different will cause checksums to be different.
277+ let mut options = SimpleFileOptions :: default ( )
278+ . compression_method ( zip:: CompressionMethod :: Stored )
279+ . last_modified_time ( DateTime :: default ( ) ) ;
280+
281+ // Preserve Unix file permissions on non-Windows systems
282+ #[ cfg( not( windows) ) ]
283+ {
284+ let mode = metadata. permissions ( ) . mode ( ) ;
285+ options = options. unix_permissions ( mode) ;
286+ }
287+
274288 zip. start_file ( relative_path. to_string_lossy ( ) , options) ?;
275289 let file_byteview = ByteView :: open ( & entry_path) ?;
276290 zip. write_all ( file_byteview. as_slice ( ) ) ?;
0 commit comments