diff --git a/git_archive_all.py b/git_archive_all.py index fc69a99..87f097d 100755 --- a/git_archive_all.py +++ b/git_archive_all.py @@ -165,6 +165,7 @@ def __init__(self, prefix='', exclude=True, force_sub=False, extra=None, main_re If None, tries to resolve via Git's CLI. """ self._check_attr_gens = {} + self._ignored_paths_cache = {} if git_version is None: git_version = self.get_git_version() @@ -271,9 +272,26 @@ def is_file_excluded(self, repo_abspath, repo_file_path): @return: True if file should be excluded. Otherwise False. """ - next(self._check_attr_gens[repo_abspath]) - attrs = self._check_attr_gens[repo_abspath].send(repo_file_path) - return attrs['export-ignore'] == b'set' + cache = self._ignored_paths_cache.setdefault(repo_abspath, {}) + + if repo_file_path not in cache: + next(self._check_attr_gens[repo_abspath]) + attrs = self._check_attr_gens[repo_abspath].send(repo_file_path) + export_ignore_attr = attrs['export-ignore'] + + if export_ignore_attr == b'set': + cache[repo_file_path] = True + elif export_ignore_attr == b'unset': + cache[repo_file_path] = False + else: + repo_file_dir_path = path.dirname(repo_file_path) + + if repo_file_dir_path: + cache[repo_file_path] = self.is_file_excluded(repo_abspath, repo_file_dir_path) + else: + cache[repo_file_path] = False + + return cache[repo_file_path] def archive_all_files(self, archiver): """ diff --git a/git_archive_all.pyi b/git_archive_all.pyi index f90b737..28733b9 100644 --- a/git_archive_all.pyi +++ b/git_archive_all.pyi @@ -24,6 +24,8 @@ class GitArchiver(object): LOG: ClassVar[logging.Logger] _check_attr_gens: Dict[str, CheckGitAttrGen] + _ignored_paths_cache: Dict[PathStr, Dict[PathStr, bool]] + git_version: Optional[Tuple[int]] main_repo_abspath: PathStr prefix: PathStr diff --git a/test_git_archive_all.py b/test_git_archive_all.py index 7c589a7..76d2e80 100644 --- a/test_git_archive_all.py +++ b/test_git_archive_all.py @@ -323,6 +323,16 @@ def make_actual_tree(tar_file): b'\"\\\xc2.dat\"': FileRecord('Although practicality beats purity.') }) +ignore_dir = { + '.gitattributes': FileRecord('.gitattributes export-ignore\n**/src export-ignore\ndata/src/__main__.py -export-ignore', excluded=True), + '__init__.py': FileRecord('#Beautiful is better than ugly.'), + 'data': DirRecord({ + 'src': DirRecord({ + '__init__.py': FileRecord('#Explicit is better than implicit.', excluded=True), + '__main__.py': FileRecord('#Simple is better than complex.') + }) + }) +} skipif_file_darwin = pytest.mark.skipif(sys.platform.startswith('darwin'), reason='Invalid macOS filename.') skipif_file_win32 = pytest.mark.skipif(sys.platform.startswith('win32'), reason="Invalid Windows filename.") @@ -349,7 +359,8 @@ def make_actual_tree(tar_file): pytest.param(backslash_base, id='Backslash', marks=skipif_file_win32), pytest.param(backslash_quoted, id='Backslash (Quoted)', marks=skipif_file_win32), pytest.param(non_unicode_backslash_base, id='Non-Unicode Backslash', marks=[skipif_file_win32, skipif_file_darwin]), - pytest.param(non_unicode_backslash_quoted, id='Non-Unicode Backslash (Quoted)', marks=[skipif_file_win32, skipif_file_darwin]) + pytest.param(non_unicode_backslash_quoted, id='Non-Unicode Backslash (Quoted)', marks=[skipif_file_win32, skipif_file_darwin]), + pytest.param(ignore_dir, id='Ignore Directory') ]) def test_ignore(contents, tmpdir, git_env, monkeypatch): """