From 5b8477ed61e25aa4cada5954171894bd7deaf63f Mon Sep 17 00:00:00 2001 From: Simone Scarduzio Date: Wed, 8 Oct 2025 13:00:58 +0200 Subject: [PATCH] fix: Correct ls command path handling and prefix display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed issues where ls command was: - Showing incorrect prefixes (e.g., "PRE build/" instead of "PRE 1.67.0-pre6/") - Getting into loops when listing subdirectories - Not properly handling paths without trailing slashes Changes: - Ensure prefix ends with / for proper path handling - Use S3 Delimiter parameter to get proper subdirectory grouping - Display only relative subdirectory names, not full paths - Use common_prefixes from S3 response instead of manual parsing This now matches AWS CLI behavior where: - `ls s3://bucket/build/` shows subdirectories as `PRE org/` and `PRE 1.67.0-pre6/` - Not `PRE build/org/` and `PRE build/1.67.0-pre6/` All 99 tests passing, quality checks passing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/deltaglider/app/cli/main.py | 40 ++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/deltaglider/app/cli/main.py b/src/deltaglider/app/cli/main.py index e82676d..54f5456 100644 --- a/src/deltaglider/app/cli/main.py +++ b/src/deltaglider/app/cli/main.py @@ -240,6 +240,13 @@ def ls( prefix_str: str bucket_name, prefix_str = parse_s3_url(s3_url) + # Ensure prefix ends with / if it's meant to be a directory + # This helps with proper path handling + if prefix_str and not prefix_str.endswith("/"): + # Check if this is a file or directory by listing + # For now, assume it's a directory prefix + prefix_str = prefix_str + "/" + # Format bytes to human readable def format_bytes(size: int) -> str: if not human_readable: @@ -256,29 +263,30 @@ def ls( client = DeltaGliderClient(service) dg_response: ListObjectsResponse = client.list_objects( - Bucket=bucket_name, Prefix=prefix_str, MaxKeys=10000 + Bucket=bucket_name, Prefix=prefix_str, MaxKeys=10000, Delimiter="/" if not recursive else "" ) objects = dg_response.contents # Filter by recursive flag if not recursive: - # Only show direct children - seen_prefixes = set() + # Show common prefixes (subdirectories) from S3 response + for common_prefix in dg_response.common_prefixes: + prefix_path = common_prefix.get("Prefix", "") + # Show only the directory name, not the full path + if prefix_str: + # Strip the current prefix to show only the subdirectory + display_name = prefix_path[len(prefix_str):] + else: + display_name = prefix_path + click.echo(f" PRE {display_name}") + + # Only show files at current level (not in subdirectories) filtered_objects = [] for obj in objects: - rel_path = obj.key[len(prefix_str) :] if prefix_str else obj.key - if "/" in rel_path: - # It's in a subdirectory - subdir = rel_path.split("/")[0] + "/" - if subdir not in seen_prefixes: - seen_prefixes.add(subdir) - # Show as directory - full_prefix = f"{prefix_str}{subdir}" if prefix_str else subdir - click.echo(f" PRE {full_prefix}") - else: - # Direct file - if rel_path: # Only add if there's actually a file at this level - filtered_objects.append(obj) + rel_path = obj.key[len(prefix_str):] if prefix_str else obj.key + # Only include if it's a direct child (no / in relative path) + if "/" not in rel_path and rel_path: + filtered_objects.append(obj) objects = filtered_objects # Display objects (SDK already filters reference.bin and strips .delta)