From 2f8485400a879a6426a5cb95793a4a8d94ede0aa Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sun, 18 May 2025 14:31:15 +0200 Subject: [PATCH] list --depth=N: list files up to N depth in path hierarchy, fixes #8268 IOW: if there are > N slashes in the path, it is not listed. --- src/borg/archiver/list_cmd.py | 23 +++++++- src/borg/testsuite/archiver/list_cmd_test.py | 61 ++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/borg/archiver/list_cmd.py b/src/borg/archiver/list_cmd.py index 013e695cd..7e13d94fd 100644 --- a/src/borg/archiver/list_cmd.py +++ b/src/borg/archiver/list_cmd.py @@ -32,7 +32,21 @@ class ListMixIn: def _list_inner(cache): archive = Archive(manifest, archive_info.id, cache=cache) formatter = ItemFormatter(archive, format) - for item in archive.iter_items(lambda item: matcher.match(item.path)): + + def item_filter(item): + # Check if the item matches the patterns/paths. + if not matcher.match(item.path): + return False + # If depth is specified, also check the depth of the path. + if args.depth is not None: + # Count path separators to determine depth. + # For paths like "dir/subdir/file.txt", the depth is 2. + path_depth = item.path.count("/") + if path_depth > args.depth: + return False + return True + + for item in archive.iter_items(item_filter): sys.stdout.write(formatter.format_item(item, args.json_lines, sort=True)) # Only load the cache if it will be used @@ -117,6 +131,13 @@ class ListMixIn: "but keys used in it are added to the JSON output. " "Some keys are always present. Note: JSON can only represent text.", ) + subparser.add_argument( + "--depth", + metavar="N", + dest="depth", + type=int, + help="only list files up to the specified directory level depth", + ) subparser.add_argument("name", metavar="NAME", type=archivename_validator, help="specify the archive name") subparser.add_argument( "paths", metavar="PATH", nargs="*", type=PathSpec, help="paths to list; patterns are supported" diff --git a/src/borg/testsuite/archiver/list_cmd_test.py b/src/borg/testsuite/archiver/list_cmd_test.py index b0d4b6423..ba7538867 100644 --- a/src/borg/testsuite/archiver/list_cmd_test.py +++ b/src/borg/testsuite/archiver/list_cmd_test.py @@ -74,3 +74,64 @@ def test_list_json(archivers, request): file1 = items[1] assert file1["path"] == "input/file1" assert file1["sha256"] == "b2915eb69f260d8d3c25249195f2c8f4f716ea82ec760ae929732c0262442b2b" + + +def test_list_depth(archivers, request): + """Test the --depth option for the list command.""" + archiver = request.getfixturevalue(archivers) + + # Create repository + cmd(archiver, "repo-create", RK_ENCRYPTION) + + # Create files at different directory depths + create_regular_file(archiver.input_path, "file_at_depth_1.txt", size=1) + create_regular_file(archiver.input_path, "dir1/file_at_depth_2.txt", size=1) + create_regular_file(archiver.input_path, "dir1/dir2/file_at_depth_3.txt", size=1) + + # Create archive + cmd(archiver, "create", "test", "input") + + # Test with depth=0 (only the root directory) + output_depth_0 = cmd(archiver, "list", "test", "--depth=0") + assert "input" in output_depth_0 + assert "input/file_at_depth_1.txt" not in output_depth_0 + assert "input/dir1" not in output_depth_0 + assert "input/dir1/file_at_depth_2.txt" not in output_depth_0 + assert "input/dir1/dir2" not in output_depth_0 + assert "input/dir1/dir2/file_at_depth_3.txt" not in output_depth_0 + + # Test with depth=1 (only input directory and files directly in it) + output_depth_1 = cmd(archiver, "list", "test", "--depth=1") + assert "input" in output_depth_1 + assert "input/file_at_depth_1.txt" in output_depth_1 + assert "input/dir1" in output_depth_1 + assert "input/dir1/file_at_depth_2.txt" not in output_depth_1 + assert "input/dir1/dir2" not in output_depth_1 + assert "input/dir1/dir2/file_at_depth_3.txt" not in output_depth_1 + + # Test with depth=2 (files up to one level inside input) + output_depth_2 = cmd(archiver, "list", "test", "--depth=2") + assert "input" in output_depth_2 + assert "input/file_at_depth_1.txt" in output_depth_2 + assert "input/dir1" in output_depth_2 + assert "input/dir1/file_at_depth_2.txt" in output_depth_2 + assert "input/dir1/dir2" in output_depth_2 + assert "input/dir1/dir2/file_at_depth_3.txt" not in output_depth_2 + + # Test with depth=3 (files up to two levels inside input) + output_depth_3 = cmd(archiver, "list", "test", "--depth=3") + assert "input" in output_depth_3 + assert "input/file_at_depth_1.txt" in output_depth_3 + assert "input/dir1" in output_depth_3 + assert "input/dir1/file_at_depth_2.txt" in output_depth_3 + assert "input/dir1/dir2" in output_depth_3 + assert "input/dir1/dir2/file_at_depth_3.txt" in output_depth_3 + + # Test without depth parameter (should show all files) + output_no_depth = cmd(archiver, "list", "test") + assert "input" in output_no_depth + assert "input/file_at_depth_1.txt" in output_no_depth + assert "input/dir1" in output_no_depth + assert "input/dir1/file_at_depth_2.txt" in output_no_depth + assert "input/dir1/dir2" in output_no_depth + assert "input/dir1/dir2/file_at_depth_3.txt" in output_no_depth