Last active
December 20, 2021 14:20
-
-
Save pinxau1000/a1c8ccf5d9dc674c74e618b0985f5340 to your computer and use it in GitHub Desktop.
Copies Directory Structure Preserving the Permissions. Can also Overwrite Existing Files/Directories and Copy Files. Can Filter Files and/or Directories.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import argparse | |
import filecmp | |
import os | |
import re | |
import shutil | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser(description="Copies a source dir structure recursively to a destiny dir.") | |
parser.add_argument("source_dir", type=os.path.normpath, help="Path to the source directory root.") | |
parser.add_argument("dest_dir", type=os.path.normpath, help="Path to the destiny directory root.") | |
parser.add_argument("-if", "--include_files", action="store_true", | |
help="If passed then files are also copied.") | |
parser.add_argument("-pp", "--preserve_perm", action="store_true", | |
help="If passed then dest permissions are copied from the root permissions.") | |
parser.add_argument("-oe", "--overwrite_existing", action="store_true", | |
help="If passed then files or directories that already exist in dest_dir are overwritten.") | |
parser.add_argument("--no_check", action="store_true", | |
help="If passed then files and directories are not compared against their the source.") | |
parser.add_argument("--dir_filter", type=re.compile, default=".*", | |
help="Only directories matching this filter are copied. Use glob syntax.") | |
parser.add_argument("--file_filter", type=re.compile, default=".*", | |
help="Only files matching this filter are copied. Be aware that --include_files must be " | |
"passed to copy files. Use glob syntax.") | |
args = parser.parse_args() | |
source_d_perm_mask = os.stat(args.source_dir).st_mode & 0o777 | |
os.chmod(path=args.source_dir, mode=source_d_perm_mask | 0o700) # 0o700 ensures write permissions on src directory! | |
sanity_check_ignore_list = [] | |
for root, dirs, files in list(os.walk(args.source_dir)): | |
for d in dirs: | |
# Directory path: https://www.geeksforgeeks.org/how-to-get-the-permission-mask-of-a-file-in-python/ | |
common_path = os.path.commonpath([args.source_dir, root]) | |
dest_path = os.path.join(args.dest_dir + root.replace(common_path, ""), d) | |
dest_d_perm_mask = os.stat(os.path.join(root, d)).st_mode & 0o777 | |
if not re.match(args.dir_filter, d): | |
sanity_check_ignore_list += [ | |
dest_path, | |
os.path.join(root, d) | |
] | |
continue | |
# Change the permission of root dir to allow dir creation | |
root_d_perm_mask = os.stat(root).st_mode & 0o777 | |
os.chmod(path=root, mode=root_d_perm_mask | 0o700) # 0o700 ensures write permissions on dst directory! | |
if os.path.isdir(dest_path): | |
if args.overwrite_existing: | |
# Removes and creates a new dir if overwrite is true | |
shutil.rmtree(path=dest_path) | |
os.mkdir(path=dest_path) | |
if args.preserve_perm: | |
# Change the permissions if preserve_perm is true | |
os.chmod(path=dest_path, mode=dest_d_perm_mask) | |
else: | |
# Makes dir | |
os.mkdir(dest_path) | |
# Change the permissions if preserve_perm is true | |
if args.preserve_perm: | |
os.chmod(path=dest_path, mode=dest_d_perm_mask) | |
# restore permissions of root dir | |
os.chmod(path=root, mode=root_d_perm_mask) # 0o100 ensures write permissions on dst directory! | |
if args.include_files: | |
for f in files: | |
# File path | |
common_path = os.path.commonpath([args.source_dir, root]) | |
dest_path = os.path.join(args.dest_dir + root.replace(common_path, ""), f) | |
src_path = os.path.join(root, f) | |
if not re.match(args.file_filter, f): | |
sanity_check_ignore_list += [ | |
dest_path, | |
src_path | |
] | |
continue | |
# Change the permission of dst dir and src file to allow copy | |
root_d_perm_mask = os.stat(root).st_mode & 0o777 | |
source_f_perm_mask = os.stat(src_path).st_mode & 0o777 | |
os.chmod(path=root, mode=root_d_perm_mask | 0o700) # 0o100 ensures write permissions on dst directory! | |
os.chmod(path=src_path, mode=source_f_perm_mask | 0o700) # 0o001 ensures copy permissions on src file! | |
if os.path.isfile(dest_path): | |
if args.overwrite_existing: | |
# Removes and copies the source file if overwrite is true | |
os.remove(path=dest_path) | |
shutil.copy(src=src_path, dst=dest_path) | |
if args.preserve_perm: | |
# Change the permissions if preserve_perm is true | |
os.chmod(path=dest_path, mode=source_f_perm_mask) | |
else: | |
shutil.copy(src=src_path, dst=dest_path) | |
# Change the permissions if preserve_perm is true | |
if args.preserve_perm: | |
os.chmod(path=dest_path, mode=source_f_perm_mask) | |
# restore permissions of dst dir and src file | |
os.chmod(path=root, mode=root_d_perm_mask) # 0o100 ensures copy permissions on source file! | |
os.chmod(path=src_path, mode=source_f_perm_mask) # 0o100 ensures copy permissions on source file! | |
if not args.no_check: | |
dir_comp = filecmp.dircmp(a=args.source_dir, b=args.dest_dir, ignore=sanity_check_ignore_list) | |
print_str = "" | |
if len(dir_comp.diff_files) != 0: | |
print_str += f"Files differ: {dir_comp.diff_files}.\n" | |
if len(dir_comp.funny_files) != 0: | |
print_str += f"Files could not be compared: {dir_comp.funny_files}.\n" | |
if len(dir_comp.left_only) != 0: | |
print_str += f"Files and Dirs could not be copied to destiny: {dir_comp.left_only}.\n" | |
if len(dir_comp.right_only) != 0: | |
print_str += f"Files and Dirs in destiny not in source: {dir_comp.right_only}.\n" | |
if print_str != "": | |
print("Full `os.dircmp` report:") | |
print(dir_comp.report_full_closure()) | |
print("") | |
raise ValueError(f"Source and Destiny didn't match!\n{print_str}") | |
else: | |
# Restores source dir permissions | |
os.chmod(path=args.source_dir, mode=source_d_perm_mask) | |
print("Success!") | |
else: | |
# Restores source dir permissions | |
os.chmod(path=args.source_dir, mode=source_d_perm_mask) | |
print("Success!") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment