diff --git a/shallow_backup/printing.py b/shallow_backup/printing.py index f16bd5ed..ee0857c8 100644 --- a/shallow_backup/printing.py +++ b/shallow_backup/printing.py @@ -3,7 +3,7 @@ import inquirer from colorama import Fore, Style from .constants import ProjInfo - +from typing import List, Set, Union def print_blue(text): print(Fore.BLUE + text + Style.RESET_ALL) @@ -53,6 +53,13 @@ def print_path_green(text, path): print(Fore.GREEN + Style.BRIGHT + text, Style.NORMAL + path + Style.RESET_ALL) +def print_list_pretty(items_to_print: Union[Set, List], style=Style.BRIGHT, color=Fore.RED): + print(f"{style}{color}") + for x in items_to_print: + print(f" - {x}") + print(Style.RESET_ALL) + + def print_dry_run_copy_info(source, dest): """Show source -> dest copy. Replaces expanded ~ with ~ if it's at the beginning of paths. source and dest are trimmed in the middle if needed. Removed characters will be replaced by ... diff --git a/shallow_backup/reinstall.py b/shallow_backup/reinstall.py index cdf67260..7fd68c40 100644 --- a/shallow_backup/reinstall.py +++ b/shallow_backup/reinstall.py @@ -5,13 +5,13 @@ get_abs_path_subfiles, exit_if_dir_is_empty, safe_mkdir, - evaluate_condition, + evaluate_condition, find_path_for_permission_error_reporting, ) from .printing import * from colorama import Fore, Style from .compatibility import * from .config import get_config -from pathlib import Path +from pathlib import Path, PurePath from shutil import copytree, copyfile, copy # NOTE: Naming convention is like this since the CLI flags would otherwise @@ -67,6 +67,7 @@ def reinstall_dots_sb( dest = source.replace(dots_path, home_path + "/") full_path_dotfiles_to_reinstall.append((Path(source), Path(dest))) + files_with_permission_errors = set() # Copy files from backup to system for dot_source, dot_dest in full_path_dotfiles_to_reinstall: if dry_run: @@ -88,12 +89,17 @@ def reinstall_dots_sb( try: copy(dot_source, dot_dest) except PermissionError as err: - print_red_bold(f"ERROR: {err}") + files_with_permission_errors.add(find_path_for_permission_error_reporting(err.filename)) except FileNotFoundError as err: print_red_bold(f"ERROR: {err}") if reinstallation_error_count != 0: - print_red_bold(f"\n\nSome errors which require manual resolution detected.") + print_red_bold(f"\nSome errors which require manual resolution detected.") + + num_permission_errors = len(files_with_permission_errors) + if num_permission_errors != 0: + print_red_bold(f"\n{num_permission_errors} permission errors detected. Most of the time, this is not a problem.\nGit repos will have some read-only files, and will prevent you from writing to them without using sudo.\nAdditionally, some package managers (like zcomet, etc) make their install files read-only.\nYou should update these files using the respective tools that created them.\nThe following paths were problematic:") + print_list_pretty(sorted(files_with_permission_errors)) print_section_header("DOTFILE REINSTALLATION COMPLETED", Fore.BLUE) diff --git a/shallow_backup/utils.py b/shallow_backup/utils.py index ac2a20ff..8e8c7503 100644 --- a/shallow_backup/utils.py +++ b/shallow_backup/utils.py @@ -3,7 +3,8 @@ from shlex import split import shutil from shutil import rmtree, copytree, copyfile -from typing import List, Union +from pathlib import Path +from re import fullmatch from .printing import * @@ -240,3 +241,21 @@ def strip_home(full_path): return full_path.replace(home_path + "/", "") else: return full_path + + +def find_path_for_permission_error_reporting(path_maybe_containing_git_dir: str): + """Given a path containing a git directory, return the path to the git dir. + This will be used to create a set of git dirs we ran into errors while reinstalling. + ~/.config/zsh/.zinit/plugins/changyuheng---zsh-interactive-cd/.git/objects/62/5373abf600839f2fdcd5c6d13184a1fe6dc708 + will turn into + ~/.config/zsh/.zinit/plugins/changyuheng---zsh-interactive-cd/.git""" + + if not fullmatch(".*/.git/(.*/)?objects/.*", path_maybe_containing_git_dir): + return path_maybe_containing_git_dir + + candidate_for_git_repo_home = Path(path_maybe_containing_git_dir).parent + while candidate_for_git_repo_home.name != ".git": + candidate_for_git_repo_home = candidate_for_git_repo_home.parent + + return str(candidate_for_git_repo_home) +