Commit Diff


commit - 699fde009ad0ee86b1ef7b1a619548f2006cc356
commit + 744fcc280f5a4e23f14598fb4630d1c76f33389b
blob - cb4bcad46b5bdbdaa150ea20aebef169cce2be60
blob + 1c1d8c4cb574c330838b5e118156315ec1e2ec44
--- dulwich/porcelain.py
+++ dulwich/porcelain.py
@@ -1156,15 +1156,19 @@ def pull(
             _import_remote_refs(r.refs, remote_name, fetch_result.refs)
 
 
-def status(repo=".", ignored=False, walk_untracked=True):
+def status(repo=".", ignored=False, untracked_files="all"):
     """Returns staged, unstaged, and untracked changes relative to the HEAD.
 
     Args:
       repo: Path to repository or repository object
       ignored: Whether to include ignored files in untracked
-      walk_untracked: Whether to walk untracked directories
-            When True, behaves like `git status -uall`.
-            When False, behaves like to `git status -unormal` (git default).
+      untracked_files: How to handle untracked files, defaults to "all":
+          "no": do not return untracked files
+          "all": include all files in untracked directories
+        Using `untracked_files="no"` can be faster than "all" when the worktreee
+          contains many untracked files/directories.
+
+    Note: `untracked_files="normal" (`git`'s default) is not implemented.
 
     Returns: GitStatus tuple,
         staged -  dict with lists of staged paths (diff index/HEAD)
@@ -1181,7 +1185,11 @@ def status(repo=".", ignored=False, walk_untracked=Tru
         unstaged_changes = list(get_unstaged_changes(index, r.path, filter_callback))
 
         untracked_paths = get_untracked_paths(
-            r.path, r.path, index, exclude_ignored=not ignored, walk=walk_untracked
+            r.path,
+            r.path,
+            index,
+            exclude_ignored=not ignored,
+            untracked_files=untracked_files,
         )
         untracked_changes = list(untracked_paths)
 
@@ -1221,11 +1229,7 @@ def _walk_working_dir_paths(frompath, basepath, prune_
 
 
 def get_untracked_paths(
-    frompath,
-    basepath,
-    index,
-    exclude_ignored=False,
-    walk=True,
+    frompath, basepath, index, exclude_ignored=False, untracked_files="all"
 ):
     """Get untracked paths.
 
@@ -1234,17 +1238,28 @@ def get_untracked_paths(
       basepath: Path to compare to
       index: Index to check against
       exclude_ignored: Whether to exclude ignored paths
-      walk: Whether to walk untracked paths.
+      untracked_files: How to handle untracked files:
+        - "no": return an empty list
+        - "all": return all files in untracked directories
+        - "normal": Not implemented
 
     Note: ignored directories will never be walked for performance reasons.
       If exclude_ignored is False, only the path to an ignored directory will
       be yielded, no files inside the directory will be returned
     """
+    if untracked_files == "normal":
+        raise NotImplementedError("normal is not yet supported")
+
+    if untracked_files not in ("no", "all"):
+        raise ValueError("untracked_files must be one of (no, all)")
+
+    if untracked_files == "no":
+        return
+
     with open_repo_closing(basepath) as r:
         ignore_manager = IgnoreFilterManager.from_repo(r)
 
     ignored_dirs = []
-    untracked_dirs = []
 
     def prune_dirnames(dirpath, dirnames):
         for i in range(len(dirnames) - 1, -1, -1):
@@ -1256,15 +1271,6 @@ def get_untracked_paths(
                         os.path.join(os.path.relpath(path, frompath), "")
                     )
                 del dirnames[i]
-            elif not walk:
-                if not any(
-                    os.fsdecode(index_path).startswith(dirnames[i])
-                    for index_path, _, in index.items()
-                ):
-                    untracked_dirs.append(
-                        os.path.join(os.path.relpath(path, frompath)) + os.sep
-                    )
-                    del dirnames[i]
         return dirnames
 
     for ap, is_dir in _walk_working_dir_paths(
@@ -1279,7 +1285,6 @@ def get_untracked_paths(
                     yield os.path.relpath(ap, frompath)
 
     yield from ignored_dirs
-    yield from untracked_dirs
 
 
 def get_tree_changes(repo):
blob - f70a6d678b3cc811898a69a67764ddb5039df79d
blob + 201669ebff9d296ddd2ea89404ca02dfa5debb6e
--- dulwich/tests/test_porcelain.py
+++ dulwich/tests/test_porcelain.py
@@ -1849,18 +1849,13 @@ class StatusTests(PorcelainTestCase):
         mod_path = os.path.join(self.repo.path, "bar")
         add_path = os.path.join(self.repo.path, "baz")
         us_path = os.path.join(self.repo.path, "blye")
-        ut_path = os.path.join(self.repo.path, "untracked_file")
-        os.mkdir(os.path.join(self.repo.path, "untracked_dir"))
-        ut_subfile_path = os.path.join(self.repo.path, "untracked_dir/file")
-
+        ut_path = os.path.join(self.repo.path, "blyat")
         with open(del_path, "w") as f:
             f.write("origstuff")
         with open(mod_path, "w") as f:
             f.write("origstuff")
         with open(us_path, "w") as f:
             f.write("origstuff")
-        with open(ut_subfile_path, "w") as f:
-            f.write("origstuff")
         porcelain.add(repo=self.repo.path, paths=[del_path, mod_path, us_path])
         porcelain.commit(
             repo=self.repo.path,
@@ -1886,16 +1881,13 @@ class StatusTests(PorcelainTestCase):
             results.staged,
         )
         self.assertListEqual(results.unstaged, [b"blye"])
-        self.assertListEqual(
-            results.untracked, [
-                "untracked_file", os.path.join("untracked_dir", "file")]
-        )
-        results_no_walk = porcelain.status(self.repo.path, walk_untracked=False)
-        self.assertListEqual(
-            results_no_walk.untracked,
-            ["untracked_file", "untracked_dir" + os.path.sep]
-        )
+        results_no_untracked = porcelain.status(self.repo.path, untracked_files="no")
+        self.assertListEqual(results_no_untracked.untracked, [])
 
+    def test_status_wrong_untracked_files_value(self):
+        with self.assertRaises(ValueError):
+            porcelain.status(self.repo.path, untracked_files="antani")
+
     def test_status_crlf_mismatch(self):
         # First make a commit as if the file has been added on a Linux system
         # or with core.autocrlf=True
@@ -2187,35 +2179,25 @@ class StatusTests(PorcelainTestCase):
                     self.repo.open_index(),
                     exclude_ignored=True,
                 )
-            ),
+            )
         )
 
-    def test_get_untracked_paths_walk(self):
-        os.mkdir(os.path.join(self.repo.path, "dir"))
-        os.mkdir(os.path.join(self.repo.path, "dir/subdir"))
-        with open(os.path.join(self.repo.path, "dir", "file"), "w") as f:
-            f.write("foo")
-        with open(os.path.join(self.repo.path, "dir/subdir/subfile"), "w") as f:
-            f.write("foo")
-
-        self.assertEqual(
-            {"dir" + os.path.sep},
-            set(
+    def test_get_untracked_paths_invalid_untracked_files(self):
+        with self.assertRaises(ValueError):
+            list(
                 porcelain.get_untracked_paths(
-                    self.repo.path, self.repo.path, self.repo.open_index(), walk=False
-                )
-            ),
-        )
-        self.assertEqual(
-            set([os.path.join("dir", "file"),
-                 os.path.join("dir", "subdir", "subfile")]),
-            set(
-                porcelain.get_untracked_paths(
-                    self.repo.path, self.repo.path, self.repo.open_index(), walk=True
+                    self.repo.path,
+                    self.repo.path,
+                    self.repo.open_index(),
+                    untracked_files="invalid_value",
                 )
-            ),
-        )
+            )
 
+    def test_get_untracked_paths_normal(self):
+        with self.assertRaises(NotImplementedError):
+            _, _, _ = porcelain.status(
+                repo=self.repo.path, untracked_files="normal"
+            )
 
 # TODO(jelmer): Add test for dulwich.porcelain.daemon