def get_cruft_file(project_dir_path: Path, exists: bool = True) -> Path: cruft_file = project_dir_path / ".cruft.json" if not exists and cruft_file.is_file(): raise CruftAlreadyPresent(cruft_file) if exists and not cruft_file.is_file(): raise NoCruftFound(project_dir_path.resolve()) return cruft_file
def check(expanded_dir: str = ".") -> bool: """Checks to see if there have been any updates to the Cookiecutter template used to generate this project. """ expanded_dir_path = Path(expanded_dir) cruft_file = expanded_dir_path / ".cruft.json" if not cruft_file.is_file(): raise NoCruftFound(expanded_dir_path.resolve()) cruft_state = json.loads(cruft_file.read_text()) with TemporaryDirectory() as cookiecutter_template_dir: repo = Repo.clone_from(cruft_state["template"], cookiecutter_template_dir) last_commit = repo.head.object.hexsha if last_commit == cruft_state["commit"] or not repo.index.diff( cruft_state["commit"]): return True return False
def check(expanded_dir: str = ".") -> bool: """Checks to see if there have been any updates to the Cookiecutter template used to generate this project. """ cruft_file = os.path.join(expanded_dir, ".cruft.json") if not os.path.isfile(cruft_file): raise NoCruftFound(os.path.abspath(expanded_dir)) with open(cruft_file) as cruft_open_file: cruft_state = json.load(cruft_open_file) with TemporaryDirectory() as cookiecutter_template_dir: repo = Repo.clone_from(cruft_state["template"], cookiecutter_template_dir) last_commit = repo.head.object.hexsha if last_commit == cruft_state["commit"] or not repo.index.diff( cruft_state["commit"]): return True return False
def update( expanded_dir: str = ".", cookiecutter_input: bool = False, skip_apply_ask: bool = False, skip_update: bool = False, ) -> bool: """Update specified project's cruft to the latest and greatest release.""" expanded_dir_path = Path(expanded_dir) pyproject_file = expanded_dir_path / "pyproject.toml" cruft_file = expanded_dir_path / ".cruft.json" if not cruft_file.is_file(): raise NoCruftFound(cruft_file) cruft_state = json.loads(cruft_file.read_text()) skip_cruft = cruft_state.get("skip", []) if toml and pyproject_file.is_file(): pyproject_cruft = toml.loads(pyproject_file.read_text()).get( "tool", {}).get("cruft", {}) skip_cruft.extend(pyproject_cruft.get("skip", [])) with TemporaryDirectory() as compare_directory_str: compare_directory = Path(compare_directory_str) template_dir = compare_directory / "template" try: repo = Repo.clone_from(cruft_state["template"], template_dir) last_commit = repo.head.object.hexsha except Exception as e: # pragma: no cover raise InvalidCookiecutterRepository(e) if last_commit == cruft_state["commit"] or not repo.index.diff( cruft_state["commit"]): return False directory = cruft_state.get("directory", "") template_dir = template_dir / directory context_file = template_dir / "cookiecutter.json" new_output_dir = compare_directory / "new_output" new_context = _generate_output( context_file=str(context_file), cruft_state=cruft_state, cookiecutter_input=cookiecutter_input, template_dir=str(template_dir), output_dir=str(new_output_dir), ) old_output_dir = compare_directory / "old_output" repo.head.reset(commit=cruft_state["commit"], working_tree=True) _generate_output( context_file=str(context_file), cruft_state=cruft_state, cookiecutter_input=cookiecutter_input, template_dir=str(template_dir), output_dir=str(old_output_dir), ) for dir_item in old_output_dir.glob("*"): if dir_item.is_dir(): old_main_directory = dir_item new_main_directory = new_output_dir / old_main_directory.name for skip_file in skip_cruft: file_path_old = old_main_directory / skip_file file_path_new = new_main_directory / skip_file for file_path in (file_path_old, file_path_new): if file_path.is_dir(): rmtree(file_path) elif file_path.is_file(): file_path.unlink() diff = run( [ "git", "diff", "--no-index", str(old_main_directory), str(new_main_directory) ], stdout=PIPE, stderr=PIPE, ).stdout.decode("utf8") diff = (diff.replace("\\\\", "\\").replace(str(old_main_directory), "").replace(str(new_main_directory), "")) print("The following diff would be applied:\n") print(diff) print("") if not skip_apply_ask and not skip_update: # pragma: no cover update_str: str = "" while update_str not in ("y", "n", "s"): print( 'Respond with "s" to intentionally skip the update while marking ' "your project as up-to-date.") update_str = input( "Apply diff and update [y/n/s]? ").lower() # nosec if update_str == "n": sys.exit("User cancelled Cookiecutter template update.") elif update_str == "s": skip_update = True current_directory = Path.cwd() try: os.chdir(expanded_dir_path) if not skip_update: run(["patch", "-p1", "--merge"], input=diff.encode("utf8")) cruft_state["commit"] = last_commit cruft_state["context"] = new_context cruft_state["directory"] = directory cruft_file.write_text(json_dumps(cruft_state)) finally: os.chdir(current_directory) return True
def update(expanded_dir: str = ".", cookiecutter_input: bool = False, skip_apply_ask: bool = False) -> bool: """Update specified project's cruft to the latest and greatest release.""" cruft_file = os.path.join(expanded_dir, ".cruft.json") if not os.path.isfile(cruft_file): raise NoCruftFound(os.path.abspath(expanded_dir)) with open(cruft_file) as cruft_open_file: cruft_state = json.load(cruft_open_file) with TemporaryDirectory() as compare_directory: template_dir = os.path.join(compare_directory, "template") try: repo = Repo.clone_from(cruft_state["template"], template_dir) last_commit = repo.head.object.hexsha except Exception as e: raise InvalidCookiecutterRepository(e) if last_commit == cruft_state["commit"] or not repo.index.diff( cruft_state["commit"]): return False context_file = os.path.join(template_dir, "cookiecutter.json") new_output_dir = os.path.join(compare_directory, "new_output") new_context = _generate_output( context_file=context_file, cruft_state=cruft_state, cookiecutter_input=cookiecutter_input, template_dir=template_dir, output_dir=new_output_dir, ) old_output_dir = os.path.join(compare_directory, "old_output") repo.head.reset(commit=cruft_state["commit"], working_tree=True) _generate_output( context_file=context_file, cruft_state=cruft_state, cookiecutter_input=cookiecutter_input, template_dir=template_dir, output_dir=old_output_dir, ) main_directory: str = "" for file_name in os.listdir(old_output_dir): file_path = os.path.join(old_output_dir, file_name) if os.path.isdir(file_path): main_directory = file_name new_main_directory = os.path.join(new_output_dir, main_directory) old_main_directory = os.path.join(old_output_dir, main_directory) diff = run(["git", "diff", old_main_directory, new_main_directory], capture_output=True).stdout.decode("utf8") diff = diff.replace(old_main_directory, "").replace(new_main_directory, "") print("The following diff would be applied:\n") print(diff) print("") if not skip_apply_ask: update = "" while update.lower() not in ("y", "n"): update = input("Apply diff and update [y/n]? ") # nosec if update.lower() == "n": sys.exit("User cancelled Cookiecutter template update.") current_directory = os.getcwd() try: os.chdir(expanded_dir) run(["git", "apply"], input=diff.encode("utf8")) cruft_state["commit"] = last_commit cruft_state["context"] = new_context with open(cruft_file, "w") as cruft_output: json_dump(cruft_state, cruft_output) finally: os.chdir(current_directory) return True