# HG changeset patch # User mattseddon <37993418+mattseddon@users.noreply.github.com> # Date 1610840173 -39600 # Sun Jan 17 10:36:13 2021 +1100 # Node ID 052026e03a861b0842b9a7a4393ffa9818017687 # Parent 7dbd22ac20704cd553095edfee9770400d31a9d3 Issue 793: Fix failure in get_untracked_paths when the repository contains symlinks (#830) diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -1,3 +1,8 @@ +0.20.16 2021-01-16 + + * Add flag to only attempt to fetch ignored untracked files when specifically requested. + (Matt Seddon) + 0.20.15 2020-12-23 * Add some functions for parsing and writing bundles. diff --git a/dulwich/porcelain.py b/dulwich/porcelain.py --- a/dulwich/porcelain.py +++ b/dulwich/porcelain.py @@ -1117,14 +1117,11 @@ unstaged_changes = list( get_unstaged_changes(index, r.path, filter_callback) ) - ignore_manager = IgnoreFilterManager.from_repo(r) - untracked_paths = get_untracked_paths(r.path, r.path, index) - if ignored: - untracked_changes = list(untracked_paths) - else: - untracked_changes = [ - p for p in untracked_paths - if not ignore_manager.is_ignored(p)] + + untracked_paths = get_untracked_paths(r.path, r.path, index, + exclude_ignored=not ignored) + untracked_changes = list(untracked_paths) + return GitStatus(tracked_changes, unstaged_changes, untracked_changes) @@ -1154,21 +1151,41 @@ yield filepath, False -def get_untracked_paths(frompath, basepath, index): +def get_untracked_paths(frompath, basepath, index, exclude_ignored=False): """Get untracked paths. Args: ;param frompath: Path to walk basepath: Path to compare to index: Index to check against + exclude_ignored: Whether to exclude ignored paths """ + ignore_manager = _get_ignore_manager(frompath, exclude_ignored) + for ap, is_dir in _walk_working_dir_paths(frompath, basepath): + if (exclude_ignored and + ignore_manager.is_ignored(os.path.relpath(ap, frompath))): + continue if not is_dir: ip = path_to_tree_path(basepath, ap) if ip not in index: yield os.path.relpath(ap, frompath) +def _get_ignore_manager(frompath, exclude_ignored): + """Get a repo's IgnoreFilterManager from a path if required. + + Args: + ;param frompath: The path of the repo + exclude_ignored: Whether to return the IgnoreFilterManager + """ + if exclude_ignored: + with open_repo_closing(frompath) as r: + ignore_manager = IgnoreFilterManager.from_repo(r) + + return ignore_manager + + def get_tree_changes(repo): """Return add/delete/modify changes to tree by comparing index to HEAD. diff --git a/dulwich/tests/test_porcelain.py b/dulwich/tests/test_porcelain.py --- a/dulwich/tests/test_porcelain.py +++ b/dulwich/tests/test_porcelain.py @@ -1444,20 +1444,39 @@ .untracked)) def test_get_untracked_paths_nested(self): + with open(os.path.join(self.repo.path, '.gitignore'), 'w') as f: + f.write('nested/\n') with open(os.path.join(self.repo.path, 'notignored'), 'w') as f: f.write('blah\n') + subrepo = Repo.init(os.path.join(self.repo.path, 'nested'), mkdir=True) - with open(os.path.join(subrepo.path, 'another'), 'w') as f: - f.write('foo\n') + with open(os.path.join(subrepo.path, 'ignored'), 'w') as f: + f.write('bleep\n') + with open(os.path.join(subrepo.path, 'with'), 'w') as f: + f.write('bloop\n') + with open(os.path.join(subrepo.path, 'manager'), 'w') as f: + f.write('blop\n') self.assertEqual( - set(['notignored']), + set(['.gitignore', 'notignored']), set(porcelain.get_untracked_paths(self.repo.path, self.repo.path, self.repo.open_index()))) self.assertEqual( - set(['another']), + set(['ignored', 'with', 'manager']), set(porcelain.get_untracked_paths(subrepo.path, subrepo.path, subrepo.open_index()))) + self.assertEqual( + set([os.path.join('nested', 'ignored'), + os.path.join('nested', 'with'), + os.path.join('nested', 'manager')]), + set(porcelain.get_untracked_paths(self.repo.path, subrepo.path, + self.repo.open_index(), + exclude_ignored=False))) + self.assertEqual( + set([]), + set(porcelain.get_untracked_paths(self.repo.path, subrepo.path, + self.repo.open_index(), + exclude_ignored=True))) # TODO(jelmer): Add test for dulwich.porcelain.daemon