From ac0599772bf40105fd23e343a52145332c6b92d7 Mon Sep 17 00:00:00 2001 From: Aaron Lichtman Date: Sun, 22 Mar 2020 06:53:30 -0500 Subject: [PATCH] Carefully reinstall .git and .gitignore files - Add root-gitignore and dotfiles-gitignore keys to config to replace default-gitignore. - Move all gitignore functionality to config. - Add tests for new .git-related operations - Bump version to v4.0 --- README.md | 4 ++++ shallow_backup/__main__.py | 12 ++++++------ shallow_backup/config.py | 16 +++++++++------- shallow_backup/constants.py | 2 +- shallow_backup/git_wrapper.py | 25 +++++++++++++++---------- shallow_backup/utils.py | 11 ++++++++--- tests/test_git_folder_moving.py | 4 ++-- tests/test_reinstall_dotfiles.py | 24 ++++++++++++++++++------ 8 files changed, 63 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index cde23453..5680f9e1 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,10 @@ If you'd like to modify which files are backed up, you have to edit the `~/.conf 1. Select the appropriate option in the CLI and follow the prompts. 2. Open the file in a text editor and make your changes. +#### .gitignore + +As of `v4.0`, any `.gitignore` changes should be made in the `shallow-backup` config file. `.gitignore` changes that are meant to apply to all directories should be under the `root-gitignore` key. Dotfile specific gitignores should be placed under the `dotfiles-gitignore` key. The original `default-gitignore` key in the config is still supported for backwards compatibility, however, converting to the new config format is strongly encouraged. + #### Output Structure --- diff --git a/shallow_backup/__main__.py b/shallow_backup/__main__.py index be5e3698..5c8c33c7 100644 --- a/shallow_backup/__main__.py +++ b/shallow_backup/__main__.py @@ -95,19 +95,19 @@ def cli(add_dot, all, configs, delete_config, destroy_backup, dotfiles, fonts, n backup_home_path = expand_to_abs_path(get_config()["backup_path"]) mkdir_warn_overwrite(backup_home_path) repo, new_git_repo_created = safe_git_init(backup_home_path) + create_gitignore(backup_home_path, "root-gitignore") - # Create default gitignore if we just ran git init - if new_git_repo_created: - safe_create_gitignore(backup_home_path) - # Prompt user for remote URL - if not remote: - git_url_prompt(repo) + # Prompt user for remote URL if needed + if new_git_repo_created and not remote: + git_url_prompt(repo) # Set remote URL from CLI arg if remote: git_set_remote(repo, remote) dotfiles_path = os.path.join(backup_home_path, "dotfiles") + create_gitignore(dotfiles_path, "dotfiles-gitignore") + configs_path = os.path.join(backup_home_path, "configs") packages_path = os.path.join(backup_home_path, "packages") fonts_path = os.path.join(backup_home_path, "fonts") diff --git a/shallow_backup/config.py b/shallow_backup/config.py index 59c68bc8..dea200bd 100644 --- a/shallow_backup/config.py +++ b/shallow_backup/config.py @@ -64,11 +64,16 @@ def get_default_config(): ".ssh", ".vim" ], - "default-gitignore": [ + "root-gitignore": [ "dotfiles/.ssh", "dotfiles/.pypirc", ".DS_Store" ], + "dotfiles-gitignore": [ + ".ssh", + ".pypirc", + ".DS_Store", + ], "config_mapping" : get_config_paths() } @@ -142,18 +147,15 @@ def show_config(): """ print_section_header("SHALLOW BACKUP CONFIG", Fore.RED) for section, contents in get_config().items(): - # Hide gitignore config - if section == "default-gitignore": - continue # Print backup path on same line - elif section == "backup_path": + if section == "backup_path": print_path_red("Backup Path:", contents) elif section == "config_mapping": - print_red_bold("Configs:") + print_red_bold("\nConfigs:") for path, dest in contents.items(): print(" {} -> {}".format(path, dest)) # Print section header and intent contents. (Dotfiles/folders) else: - print_red_bold("\n{}: ".format(section.capitalize())) + print_red_bold("\n{}: ".format(section.replace("-", " ").capitalize())) for item in contents: print(" {}".format(item)) diff --git a/shallow_backup/constants.py b/shallow_backup/constants.py index d47b690b..b5f1e513 100644 --- a/shallow_backup/constants.py +++ b/shallow_backup/constants.py @@ -1,6 +1,6 @@ class ProjInfo: PROJECT_NAME = 'shallow-backup' - VERSION = '3.4' + VERSION = '4.0' AUTHOR_GITHUB = 'alichtman' AUTHOR_FULL_NAME = 'Aaron Lichtman' DESCRIPTION = "Easily create lightweight backups of installed packages, dotfiles, and more." diff --git a/shallow_backup/git_wrapper.py b/shallow_backup/git_wrapper.py index adfee7ef..663ce96e 100644 --- a/shallow_backup/git_wrapper.py +++ b/shallow_backup/git_wrapper.py @@ -38,19 +38,24 @@ def git_set_remote(repo, remote_url): origin.fetch() -def safe_create_gitignore(dir_path): +def create_gitignore(dir_path, key): """ Creates a .gitignore file that ignores all files listed in config. + Handles backwards compatibility for the default-gitignore -> root-gitignore + change and the introduction of the dotfiles-gitignore key in v4.0. """ gitignore_path = os.path.join(dir_path, ".gitignore") - if os.path.exists(gitignore_path): - print_yellow_bold("Detected .gitignore file.") - else: - print_yellow_bold("Creating default .gitignore...") - files_to_ignore = get_config()["default-gitignore"] - with open(gitignore_path, "w+") as f: - for ignore in files_to_ignore: - f.write("{}\n".format(ignore)) + print_yellow_bold(f"Updating .gitignore file at {gitignore_path} with config from {key}") + try: + files_to_ignore = get_config()[key] + except KeyError: + if key == "root-gitignore": + files_to_ignore = get_config()["default-gitignore"] + elif key == "dotfiles-gitignore": + pass + with open(os.path.join(dir_path, ".gitignore"), "w+") as f: + for ignore in files_to_ignore: + f.write("{}\n".format(ignore)) def safe_git_init(dir_path): @@ -87,7 +92,7 @@ def git_add_all_commit_push(repo, message, separate_dotfiles_repo=False): repo.git.add(A=True) try: repo.git.commit(m=COMMIT_MSG[message]) - # Git submodule issue https://github.com/alichtman/shallow-backup/issues/229 + # Git submodule issue https://github.com/alichtman/shallow-backup/issues/229 except git.exc.GitCommandError as e: error = e.stdout.strip() error = error[error.find("\'") + 1:-1] diff --git a/shallow_backup/utils.py b/shallow_backup/utils.py index 9f01562d..ec0d4104 100644 --- a/shallow_backup/utils.py +++ b/shallow_backup/utils.py @@ -129,14 +129,19 @@ def destroy_backup_dir(backup_path): def get_abs_path_subfiles(directory): """ - Returns list of absolute paths of files and folders contained in a directory, excluding .git directories. + Returns list of absolute paths of files and folders contained in a directory, + excluding the root .git directory and root .gitignore.. """ file_paths = [] for path, _, files in os.walk(directory): for name in files: joined = os.path.join(path, name) - if "/.git/" not in joined: - file_paths.append(os.path.join(path, name)) + root_git_dir = os.path.join(directory, ".git") + root_gitignore = os.path.join(directory, ".gitignore") + if root_git_dir not in joined and root_gitignore not in joined: + file_paths.append(joined) + else: + print(f"Excluded: {joined}") return file_paths diff --git a/tests/test_git_folder_moving.py b/tests/test_git_folder_moving.py index b334bfda..a510e0a9 100644 --- a/tests/test_git_folder_moving.py +++ b/tests/test_git_folder_moving.py @@ -3,7 +3,7 @@ import shutil from .test_utils import BACKUP_DEST_DIR, FAKE_HOME_DIR, DIRS, setup_env_vars, create_config_for_test sys.path.insert(0, "../shallow_backup") -from shallow_backup.git_wrapper import move_git_repo, safe_git_init, safe_create_gitignore +from shallow_backup.git_wrapper import move_git_repo, safe_git_init, create_gitignore class TestGitFolderCopying: @@ -32,7 +32,7 @@ def test_copy_git_folder(self): Test copying the .git folder and .gitignore from an old directory to a new one """ safe_git_init(FAKE_HOME_DIR) - safe_create_gitignore(FAKE_HOME_DIR) + create_gitignore(FAKE_HOME_DIR, "root-gitignore") move_git_repo(FAKE_HOME_DIR, BACKUP_DEST_DIR) assert os.path.isdir(os.path.join(BACKUP_DEST_DIR, '.git/')) assert os.path.isfile(os.path.join(BACKUP_DEST_DIR, '.gitignore')) diff --git a/tests/test_reinstall_dotfiles.py b/tests/test_reinstall_dotfiles.py index ea6eb8da..47e0445e 100644 --- a/tests/test_reinstall_dotfiles.py +++ b/tests/test_reinstall_dotfiles.py @@ -29,6 +29,14 @@ def create_file(parent, name): with open(file, "w+") as f: f.write(TEST_TEXT_CONTENT) + def create_git_dir(parent): + git_dir = create_dir(parent, ".git/") + git_objects = create_dir(git_dir, "objects/") + create_file(git_dir, "config") + create_file(git_objects, "obj1") + return git_dir + + setup_env_vars() create_config_for_test() for directory in DIRS: @@ -49,14 +57,13 @@ def create_file(parent, name): testfolder = create_dir(DOTFILES_PATH, "testfolder1/") testfolder2 = create_dir(testfolder, "testfolder2/") - # Git dir that should not be reinstalled - git = create_dir(DOTFILES_PATH, ".git/") - git_objects = create_dir(git, "objects/") - create_file(git, "config") - create_file(git_objects, "obj1") + git_dir_should_not_reinstall = create_git_dir(DOTFILES_PATH) + git_dir_should_reinstall = create_git_dir(testfolder2) # SAMPLE DOTFILES TO REINSTALL create_file(testfolder2, ".testsubfolder_rc1") + create_file(testfolder2, ".gitignore") + create_file(DOTFILES_PATH, ".gitignore") create_file(testfolder2, ".testsubfolder_rc2") create_file(DOTFILES_PATH, ".testrc") @@ -76,4 +83,9 @@ def test_reinstall_dotfiles(self): assert os.path.isfile(os.path.join(testfolder2, '.testsubfolder_rc1')) assert os.path.isfile(os.path.join(testfolder2, '.testsubfolder_rc2')) - assert not os.path.isdir(os.path.join(FAKE_HOME_DIR, '.git')) + # Don't reinstall root-level git files + assert not os.path.isdir(os.path.join(FAKE_HOME_DIR, ".git")) + assert not os.path.isfile(os.path.join(FAKE_HOME_DIR, ".gitignore")) + # Do reinstall all other git files + assert os.path.isdir(os.path.join(testfolder2, ".git")) + assert os.path.isfile(os.path.join(testfolder2, ".gitignore"))