Skip to content

Instantly share code, notes, and snippets.

@pinxau1000
Last active December 20, 2021 14:20
Show Gist options
  • Save pinxau1000/a1c8ccf5d9dc674c74e618b0985f5340 to your computer and use it in GitHub Desktop.
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.
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