From b7e875962313e3b2e9f9e276ca9878fb723d7f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Prchl=C3=ADk?= Date: Wed, 31 Jul 2024 15:24:17 +0200 Subject: [PATCH] squash: aligned with tmt codebase --- tmt/base.py | 40 +++++++++++++++++++++++++++++++--------- tmt/utils/__init__.py | 25 +++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/tmt/base.py b/tmt/base.py index e7200ed507..6292eb6832 100644 --- a/tmt/base.py +++ b/tmt/base.py @@ -8,7 +8,6 @@ import os import re import shutil -import subprocess import sys import tempfile import time @@ -1797,7 +1796,7 @@ def _initialize_worktree(self) -> None: # Prepare worktree path and detect the source tree root assert self.workdir is not None # narrow type self.worktree = self.workdir / 'tree' - tree_root = self.node.root + tree_root = Path(self.node.root) if self.node.root else None # Create an empty directory if there's no metadata tree if not tree_root: @@ -1807,14 +1806,37 @@ def _initialize_worktree(self) -> None: # Sync metadata root to the worktree self.debug(f"Sync the worktree to '{self.worktree}'.", level=2) - excludes_tempfile = tempfile.NamedTemporaryFile() + + ignore: list[Path] = [ + Path('.git') + ] + # If we're in a git repository, honor .gitignore; xref - # https://stackoverflow.com/questions/13713101/rsync-exclude-according-to-gitignore-hgignore-svnignore-like-filter-c - if os.path.isdir(f"{tree_root}/.git"): - subprocess.check_call(["git", "ls-files", "--exclude-standard", "-oi", "--directory"], stdout=excludes_tempfile) - # Note: rsync doesn't use reflinks right now, so in the future it'd be even better to - # use e.g. `cp` but filtering out the above. - self.run(Command("rsync", "-ar", "--exclude", ".git", "--exclude-from", excludes_tempfile.name, f"{tree_root}/", self.worktree)) + # https://stackoverflow.com/questions/13713101/rsync-exclude-according-to-gitignore-hgignore-svnignore-like-filter-c # noqa: E501 + git_root = tmt.utils.git_root(fmf_root=tree_root, logger=self._logger) + if git_root: + ignore.extend(tmt.utils.git_ignore(root=git_root, logger=self._logger)) + + self.debug( + "Ignoring the following paths during worktree sync", + tmt.utils.format_value(ignore), + level=4) + + with tempfile.NamedTemporaryFile(mode='w') as excludes_tempfile: + excludes_tempfile.write('\n'.join(str(path) for path in ignore)) + + # Make sure ignored paths are saved before telling rsync to use them. + # With Python 3.12, we could use `delete_on_false=False` and call `close()`. + excludes_tempfile.flush() + os.fsync(excludes_tempfile.fileno()) + + # Note: rsync doesn't use reflinks right now, so in the future it'd be even better to + # use e.g. `cp` but filtering out the above. + self.run(Command( + "rsync", + "-ar", + "--exclude-from", excludes_tempfile.name, + f"{tree_root}/", self.worktree)) def _initialize_data_directory(self) -> None: """ diff --git a/tmt/utils/__init__.py b/tmt/utils/__init__.py index 916f2ea71b..8fb0cf6092 100644 --- a/tmt/utils/__init__.py +++ b/tmt/utils/__init__.py @@ -4524,6 +4524,31 @@ def git_add(*, path: Path, logger: tmt.log.Logger) -> None: raise GeneralError(f"Failed to add path '{path}' to git index.") from error +def git_ignore(*, root: Path, logger: tmt.log.Logger) -> list[Path]: + """ + Collect effective paths ignored by git. + + :param root: path to the root of git repository. + :param logger: used for logging. + :returns: list of actual paths tah would be ignored by git based on + its ``.gitignore`` files. If a whole directory is to be ignored, + its listed as a directory path, not listing its content. + """ + + output = Command( + 'git', + 'ls-files', + # Consider standard git exclusion files + '--exclude-standard', + # List untracked files matching exclusion patterns + '-oi', + # If a whole directory is to be ignored, list only its name with a trailing slash + '--directory') \ + .run(cwd=root, logger=logger) + + return [Path(line.strip()) for line in output.stdout.splitlines()] if output.stdout else [] + + def default_branch( *, repository: Path,