diff --git a/copier/_main.py b/copier/_main.py index 41d5266b7..21741b08d 100644 --- a/copier/_main.py +++ b/copier/_main.py @@ -1363,6 +1363,15 @@ def _apply_update(self) -> None: # noqa: C901 ).strip() ) subproject_subdir = self.subproject.local_abspath.relative_to(subproject_top) + subproject_git_config_path = Path( + git( + "-C", + self.subproject.local_abspath, + "rev-parse", + "--absolute-git-dir", + ).strip(), + "config", + ) with ( TemporaryDirectory( @@ -1386,6 +1395,13 @@ def _apply_update(self) -> None: # noqa: C901 # https://github.com/orgs/copier-org/discussions/2345 exclude=[*self.template.exclude, *self.exclude], ) as old_worker: + # Initialize a Git repository in the temporary destination and configure + # it to include the Git configuration of the real destination, so + # tasks/migrations that rely on Git configuration (e.g., GitHub CLI) can + # work properly when running in the temporary destination. + with local.cwd(old_copy): + git("init") + git("config", "include.path", subproject_git_config_path) old_worker.run_copy() # Run pre-migration tasks with Phase.use(Phase.MIGRATE): @@ -1397,7 +1413,8 @@ def _apply_update(self) -> None: # noqa: C901 with local.cwd(subproject_top): subproject_head = git("write-tree").strip() with local.cwd(old_copy): - self._git_initialize_repo() + git("add", ".") + self._git_commit() # Configure borrowing Git objects from the real destination. set_git_alternates(subproject_top) # Save a list of files that were intentionally removed in the generated @@ -1457,9 +1474,17 @@ def _apply_update(self) -> None: # noqa: C901 exclude=exclude_plus_removed, vcs_ref=self.resolved_vcs_ref, ) as new_worker: + # Initialize a Git repository in the temporary destination and configure + # it to include the Git configuration of the real destination, so + # tasks/migrations that rely on Git configuration (e.g., GitHub CLI) can + # work properly when running in the temporary destination. + with local.cwd(new_copy): + git("init") + git("config", "include.path", subproject_git_config_path) new_worker.run_copy() with local.cwd(new_copy): - self._git_initialize_repo() + git("add", ".") + self._git_commit() new_copy_head = git("rev-parse", "HEAD").strip() # Extract diff between temporary destination and real destination # with some special handling of newly added files in both the project @@ -1653,13 +1678,6 @@ def _apply_update(self) -> None: # noqa: C901 self.template.migration_tasks("after", self.subproject.template) # type: ignore[arg-type] ) - def _git_initialize_repo(self) -> None: - """Initialize a git repository in the current directory.""" - git = get_git() - git("init", retcode=None) - git("add", ".") - self._git_commit() - def _git_commit(self, message: str = "dumb commit") -> None: git = get_git() # 1st commit could fail if any pre-commit hook reformats code diff --git a/tests/test_updatediff.py b/tests/test_updatediff.py index 050ef6f8d..8ce331324 100644 --- a/tests/test_updatediff.py +++ b/tests/test_updatediff.py @@ -2584,3 +2584,36 @@ def test_update_with_exec_bit_change_and_merge_conflict( ++>>>>>>> after updating """) # editorconfig-checker-enable + + +def test_tasks_run_in_git_repo_with_subproject_config( + tmp_path_factory: pytest.TempPathFactory, +) -> None: + """Test that tasks run in a Git repo with the subproject's Git config.""" + src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) + + build_file_tree( + { + (src / "{{ _copier_conf.answers_file }}.jinja"): ( + "{{ _copier_answers|to_yaml }}" + ), + (src / "copier.yml"): ( + f"""\ + _tasks: + - command: git remote get-url origin >> {dst / "git-origin-task.txt"} + when: "{{{{ _copier_operation == 'update' }}}}" + """ + ), + } + ) + git_save(src) + + run_copy(str(src), dst, unsafe=True) + + with local.cwd(dst): + git_save() + git("remote", "add", "origin", remote := "http://example.com/repo.git") + + run_update(dst, unsafe=True, overwrite=True) + + assert (dst / "git-origin-task.txt").read_text() == f"{remote}\n" * 3 diff --git a/tests/test_vcs.py b/tests/test_vcs.py index 4b97a160f..7d9bb607a 100644 --- a/tests/test_vcs.py +++ b/tests/test_vcs.py @@ -183,7 +183,11 @@ def test_update_using_local_source_path_with_tilde(tmp_path: Path) -> None: # generate project and assert correct path in answers worker = run_copy( - src_path=user_src_path, dst_path=tmp_path, defaults=True, unsafe=True + src_path=user_src_path, + dst_path=tmp_path, + defaults=True, + unsafe=True, + skip_tasks=True, ) assert worker.answers.combined["_src_path"] == user_src_path @@ -198,6 +202,7 @@ def test_update_using_local_source_path_with_tilde(tmp_path: Path) -> None: overwrite=True, answers_file=".copier-answers.autopretty.yml", unsafe=True, + skip_tasks=True, ) assert worker.answers.combined["_src_path"] == user_src_path