Skip to content

Commit

Permalink
refactor: overwrite always on updates
Browse files Browse the repository at this point in the history
Updates not only overwrite files when actually updating, but also when applying the git diff.

Since only git-tracked subprojects can be updated, in reality there's no problem when overwriting. I find myself always overwriting and later fixing stuff with git tools.

BREAKING CHANGE: Updates will overwrite existing files always. If you need to select only some files, just use `git mergetool` or `git difftool` after updating.

BREAKING CHANGE: Flag `--overwrite/-w` disappeared from `copier update`. It is now implicit.

BREAKING CHANGE: To update via API, `overwrite=True` is now required.

Fix #741.
  • Loading branch information
yajo committed May 29, 2023
1 parent 2afddf6 commit 8a1b5d5
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 22 deletions.
49 changes: 35 additions & 14 deletions copier/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,18 +129,6 @@ class _Subcommand(cli.Application):
),
)
pretend = cli.Flag(["-n", "--pretend"], help="Run but do not make any changes")
force = cli.Flag(
["-f", "--force"],
help="Same as `--defaults --overwrite`.",
)
defaults = cli.Flag(
["-l", "--defaults"],
help="Use default answers to questions, which might be null if not specified.",
)
overwrite = cli.Flag(
["-w", "--overwrite"],
help="Overwrite files that already exist, without asking.",
)
skip = cli.SwitchAttr(
["-s", "--skip"],
str,
Expand Down Expand Up @@ -185,8 +173,6 @@ def _worker(self, src_path: OptStr = None, dst_path: str = ".", **kwargs) -> Wor
dst_path=Path(dst_path),
answers_file=self.answers_file,
exclude=self.exclude,
defaults=self.force or self.defaults,
overwrite=self.force or self.overwrite,
pretend=self.pretend,
skip_if_exists=self.skip,
quiet=self.quiet,
Expand All @@ -212,6 +198,18 @@ class CopierCopySubApp(_Subcommand):
default=True,
help="On error, do not delete destination if it was created by Copier.",
)
defaults = cli.Flag(
["-l", "--defaults"],
help="Use default answers to questions, which might be null if not specified.",
)
force = cli.Flag(
["-f", "--force"],
help="Same as `--defaults --overwrite`.",
)
overwrite = cli.Flag(
["-w", "--overwrite"],
help="Overwrite files that already exist, without asking.",
)

@handle_exceptions
def main(self, template_src: str, destination_path: str) -> int:
Expand All @@ -230,6 +228,8 @@ def main(self, template_src: str, destination_path: str) -> int:
template_src,
destination_path,
cleanup_on_error=self.cleanup_on_error,
defaults=self.force or self.defaults,
overwrite=self.force or self.overwrite,
).run_copy()
return 0

Expand Down Expand Up @@ -260,6 +260,19 @@ class CopierRecopySubApp(_Subcommand):
"""
)

defaults = cli.Flag(
["-l", "--defaults"],
help="Use default answers to questions, which might be null if not specified.",
)
force = cli.Flag(
["-f", "--force"],
help="Same as `--defaults --overwrite`.",
)
overwrite = cli.Flag(
["-w", "--overwrite"],
help="Overwrite files that already exist, without asking.",
)

@handle_exceptions
def main(self, destination_path: cli.ExistingDirectory = ".") -> int:
"""Call [run_recopy][copier.main.Worker.run_recopy].
Expand All @@ -274,6 +287,8 @@ def main(self, destination_path: cli.ExistingDirectory = ".") -> int:
"""
self._worker(
dst_path=destination_path,
defaults=self.force or self.defaults,
overwrite=self.force or self.overwrite,
).run_recopy()
return 0

Expand Down Expand Up @@ -318,6 +333,10 @@ class CopierUpdateSubApp(_Subcommand):
"accuracy, decrease for resilience."
),
)
defaults = cli.Flag(
["-l", "-f", "--defaults"],
help="Use default answers to questions, which might be null if not specified.",
)

@handle_exceptions
def main(self, destination_path: cli.ExistingDirectory = ".") -> int:
Expand All @@ -335,6 +354,8 @@ def main(self, destination_path: cli.ExistingDirectory = ".") -> int:
dst_path=destination_path,
conflict=self.conflict,
context_lines=self.context_lines,
defaults=self.defaults,
overwrite=True,
).run_update()
return 0

Expand Down
5 changes: 5 additions & 0 deletions copier/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,11 @@ def run_update(self) -> None:
f"Your are downgrading from {self.subproject.template.version} to {self.template.version}. "
"Downgrades are not supported."
)
if not self.overwrite:
# Only git-tracked subprojects can be updated, so the user can
# review the diff before committing; so we can safely avoid
# asking for confirmation
raise UserMessageError("Enable overwrite to update a subproject.")
if not self.quiet:
# TODO Unify printing tools
print(
Expand Down
2 changes: 1 addition & 1 deletion tests/test_conditional_file_name.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def test_answer_changes(
git("commit", "-mv1")

if interactive:
tui = spawn(COPIER_PATH + ("update", "--overwrite", str(dst)), timeout=10)
tui = spawn(COPIER_PATH + ("update", str(dst)), timeout=10)
expect_prompt(tui, "condition", "bool")
tui.expect_exact("(Y/n)")
tui.sendline("n")
Expand Down
18 changes: 11 additions & 7 deletions tests/test_updatediff.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from plumbum.cmd import git

from copier.cli import CopierApp
from copier.errors import UserMessageError
from copier.main import Worker, run_copy, run_update
from copier.types import Literal

Expand Down Expand Up @@ -201,7 +202,7 @@ def test_updatediff(tmp_path_factory: pytest.TempPathFactory) -> None:
)
commit("-m", "I prefer grog")
# Update target to latest tag and check it's updated in answers file
CopierApp.run(["copier", "update", "--defaults", "--overwrite"], exit=False)
CopierApp.run(["copier", "update", "--defaults"], exit=False)
assert answers.read_text() == dedent(
f"""\
# Changes here will be overwritten by Copier
Expand All @@ -223,7 +224,7 @@ def test_updatediff(tmp_path_factory: pytest.TempPathFactory) -> None:
commit("-m", "Update template to v0.0.2")
# Update target to latest commit, which is still untagged
CopierApp.run(
["copier", "update", "--defaults", "--overwrite", "--vcs-ref=HEAD"],
["copier", "update", "--defaults", "--vcs-ref=HEAD"],
exit=False,
)
# Check no new migrations were executed
Expand Down Expand Up @@ -258,7 +259,7 @@ def test_updatediff(tmp_path_factory: pytest.TempPathFactory) -> None:
assert not git("status", "--porcelain")
# No more updates exist, so updating again should change nothing
CopierApp.run(
["copier", "update", "--defaults", "--overwrite", "--vcs-ref=HEAD"],
["copier", "update", "--defaults", "--vcs-ref=HEAD"],
exit=False,
)
assert not git("status", "--porcelain")
Expand Down Expand Up @@ -575,7 +576,7 @@ def test_overwrite_answers_file_always(
git("commit", "-m1")
# When updating, the only thing to overwrite is the copier answers file,
# which shouldn't ask, so also this shouldn't hang with overwrite=False
run_update(defaults=True, answers_file=answers_file)
run_update(defaults=True, overwrite=True, answers_file=answers_file)
answers = yaml.safe_load(
(dst / (answers_file or ".copier-answers.yml")).read_bytes()
)
Expand Down Expand Up @@ -641,7 +642,11 @@ def test_file_removed(tmp_path_factory: pytest.TempPathFactory) -> None:
git("tag", "2")
# Subproject updates
with local.cwd(dst):
run_update(conflict="rej")
with pytest.raises(
UserMessageError, match="Enable overwrite to update a subproject."
):
run_update(conflict="rej")
run_update(conflict="rej", overwrite=True)
# Check what must still exist
assert (dst / ".copier-answers.yml").is_file()
assert (dst / "I.txt").is_file()
Expand Down Expand Up @@ -730,7 +735,7 @@ def test_update_inline_changed_answers_and_questions(
git("commit", "-am2")
# Update from template, inline, with answer changes
if interactive:
tui = spawn(COPIER_PATH + ("update", "-w", "--conflict=inline"), timeout=10)
tui = spawn(COPIER_PATH + ("update", "--conflict=inline"), timeout=10)
tui.expect_exact("b (bool)")
tui.expect_exact("(Y/n)")
tui.sendline()
Expand Down Expand Up @@ -941,7 +946,6 @@ def function_two():
else:
COPIER_CMD(
"update",
"--overwrite",
str(dst),
"--conflict=inline",
f"--context-lines={context_lines}",
Expand Down

0 comments on commit 8a1b5d5

Please sign in to comment.