From 8987339e34aec7878d4f5a22ac47aec066756bcb Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Fri, 31 Mar 2023 14:07:49 +0100 Subject: [PATCH] fix: properly support diffs over updates with new interactive answers Just like 802d0f96548c3c8a0bf1128f8f18da4cf5ca05ad, but this time we make sure that interactive answers also work as expected. Basically, I moved the creation of the updated and clean worker after the copying of the real destination worker. With this change, we can ask the user for answers and make sure we use them when producing the clean and updated copy. @moduon MT-1075 --- copier/main.py | 29 ++++++++++++++--------------- tests/test_updatediff.py | 35 +++++++++++++++++++++++++++++++---- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/copier/main.py b/copier/main.py index b73bd7763..a30d6419c 100644 --- a/copier/main.py +++ b/copier/main.py @@ -700,12 +700,10 @@ def _apply_update(self): ) subproject_subdir = self.subproject.local_abspath.relative_to(subproject_top) - # Copy old template into a temporary destination with TemporaryDirectory( - prefix=f"{__name__}.update_diff." - ) as old_copy, TemporaryDirectory( - prefix=f"{__name__}.recopy_diff." - ) as new_copy: + prefix=f"{__name__}.old_copy." + ) as old_copy, TemporaryDirectory(prefix=f"{__name__}.new_copy.") as new_copy: + # Copy old template into a temporary destination old_worker = replace( self, dst_path=old_copy / subproject_subdir, @@ -716,16 +714,6 @@ def _apply_update(self): vcs_ref=self.subproject.template.commit, ) old_worker.run_copy() - new_worker = replace( - self, - dst_path=new_copy / subproject_subdir, - data=replace(self, defaults=True).answers.combined, - defaults=True, - quiet=True, - src_path=self.subproject.template.url, - ) - new_worker.run_copy() - compared = dircmp(old_copy, new_copy) # Extract diff between temporary destination and real destination with local.cwd(old_copy): self._git_initialize_repo() @@ -752,6 +740,17 @@ def _apply_update(self): del self.subproject.last_answers # Do a normal update in final destination self.run_copy() + # Render with the same answers in an empty dir to avoid pollution + new_worker = replace( + self, + dst_path=new_copy / subproject_subdir, + data=self.answers.combined, + defaults=True, + quiet=True, + src_path=self.subproject.template.url, + ) + new_worker.run_copy() + compared = dircmp(old_copy, new_copy) # Try to apply cached diff into final destination with local.cwd(subproject_top): apply_cmd = git["apply", "--reject", "--exclude", self.answers_relpath] diff --git a/tests/test_updatediff.py b/tests/test_updatediff.py index 4a5a028f0..8c372aa3b 100644 --- a/tests/test_updatediff.py +++ b/tests/test_updatediff.py @@ -4,6 +4,7 @@ from textwrap import dedent from typing import Optional +import pexpect import pytest import yaml from plumbum import local @@ -14,7 +15,13 @@ from copier.main import run_copy, run_update from copier.types import Literal -from .helpers import BRACKET_ENVOPS_JSON, SUFFIX_TMPL, build_file_tree +from .helpers import ( + BRACKET_ENVOPS_JSON, + COPIER_PATH, + SUFFIX_TMPL, + Spawn, + build_file_tree, +) @pytest.mark.impure @@ -632,8 +639,9 @@ def test_file_removed(tmp_path_factory: pytest.TempPathFactory) -> None: assert not (dst / "dir 5").exists() +@pytest.mark.parametrize("interactive", [True, False]) def test_update_inline_changed_answers_and_questions( - tmp_path_factory: pytest.TempPathFactory, + tmp_path_factory: pytest.TempPathFactory, interactive: bool, spawn: Spawn ) -> None: src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) with local.cwd(src): @@ -677,7 +685,14 @@ def test_update_inline_changed_answers_and_questions( git("commit", "-am2") git("tag", "2") # Init project - run_copy(str(src), dst, data={"b": True}, vcs_ref="1") + if interactive: + tui = spawn(COPIER_PATH + ("-r1", "copy", str(src), str(dst)), timeout=10) + tui.expect_exact("b (bool)") + tui.expect_exact("(y/N)") + tui.send("y") + tui.expect_exact(pexpect.EOF) + else: + run_copy(str(src), dst, data={"b": True}, vcs_ref="1") assert "ccc" not in (dst / "content").read_text() with local.cwd(dst): git("init") @@ -696,7 +711,19 @@ def test_update_inline_changed_answers_and_questions( ) git("commit", "-am2") # Update from template, inline, with answer changes - run_update(data={"c": True}, defaults=True, overwrite=True, conflict="inline") + if interactive: + tui = spawn(COPIER_PATH + ("-w", "update", "--conflict=inline"), timeout=10) + tui.expect_exact("b (bool)") + tui.expect_exact("(Y/n)") + tui.sendline() + tui.expect_exact("c (bool)") + tui.expect_exact("(y/N)") + tui.send("y") + tui.expect_exact(pexpect.EOF) + else: + run_update( + data={"c": True}, defaults=True, overwrite=True, conflict="inline" + ) assert Path("content").read_text() == dedent( """\ aaa