Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions copier/_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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):
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
33 changes: 33 additions & 0 deletions tests/test_updatediff.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 6 additions & 1 deletion tests/test_vcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to disable tasks here (and also below) because the generated project of this archived template uses a pre-commit hook from https://gitlab.com/pycqa/flake8 which doesn't exist anymore. When trying to clone a non-existing Git repository on gitlab.com, Git prompts for credentials which blocks the test case. The pre-commit hooks are installed via a task and thus executed on git commit run internally by the update algorithm. Before the changes of this PR, there was no problem because the temporary locations managed by the update algorithm were no Git repositories at the time of project generation, so the pre-commit hooks were not installed by the task.

)
assert worker.answers.combined["_src_path"] == user_src_path

Expand All @@ -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

Expand Down
Loading