55import logging
66import sys
77import typing as t
8+ from collections .abc import Callable
89from copy import deepcopy
10+ from datetime import datetime
911
1012from libvcs ._internal .shortcuts import create_project
1113from libvcs .url import registry as url_tools
2123if t .TYPE_CHECKING :
2224 import argparse
2325 import pathlib
24- from datetime import datetime
2526
2627 from libvcs ._internal .types import VCSLiteral
2728 from libvcs .sync .git import GitSync
2829
2930log = logging .getLogger (__name__ )
3031
32+ ProgressCallback = Callable [[str , datetime ], None ]
33+
3134
3235def clamp (n : int , _min : int , _max : int ) -> int :
3336 """Clamp a number between a min and max value."""
@@ -124,6 +127,8 @@ def sync(
124127 formatter = OutputFormatter (output_mode )
125128 colors = Colors (get_color_mode (color ))
126129
130+ is_human = formatter .mode == OutputMode .HUMAN
131+
127132 if config :
128133 configs = load_configs ([config ])
129134 else :
@@ -140,7 +145,7 @@ def sync(
140145 name = repo_pattern
141146
142147 found = filter_repos (configs , path = path , vcs_url = vcs_url , name = name )
143- if not found :
148+ if not found and is_human :
144149 log .info (NO_REPOS_FOR_TERM_MSG .format (name = name ))
145150 found_repos .extend (found )
146151
@@ -163,6 +168,17 @@ def sync(
163168
164169 summary = {"total" : 0 , "synced" : 0 , "previewed" : 0 , "failed" : 0 }
165170
171+ progress_callback : ProgressCallback
172+ if is_human :
173+ progress_callback = progress_cb
174+ else :
175+
176+ def silent_progress (_output : str , _timestamp : datetime ) -> None :
177+ """Suppress progress for machine-readable output."""
178+ return None
179+
180+ progress_callback = silent_progress
181+
166182 for repo in found_repos :
167183 repo_name = repo .get ("name" , "unknown" )
168184 repo_path = repo .get ("path" , "unknown" )
@@ -181,23 +197,25 @@ def sync(
181197 summary ["previewed" ] += 1
182198 event ["status" ] = "preview"
183199 formatter .emit (event )
184- log .info (f"Would sync { repo_name } at { repo_path } " )
200+ if is_human :
201+ log .info (f"Would sync { repo_name } at { repo_path } " )
185202 formatter .emit_text (
186203 f"{ colors .warning ('→' )} Would sync { colors .info (repo_name )} "
187204 f"{ colors .muted ('→' )} { repo_path } " ,
188205 )
189206 continue
190207
191208 try :
192- update_repo (repo )
209+ update_repo (repo , progress_callback = progress_callback )
193210 except Exception as e :
194211 summary ["failed" ] += 1
195212 event ["status" ] = "error"
196213 event ["error" ] = str (e )
197214 formatter .emit (event )
198- log .info (
199- f"Failed syncing { repo_name } " ,
200- )
215+ if is_human :
216+ log .info (
217+ f"Failed syncing { repo_name } " ,
218+ )
201219 if log .isEnabledFor (logging .DEBUG ):
202220 import traceback
203221
@@ -275,6 +293,7 @@ def __init__(self, repo_url: str, *args: object, **kwargs: object) -> None:
275293
276294def update_repo (
277295 repo_dict : t .Any ,
296+ progress_callback : ProgressCallback | None = None ,
278297 # repo_dict: Dict[str, Union[str, Dict[str, GitRemote], pathlib.Path]]
279298) -> GitSync :
280299 """Synchronize a single repository."""
@@ -283,7 +302,8 @@ def update_repo(
283302 repo_dict ["pip_url" ] = repo_dict .pop ("url" )
284303 if "url" not in repo_dict :
285304 repo_dict ["url" ] = repo_dict .pop ("pip_url" )
286- repo_dict ["progress_callback" ] = progress_cb
305+
306+ repo_dict ["progress_callback" ] = progress_callback or progress_cb
287307
288308 if repo_dict .get ("vcs" ) is None :
289309 vcs = guess_vcs (url = repo_dict ["url" ])
0 commit comments