1111from basic_memory .mcp .tools .utils import call_put
1212
1313
14- async def upload_path (local_path : Path , project_name : str ) -> bool :
14+ async def upload_path (
15+ local_path : Path ,
16+ project_name : str ,
17+ verbose : bool = False ,
18+ use_gitignore : bool = True ,
19+ dry_run : bool = False ,
20+ ) -> bool :
1521 """
1622 Upload a file or directory to cloud project via WebDAV.
1723
1824 Args:
1925 local_path: Path to local file or directory
2026 project_name: Name of cloud project (destination)
27+ verbose: Show detailed information about filtering and upload
28+ use_gitignore: If False, skip .gitignore patterns (still use .bmignore)
29+ dry_run: If True, show what would be uploaded without uploading
2130
2231 Returns:
2332 True if upload succeeded, False otherwise
@@ -34,43 +43,66 @@ async def upload_path(local_path: Path, project_name: str) -> bool:
3443 # Get files to upload
3544 if local_path .is_file ():
3645 files_to_upload = [(local_path , local_path .name )]
46+ if verbose :
47+ print (f"Uploading single file: { local_path .name } " )
3748 else :
38- files_to_upload = _get_files_to_upload (local_path )
49+ files_to_upload = _get_files_to_upload (local_path , verbose , use_gitignore )
3950
4051 if not files_to_upload :
4152 print ("No files found to upload" )
53+ if verbose :
54+ print (
55+ "\n Tip: Use --verbose to see which files are being filtered, "
56+ "or --no-gitignore to skip .gitignore patterns"
57+ )
4258 return True
4359
4460 print (f"Found { len (files_to_upload )} file(s) to upload" )
4561
46- # Upload files using httpx
47- total_bytes = 0
48-
49- async with get_client () as client :
50- for i , (file_path , relative_path ) in enumerate (files_to_upload , 1 ):
51- # Build remote path: /webdav/{project_name}/{relative_path}
52- remote_path = f"/webdav/{ project_name } /{ relative_path } "
53- print (f"Uploading { relative_path } ({ i } /{ len (files_to_upload )} )" )
54-
55- # Read file content asynchronously
56- async with aiofiles .open (file_path , "rb" ) as f :
57- content = await f .read ()
58-
59- # Upload via HTTP PUT to WebDAV endpoint
60- response = await call_put (client , remote_path , content = content )
61- response .raise_for_status ()
62-
63- total_bytes += file_path .stat ().st_size
64-
65- # Format size based on magnitude
62+ # Calculate total size
63+ total_bytes = sum (file_path .stat ().st_size for file_path , _ in files_to_upload )
64+
65+ # If dry run, just show what would be uploaded
66+ if dry_run :
67+ print ("\n Files that would be uploaded:" )
68+ for file_path , relative_path in files_to_upload :
69+ size = file_path .stat ().st_size
70+ if size < 1024 :
71+ size_str = f"{ size } bytes"
72+ elif size < 1024 * 1024 :
73+ size_str = f"{ size / 1024 :.1f} KB"
74+ else :
75+ size_str = f"{ size / (1024 * 1024 ):.1f} MB"
76+ print (f" { relative_path } ({ size_str } )" )
77+ else :
78+ # Upload files using httpx
79+ async with get_client () as client :
80+ for i , (file_path , relative_path ) in enumerate (files_to_upload , 1 ):
81+ # Build remote path: /webdav/{project_name}/{relative_path}
82+ remote_path = f"/webdav/{ project_name } /{ relative_path } "
83+ print (f"Uploading { relative_path } ({ i } /{ len (files_to_upload )} )" )
84+
85+ # Read file content asynchronously
86+ async with aiofiles .open (file_path , "rb" ) as f :
87+ content = await f .read ()
88+
89+ # Upload via HTTP PUT to WebDAV endpoint
90+ response = await call_put (client , remote_path , content = content )
91+ response .raise_for_status ()
92+
93+ # Format total size based on magnitude
6694 if total_bytes < 1024 :
6795 size_str = f"{ total_bytes } bytes"
6896 elif total_bytes < 1024 * 1024 :
6997 size_str = f"{ total_bytes / 1024 :.1f} KB"
7098 else :
7199 size_str = f"{ total_bytes / (1024 * 1024 ):.1f} MB"
72100
73- print (f"✓ Upload complete: { len (files_to_upload )} file(s) ({ size_str } )" )
101+ if dry_run :
102+ print (f"\n Total: { len (files_to_upload )} file(s) ({ size_str } )" )
103+ else :
104+ print (f"✓ Upload complete: { len (files_to_upload )} file(s) ({ size_str } )" )
105+
74106 return True
75107
76108 except httpx .HTTPStatusError as e :
@@ -81,22 +113,38 @@ async def upload_path(local_path: Path, project_name: str) -> bool:
81113 return False
82114
83115
84- def _get_files_to_upload (directory : Path ) -> list [tuple [Path , str ]]:
116+ def _get_files_to_upload (
117+ directory : Path , verbose : bool = False , use_gitignore : bool = True
118+ ) -> list [tuple [Path , str ]]:
85119 """
86120 Get list of files to upload from directory.
87121
88- Uses .bmignore and .gitignore patterns for filtering.
122+ Uses .bmignore and optionally .gitignore patterns for filtering.
89123
90124 Args:
91125 directory: Directory to scan
126+ verbose: Show detailed filtering information
127+ use_gitignore: If False, skip .gitignore patterns (still use .bmignore)
92128
93129 Returns:
94130 List of (absolute_path, relative_path) tuples
95131 """
96132 files = []
97-
98- # Load ignore patterns from .bmignore and .gitignore
99- ignore_patterns = load_gitignore_patterns (directory )
133+ ignored_files = []
134+
135+ # Load ignore patterns from .bmignore and optionally .gitignore
136+ ignore_patterns = load_gitignore_patterns (directory , use_gitignore = use_gitignore )
137+
138+ if verbose :
139+ gitignore_path = directory / ".gitignore"
140+ gitignore_exists = gitignore_path .exists () and use_gitignore
141+ print (f"\n Scanning directory: { directory } " )
142+ print ("Using .bmignore: Yes" )
143+ print (f"Using .gitignore: { 'Yes' if gitignore_exists else 'No' } " )
144+ print (f"Ignore patterns loaded: { len (ignore_patterns )} " )
145+ if ignore_patterns and len (ignore_patterns ) <= 20 :
146+ print (f"Patterns: { ', ' .join (sorted (ignore_patterns ))} " )
147+ print ()
100148
101149 # Walk through directory
102150 for root , dirs , filenames in os .walk (directory ):
@@ -106,23 +154,37 @@ def _get_files_to_upload(directory: Path) -> list[tuple[Path, str]]:
106154 filtered_dirs = []
107155 for d in dirs :
108156 dir_path = root_path / d
109- if not should_ignore_path (dir_path , directory , ignore_patterns ):
157+ if should_ignore_path (dir_path , directory , ignore_patterns ):
158+ if verbose :
159+ rel_path = dir_path .relative_to (directory )
160+ print (f" [IGNORED DIR] { rel_path } /" )
161+ else :
110162 filtered_dirs .append (d )
111163 dirs [:] = filtered_dirs
112164
113165 # Process files
114166 for filename in filenames :
115167 file_path = root_path / filename
116168
169+ # Calculate relative path for display/remote
170+ rel_path = file_path .relative_to (directory )
171+ remote_path = str (rel_path ).replace ("\\ " , "/" )
172+
117173 # Check if file should be ignored
118174 if should_ignore_path (file_path , directory , ignore_patterns ):
175+ ignored_files .append (remote_path )
176+ if verbose :
177+ print (f" [IGNORED] { remote_path } " )
119178 continue
120179
121- # Calculate relative path for remote
122- rel_path = file_path .relative_to (directory )
123- # Use forward slashes for WebDAV paths
124- remote_path = str (rel_path ).replace ("\\ " , "/" )
180+ if verbose :
181+ print (f" [INCLUDE] { remote_path } " )
125182
126183 files .append ((file_path , remote_path ))
127184
185+ if verbose :
186+ print ("\n Summary:" )
187+ print (f" Files to upload: { len (files )} " )
188+ print (f" Files ignored: { len (ignored_files )} " )
189+
128190 return files
0 commit comments